Search code examples
javaswingjframejtablelook-and-feel

Swing JTable row borders goes away after applying Nimbus LookAndFeel


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

  • (1) is when program starts where row borders are visible
  • (2) when Nimbus LnF applied
  • (3) LnF changed to Metal but row borders are NOT visible

Please suggest.

Application

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

}

Solution

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

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