【问题标题】:Swing JTable row borders goes away after applying Nimbus LookAndFeelSwing JTable 行边框在应用 Nimbus LookAndFeel 后消失
【发布时间】:2020-12-11 19:08:35
【问题描述】:

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

菜单有UIManager.getInstalledLookAndFeels()列表
最初 JTable 行的边框是可见的
如果 LookAndFeel 更改为 [Nimbus javax.swing.plaf.nimbus.NimbusLookAndFeel],然后尝试任何其他 LookAndFeel,行边框就会消失。
我正在使用SwingUtilities.updateComponentTreeUI(parentFrame) 申请 LnF。 LnF 适用于包括 JTable 在内的所有组件,但是一旦应用 Nimbus LnF 并在选择任何其他 LnF 之后,行边框就会消失。
作为一个选项repaint() 没有任何区别。

在图片中

  • (1) 是程序在行边界可见的地方启动
  • (2) 应用 Nimbus LnF 时
  • (3) LnF 更改为金属,但行边框不可见

请提出建议。

示例代码:

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);
    }

}

【问题讨论】:

  • (1-) 之前有人问过这个问题:(stackoverflow.com/questions/65254253/…) 并已删除。建议是提供minimal reproducible example。我还没有看到。
  • 顺便说一句,我复制了您的问题,当我从 Nimbus 转到 Metal JTable 行边框时没有正确呈现,但是其他 L&F 不会发生这种情况,并且确实认为这是一个潜在的错误,但是请使用@camickr 所说的最小可重复示例进行更新,也许当您表现出一些努力时,其他人也会这样做
  • 正如@DavidKroukamp 所说,这可能是我向 Oracle JDK 团队报告的潜在 jdk 错误。
  • @shaILU 是的,这确实是个 bug,确保你安装了最新的 jdk 8 版本,如果仍然出现,请等待 Oracle 响应
  • @shaILU,现在已删除反对票 - 不要担心反对票,并在第一时间发布适当的问题。顺便说一句,您还没有发布minimal reproducible example (1-)。您仍在使用各种第 3 部分类,因此我们无法复制/粘贴/编译和测试您粘贴的代码。不需要这些课程。创建 JTable 需要一条语句:new JTable(3, 3);。数据无关紧要。你的问题是关于边界的。

标签: java swing jframe jtable look-and-feel


【解决方案1】:

正如 cmets 和 @camickr 所讨论的,这可能是 NimbusMetal LaF 的错误。

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

基本上我覆盖JTableprepareRenderer 并检查是否正在使用Metal LaF(并且它没有在启动时设置,否则它将在JTable 周围绘制2 个边框)以及是否满足这些条件我们只需将每一行的边框设置为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;
}

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;
    }
}

【讨论】:

  • (1+) 但我建议您可以使用:UIManager.getBorder("TableHeader.cellBorder") 为任何非 Nimbus LAF 重置它。
  • 感谢大卫和@camickr。它对我有用,我接受了ans。就像这样,你们俩现在可能已经删除了反对票,如果没有,请这样做(我仍然可以看到一个反对票)。我会记住MRE。谢谢你们俩。
  • 很高兴为您提供帮助。我从来没有投反对票。我确实赞成并撤回了我的近距离投票。如果我和@camickrs 的回答有帮助,或许也可以考虑支持他们。
【解决方案2】:

我在 Windows 10 上使用 JDK11 也得到了相同的结果。

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

我的理解是这个接口应该在 Swing 组件的属性上实现。例如关于各种组件的 Font、Border、Color、Icon 属性。

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

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

您会看到大多数 LAF 的属性都在 FontUIResourceColorUIResource 等实例中。

但是,对于 Nimbus LAF,许多属性都缺少“...UIResource”。

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

编辑:

调试时很难判断这是 Metal LaF 问题……还是 Nimbus 问题

这是一个 Nimbus 问题。

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

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 渲染器。

【讨论】:

  • 很好的解释,虽然在调试时很难判断这是否是 Metal LaF 问题,即它没有正确替换表格默认值或 Nimbus 问题,即它对表格单元格渲染器做了一些奇怪的事情(通过查看源代码,它使用了合成的 LaF)。我可以通过简单地覆盖JTableprepareRenderer 方法并将边框设置为MetalBorders.TableHeaderBorder 来修复它,但是是的,这是一种黑客行为,因为您需要跟踪这是否是第一次应用金属或者它会使边界加倍。 +1。
  • @shaILU 检查我的答案以寻找潜在的解决方法
  • @DavidKroukamp 我相信这是一个 Nimbus(不是 Metal)问题。见编辑。
  • 好伙伴!!
  • 我相信在 Nimbus 效果影响 Windows 和 WindowsClassic LnF 之后,我会相应地修改代码。
【解决方案3】:

收到了我从 Oracle 提出的关于此问题的错误的电子邮件回复,他们还能够将其作为错误重现。 JDK-8258567。
链接:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8258567

【讨论】:

    猜你喜欢
    • 2013-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-23
    相关资源
    最近更新 更多