应用 Nimbus LookAndFeel 后,Swing JTable 行边框消失
Swing JTable row borders goes away after applying Nimbus LookAndFeel
我有一个 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);
}
}
我在 Windows 10 上使用 JDK11 也得到了相同的结果。
我怀疑问题出在 UIResource
界面上。请注意,这只是一个标记接口,没有实际的实现方法。
据我了解,此接口应在 Swing 组件的属性上实现。例如各种组件的字体、边框、颜色、图标属性。
然后当您调用 SwingUtilities.updateComponentTreeUI(parentFrame)
时,所有实现 UIResource
的属性都将替换为新 LAF 中的相应 属性。
查看 UIManager Defaults。它将列出每个 Swing 组件的所有属性。
您会看到,对于大多数 LAF,属性都在 FontUIResource
或 ColorUIResource
等的实例中
但是,对于 Nimbus LAF,许多属性缺少“...UIResource”。
所以我建议这是一个 Nimbus 问题,我不知道如何解决它。
编辑:
hard to tell while debugging if this was a Metal LaF issue ... or a Nimbus issue
这是一个 Nimbus 问题。
从上面link下载代码,然后进行以下更改:
SwingUtilities.updateComponentTreeUI( rootPane );
System.out.println(table.getDefaultRenderer(Object.class));
现在在非 NImbus 之间切换 LAF,您将看到非 Nimbus LAF 的默认 table 渲染器在 class 名称中包含 UIResource,这向我表明它们实现了 UIResource接口。
现在切换到 Nimbus LAF。渲染器在 class 名称中没有 UIResource。
现在切换回任何其他 LAF,并且渲染不正确,因为 Nimbus 渲染器尚未替换为正确的 LAF 渲染器。
请注意,这不是 LAF 问题。它旨在以这种方式工作。这允许您创建一个可以在所有 LAF 中使用的自定义渲染器(当然除非您使用 UIResource 接口标记渲染器)。
出于某种原因,Nimbus 开发人员似乎没有使用 UIResource 接口标记渲染器,因此一旦设置它们,它们就不会随着 LAF 的其余部分而改变。
因此,另一种解决方案是创建一个“包装器”渲染器,它简单地包装默认渲染器并调用其默认渲染逻辑,但也会实现 UIResource 接口。然后,您需要用包装器渲染器替换每个默认的 Nimbus 渲染器。
正如评论和@camickr 所讨论的,这可能是 Nimbus 或 Metal LaF.
的错误
但是我已经做了一个你现在可以使用的解决方法。
基本上我覆盖了 JTable
的 prepareRenderer
并检查是否正在使用 Metal LaF(并且它没有在启动时设置或者它会在 [=13= 周围绘制 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;
}
}
我收到了 Oracle 就此问题提出的错误的电子邮件回复,他们也能够将其重现为错误。
JDK-8258567。
Link : http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8258567
我有一个 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);
}
}
我在 Windows 10 上使用 JDK11 也得到了相同的结果。
我怀疑问题出在 UIResource
界面上。请注意,这只是一个标记接口,没有实际的实现方法。
据我了解,此接口应在 Swing 组件的属性上实现。例如各种组件的字体、边框、颜色、图标属性。
然后当您调用 SwingUtilities.updateComponentTreeUI(parentFrame)
时,所有实现 UIResource
的属性都将替换为新 LAF 中的相应 属性。
查看 UIManager Defaults。它将列出每个 Swing 组件的所有属性。
您会看到,对于大多数 LAF,属性都在 FontUIResource
或 ColorUIResource
等的实例中
但是,对于 Nimbus LAF,许多属性缺少“...UIResource”。
所以我建议这是一个 Nimbus 问题,我不知道如何解决它。
编辑:
hard to tell while debugging if this was a Metal LaF issue ... or a Nimbus issue
这是一个 Nimbus 问题。
从上面link下载代码,然后进行以下更改:
SwingUtilities.updateComponentTreeUI( rootPane );
System.out.println(table.getDefaultRenderer(Object.class));
现在在非 NImbus 之间切换 LAF,您将看到非 Nimbus LAF 的默认 table 渲染器在 class 名称中包含 UIResource,这向我表明它们实现了 UIResource接口。
现在切换到 Nimbus LAF。渲染器在 class 名称中没有 UIResource。
现在切换回任何其他 LAF,并且渲染不正确,因为 Nimbus 渲染器尚未替换为正确的 LAF 渲染器。
请注意,这不是 LAF 问题。它旨在以这种方式工作。这允许您创建一个可以在所有 LAF 中使用的自定义渲染器(当然除非您使用 UIResource 接口标记渲染器)。
出于某种原因,Nimbus 开发人员似乎没有使用 UIResource 接口标记渲染器,因此一旦设置它们,它们就不会随着 LAF 的其余部分而改变。
因此,另一种解决方案是创建一个“包装器”渲染器,它简单地包装默认渲染器并调用其默认渲染逻辑,但也会实现 UIResource 接口。然后,您需要用包装器渲染器替换每个默认的 Nimbus 渲染器。
正如评论和@camickr 所讨论的,这可能是 Nimbus 或 Metal LaF.
的错误但是我已经做了一个你现在可以使用的解决方法。
基本上我覆盖了 JTable
的 prepareRenderer
并检查是否正在使用 Metal LaF(并且它没有在启动时设置或者它会在 [=13= 周围绘制 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;
}
}
我收到了 Oracle 就此问题提出的错误的电子邮件回复,他们也能够将其重现为错误。
JDK-8258567。
Link : http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8258567