Search code examples
javaswingtablecellrendererjtableheader

Jtable, issues with displaying sorting icon TableHeader with custom renderer and system L&F


I created a custom renderer to my JTabe header but after sorting the table, the sorting icon does not appear when I'm using the default system L&F (in my case windows 8) but the icon appears when I use the Java L&F.
With Java L&F:
enter image description here

With System L&F:
enter image description here

And this is my class renderer:

private class HeaderRenderer implements TableCellRenderer{
        private final LineBorder lb = new LineBorder(new Color(0, 152, 206));
        private final Dimension dim = new Dimension(150, 25);
        private TableCellRenderer delegate;

        public HeaderRenderer(TableCellRenderer delegate) {
            this.delegate = delegate;
        }

        @Override
        public Component getTableCellRendererComponent(JTable table,Object value, boolean isSelected, boolean hasFocused, int row, int column) {
            Component comp = delegate.getTableCellRendererComponent(table, value, isSelected, hasFocused, row, column);

            if (comp instanceof JLabel) {
                JLabel label = (JLabel) comp;
                label.setBackground(Color.WHITE);
                label.setPreferredSize(dim);
                label.setHorizontalAlignment(SwingConstants.CENTER);
                label.setBorder(lb);
            }

            return comp;
        }

    }

Use:

JTableHeader tableHeaderCompnoent = table.getTableHeader();
tableHeaderCompnoent.setDefaultRenderer(new HeaderRenderer(tableHeaderCompnoent.getDefaultRenderer()));

EDIT:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;

import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;

public class TableRenderingAndSorting extends JFrame {

    private JPanel contentPane;
    private JTable table_1;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    TableRenderingAndSorting frame = new TableRenderingAndSorting();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the frame.
     */
    public TableRenderingAndSorting() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 497, 347);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(new BorderLayout(0, 0));

        table_1 = new JTable();
        table_1.setModel(new DefaultTableModel(
            new Object[][] {
                {"1", "Jack", "Developper"},
                {"2", "Richard", "Developper"},
                {"3", "Jassmine", "Developper"},
                {"4", "Tom", "Project leader"},
                {"5", "Anna", null},
            },
            new String[] {
                "Col1", "Col2", "Col3"
            }
        ));
        table_1.setAutoCreateRowSorter(true);
        contentPane.add(new JScrollPane(table_1), BorderLayout.CENTER);
        JTableHeader tableHeaderCompnoent = table_1.getTableHeader();
        tableHeaderCompnoent.setDefaultRenderer(new HeaderRenderer(tableHeaderCompnoent.getDefaultRenderer()));
        table_1.getTableHeader().setDefaultRenderer(new HeaderRenderer(tableHeaderCompnoent.getDefaultRenderer()));
    }

    private class HeaderRenderer implements TableCellRenderer{
        private final LineBorder lb = new LineBorder(new Color(0, 152, 206));
        private final Dimension dim = new Dimension(150, 25);
        private TableCellRenderer delegate;

        public HeaderRenderer(TableCellRenderer delegate) {
            this.delegate = delegate;
        }

        @Override
        public Component getTableCellRendererComponent(JTable table,Object value, boolean isSelected, boolean hasFocused, int row, int column) {
            Component comp = delegate.getTableCellRendererComponent(table, value, isSelected, hasFocused, row, column);

            if (comp instanceof JLabel) {
                JLabel label = (JLabel) comp;
                label.setBackground(Color.WHITE);
                label.setPreferredSize(dim);
                label.setHorizontalAlignment(SwingConstants.CENTER);
                label.setBorder(lb);
            }

            return comp;
        }

    }
}

Solution

  • [...] the sorting icon does not appear when I'm using the default system L&F (in my case windows 8) but the icon appears when I use the Java L&F.

    The difference between the default L&F (Metal) and the system L&F is that the first one draws the sorting icon in the center (vertical alignment) of the header cell and the latter draws it in top of the header cell.

    What happens next is that you draw a border for the header cells. A border is painted from the edge of the component inwards, meaning it paints over the component's area. Since the sorting icon is on the edge for the system L&F, it is painted over by the border.

    You can solve this by removing the border, or adding the label to a JPanel which will handle the border instead:

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocused, int row, int column) {
    
        Component comp = delegate.getTableCellRendererComponent(table, value, isSelected, hasFocused, row, column);
        JPanel panel = new JPanel(new BorderLayout());
        if (comp instanceof JLabel) {
            JLabel label = (JLabel) comp;
            label.setBackground(Color.WHITE);
            label.setHorizontalAlignment(SwingConstants.CENTER);
            panel.add(label);
            panel.setBorder(lb);
        }
        return panel;
    }
    

    I removed setPreferredSize as noted by @mKorbel and the dim field should be removed with it.

    Also, in the lines

    JTableHeader tableHeaderCompnoent = table_1.getTableHeader();
    tableHeaderCompnoent.setDefaultRenderer(new HeaderRenderer(tableHeaderCompnoent.getDefaultRenderer()));
    table_1.getTableHeader().setDefaultRenderer(new HeaderRenderer(tableHeaderCompnoent.getDefaultRenderer()));
    

    the 2nd and 3rd are the same, you can remove the 3rd.