有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java Swing JTable行边框在应用Nimbus LookAndFeel后消失

我有一个Java8程序,其中-Parent是一个JFrame,它有菜单、几个按钮、一个文本字段和一个具有固定数量的不可编辑行的JTable。无法动态更改行数和数据数

菜单中有UIManager.getInstalledLookAndFeels()
最初,JTable行边框可见
如果LookAndFeel更改为[Nimbus javax.swing.plaf.nimbus.NimbusLookAndFeel],然后尝试任何其他LookAndFeel,则行边框将消失
我正在使用SwingUtilities.updateComponentTreeUI(parentFrame)应用LnF。LnF应用于包括JTable在内的所有组件,但一旦Nimbus LnF应用,然后选择任何其他LnF,rows border就消失了
作为一种选择repaint()没有任何区别

形象地

  • (1) 当程序启动时,行边框是可见的
  • (2) 当应用Nimbus LnF时
  • (3) LnF已更改为“金属”,但行边框不可见

请建议

Application

示例代码:

package com.sv.runcmd;

import com.sv.core.logger.MyLogger;
import com.sv.swingui.SwingUtils;
import com.sv.swingui.component.AppExitButton;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import static com.sv.core.Constants.SP_DASH_SP;
import static com.sv.swingui.UIConstants.EMPTY_BORDER;

public class LnFExample extends JFrame {

    public static void main(String[] args) {
        new LnFExample().initComponents();
    }

    private static final String APP_TITLE = "LnF";

    private DefaultTableModel model;
    private JTable tblCommands;

    private JMenuBar mbarSettings;

    public LnFExample() {
        super(APP_TITLE);
        SwingUtilities.invokeLater(this::initComponents);
    }

