I have a java 8 program in which - Parent is a JFrame that has Menu, few buttons, a text field and a JTable with fixed number of non-editable rows. Number of rows and data cannot be changed dynamically.
Menu has list of UIManager.getInstalledLookAndFeels()
Initially JTable rows border are visible
If LookAndFeel change to [Nimbus javax.swing.plaf.nimbus.NimbusLookAndFeel]
, and then try any other LookAndFeel, rows border goes away.
I am using SwingUtilities.updateComponentTreeUI(parentFrame)
to apply LnF. LnF is applying on all components including JTable, but once Nimbus LnF applied and after that choosing any other LnF, rows border went off.
As a option repaint()
is not making any difference.
In image
Please suggest.
Sample Code:
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);
}
}
As discussed in comments and by @camickr, this is probably a bug with Nimbus or Metal LaF.
However I have made a workaround that you can use for now.
Essentially I override prepareRenderer
of the JTable
and check if the Metal LaF is being used ( and that it wasnt set on start up or it will paint 2 borders around the JTable
) and if those conditions are met we simply set the border for each row to 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;
}
}