    /**
     * This method initializes the form.
     */
    private void initComponents() {

        Container parentContainer = getContentPane();
        parentContainer.setLayout(new BorderLayout());

        JButton btnExit = new AppExitButton(true);
        createTable();

        JPanel topPanel = new JPanel(new GridLayout(2, 1));
        topPanel.add(btnExit);
        topPanel.setBorder(EMPTY_BORDER);

        JPanel lowerPanel = new JPanel(new BorderLayout());
        JScrollPane jspCmds = new JScrollPane(tblCommands);
        lowerPanel.add(jspCmds);

        parentContainer.add(topPanel, BorderLayout.NORTH);
        parentContainer.add(lowerPanel, BorderLayout.CENTER);

        btnExit.addActionListener(evt -> exitForm());
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent evt) {
                exitForm();
            }
        });

        createAppMenu();
        setPosition();
    }

    private final MyLogger logger = MyLogger.createLogger("rc.log");

    private void createAppMenu() {
        mbarSettings = new JMenuBar();
        JMenu menuSettings = new JMenu("Settings");
        menuSettings.add(getThemesMenu());
        mbarSettings.add(menuSettings);

        setJMenuBar(mbarSettings);
    }

    public UIManager.LookAndFeelInfo[] getAvailableLAFs() {
        return UIManager.getInstalledLookAndFeels();
    }

    public JMenu getThemesMenu() {

        JMenu menu = new JMenu("Theme");

        int i = 'a';
        int x = 0;
        for (UIManager.LookAndFeelInfo l : getAvailableLAFs()) {
            JMenuItem mi = new JMenuItem((char) i + SP_DASH_SP + l.getName());
            if (i <= 'z') {
                mi.setMnemonic(i);
            }
            int finalX = x;
            mi.addActionListener(e -> applyTheme(finalX, l));
            menu.add(mi);
            i++;
            x++;
        }
        return menu;
    }

    UIManager.LookAndFeelInfo themeToApply;

    public void applyTheme(int idx, UIManager.LookAndFeelInfo lnf) {
        themeToApply = lnf;
        SwingUtilities.invokeLater(this::applyLnF);
    }

    public void applyLnF() {
        try {
            UIManager.setLookAndFeel(themeToApply.getClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
        SwingUtilities.updateComponentTreeUI(this);
    }

    private void createTable() {
        model = SwingUtils.getTableModel(new String[]{"Col1"});
        createRows();
        Border borderBlue = new LineBorder(Color.BLUE, 1);
        tblCommands = new JTable(model);
    }

    private void createRows() {
        for (int i = 1; i <= 10; i++) {
            model.addRow(new String[]{"Row- " + i});
        }
    }

    private void setPosition() {
        // Setting to right most position
        pack();

        GraphicsConfiguration config = getGraphicsConfiguration();
        Rectangle bounds = config.getBounds();
        Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(config);

        int x = bounds.x + bounds.width - insets.right - getWidth();
        int y = bounds.y + insets.top + 10;

        setLocation(x, y);
        setVisible(true);
    }

    /**
     * Exit the Application
     */
    private void exitForm() {
        setVisible(false);
        dispose();
        logger.dispose();
        System.exit(0);
    }

}

共 (3) 个答案

  1. # 1 楼答案

    在Windows10上使用JDK11也可以得到同样的结果

    我怀疑问题出在UIResource接口上。注意,这只是一个标记接口,没有实际的实现方法

    我的理解是,这个接口应该在Swing组件的属性上实现。例如关于字体、边框、颜色、图标属性的各种组件

    然后,当您调用SwingUtilities.updateComponentTreeUI(parentFrame)时,实现UIResource的所有属性都将替换为新LAF中相应的属性

    签出UIManager Defaults。它将列出每个Swing组件的所有属性

    您将看到,对于大多数LAF,属性位于FontUIResourceColorUIResource等实例中

    然而,对于Nimbus LAF,许多属性缺少“…UIResource”

    所以我建议这是一个灵气问题,我不知道如何解决它

    编辑:

    hard to tell while debugging if this was a Metal LaF issue ... or a Nimbus issue

    这是一个灵气问题

    从上述链接下载代码,然后进行以下更改:

    SwingUtilities.updateComponentTreeUI( rootPane );
    System.out.println(table.getDefaultRenderer(Object.class));
    

    现在在非NImbus之间切换LAF,您将看到非NImbus LAF的默认表呈现器在类名中包含UIResource,这将向我指示它们实现UIResource接口

    现在切换到Nimbus LAF。呈现程序在类名中没有UIResource

    现在切换回任何其他LAF,渲染不正确,因为Nimbus渲染器尚未替换为正确的LAF渲染器

    注意,这不是LAF问题。它就是这样设计的。这允许您创建可在所有LAF中使用的自定义渲染器(当然,除非您使用UIResource接口标记渲染器)

    出于某些原因,Nimbus开发人员似乎没有使用UIResource接口标记渲染器,因此一旦设置了渲染器,它们就不会随LAF的其余部分而更改

    因此,另一个解决方案是创建一个“包装器”呈现器,它只包装一个默认呈现器并调用其默认呈现器逻辑,但也将实现UIResource接口。然后需要用包装器渲染器替换每个默认的Nimbus渲染器

  2. # 3 楼答案

    正如评论和@camickr所讨论的,这可能是NimbusMetal LaF的一个bug

    不过,我已经制定了一个解决方案,您现在可以使用

    基本上,我覆盖了JTableprepareRenderer,并检查是否正在使用金属LaF(并且它在启动时未设置,或者它将在JTable周围绘制两个边框),如果满足这些条件,我们只需将每行的边框设置为new MetalBorders.TableHeaderBorder()

    @Override
    public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
        JComponent component = (JComponent) super.prepareRenderer(renderer, row, column);
        if (UIManager.getLookAndFeel().getName().equals("Metal") && !wasMetalOnStartup) {
            component.setBorder(new MetalBorders.TableHeaderBorder());
        }
        return component;
    }
    

    enter image description here

    TestApp。java:

    import java.awt.BorderLayout;
    import java.awt.Component;
    import java.awt.event.ActionEvent;
    import java.util.UUID;
    import javax.swing.JButton;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JTable;
    import javax.swing.SwingUtilities;
    import javax.swing.UIManager;
    import javax.swing.border.EmptyBorder;
    import javax.swing.plaf.metal.MetalBorders;
    import javax.swing.plaf.metal.MetalLookAndFeel;
    import javax.swing.plaf.metal.OceanTheme;
    import javax.swing.plaf.nimbus.NimbusLookAndFeel;
    import javax.swing.table.DefaultTableModel;
    import javax.swing.table.TableCellRenderer;
    
    public class TestApp {
    
        private JTable table;
        private boolean wasMetalOnStartup = false;
    
        public TestApp() {
            setNimbusLookAndFeel();
            initComponents();
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(TestApp::new);
        }
    
        private void initComponents() {
            wasMetalOnStartup = UIManager.getLookAndFeel().getName().equals("Metal");
            JFrame frame = new JFrame("TestApp");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            JPanel panel = new JPanel();
            panel.setLayout(new BorderLayout());
            panel.setBorder(new EmptyBorder(10, 10, 10, 10));
    
            // setup chmahe LaF button
            JButton refreshButton = new JButton("Change L&F");
            refreshButton.addActionListener((ActionEvent e) -> {
                try {
                    if (!UIManager.getLookAndFeel().getName().equals("Nimbus")) {
                        setNimbusLookAndFeel();
                    } else {
                        setMetalLookAndFeel();
                    }
                    SwingUtilities.updateComponentTreeUI(frame);
                } catch (Exception ex) {
    
                }
            });
    
            table = new JTable() {
                private static final long serialVersionUID = 1L;
    
                @Override
                public boolean isCellEditable(int row, int column) {
                    return false;
                }
    
                @Override
                public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
                    JComponent component = (JComponent) super.prepareRenderer(renderer, row, column);
                    if (UIManager.getLookAndFeel().getName().equals("Metal") && !wasMetalOnStartup) {
                        component.setBorder(new MetalBorders.TableHeaderBorder());
                    }
                    return component;
                }
    
            };
    
            // setup JTable and custom table model with intial data
            Object[][] data = getRandomData();
            String[] columnNames = {"Random Data"};
            DefaultTableModel model = new DefaultTableModel(data, columnNames);
            table.setModel(model);
            table.getColumnModel().getColumn(0).setPreferredWidth(300);
            table.setPreferredScrollableViewportSize(table.getPreferredSize());
    
            // add components to the panel
            JScrollPane pane = new JScrollPane(table);
            panel.add(pane, BorderLayout.CENTER);
            panel.add(refreshButton, BorderLayout.SOUTH);
    
            frame.add(panel);
            frame.pack();
            frame.setVisible(true);
    
        }
    
        private void setNimbusLookAndFeel() {
            try {
                UIManager.setLookAndFeel(new NimbusLookAndFeel());
                wasMetalOnStartup = false;
            } catch (Exception ex) {
    
            }
        }
    
        private void setMetalLookAndFeel() {
            try {
                MetalLookAndFeel.setCurrentTheme(new OceanTheme());
                UIManager.setLookAndFeel(new MetalLookAndFeel());
                wasMetalOnStartup = false;
            } catch (Exception ex) {
    
            }
        }
    
        private Object[][] getRandomData() {
            Object[][] data = {{UUID.randomUUID()}, {UUID.randomUUID()}, {UUID.randomUUID()}, {UUID.randomUUID()}};
            return data;
        }
    }