Highlighting a column header of a JTable

喜欢而已 提交于 2019-11-30 14:15:18

The basic issue I had was there was no connection between the table header and the selection change. In fact, the header is really clever with it's repaints...

I ended up providing my own header, which attached a listener to the table's selection model and repainted the header on the selection changed.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.RowSorter.SortKey;
import static javax.swing.SortOrder.ASCENDING;
import static javax.swing.SortOrder.DESCENDING;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;

public class TestColumnHighlight {

    public static void main(String[] args) {
        new TestColumnHighlight();
    }

    public TestColumnHighlight() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JTable table = new JTable();
                DefaultTableModel model = new DefaultTableModel(
                                new Object[]{"abc", "def", "ghi", "jkl"},
                                0);

                model.addRow(new Object[]{0, 0, 0, 0});
                model.addRow(new Object[]{0, 0, 0, 0});
                model.addRow(new Object[]{0, 0, 0, 0});
                model.addRow(new Object[]{0, 0, 0, 0});
                model.addRow(new Object[]{0, 0, 0, 0});

                table.setModel(model);
                table.setTableHeader(new CustomTableHeader(table));
                table.getTableHeader().setDefaultRenderer(new ColumnHeaderRenderer());

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(table));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class CustomTableHeader extends JTableHeader {

        public CustomTableHeader(JTable table) {
            super();
            setColumnModel(table.getColumnModel());
            table.getColumnModel().getSelectionModel().addListSelectionListener(new ListSelectionListener() {
                @Override
                public void valueChanged(ListSelectionEvent e) {
                    repaint();
                }
            });
        }

        @Override
        public void columnSelectionChanged(ListSelectionEvent e) {
            repaint();
        }

    }

    public class ColumnHeaderRenderer extends DefaultTableHeaderCellRenderer {

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean focused, int row, int column) {
            super.getTableCellRendererComponent(table, value, selected, focused, row, column);

            int selectedColumn = table.getSelectedColumn();
            System.out.println("Selected " + selectedColumn + "-" + column);
            if (selectedColumn == column) {
                Color bg = table.getSelectionBackground();
                setBackground(bg);
                setOpaque(true);
            } else {
                setOpaque(false);
            }

            return this;
        }

    }

    public class DefaultTableHeaderCellRenderer extends DefaultTableCellRenderer {

        public DefaultTableHeaderCellRenderer() {
            setHorizontalAlignment(CENTER);
            setHorizontalTextPosition(LEFT);
            setVerticalAlignment(BOTTOM);
            setOpaque(false);
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                        boolean isSelected, boolean hasFocus, int row, int column) {
            super.getTableCellRendererComponent(table, value,
                            isSelected, hasFocus, row, column);
            JTableHeader tableHeader = table.getTableHeader();
            if (tableHeader != null) {
                setForeground(tableHeader.getForeground());
            }
            setIcon(getIcon(table, column));
            setBorder(UIManager.getBorder("TableHeader.cellBorder"));
            return this;
        }

        protected Icon getIcon(JTable table, int column) {
            SortKey sortKey = getSortKey(table, column);
            if (sortKey != null && table.convertColumnIndexToView(sortKey.getColumn()) == column) {
                switch (sortKey.getSortOrder()) {
                    case ASCENDING:
                        return UIManager.getIcon("Table.ascendingSortIcon");
                    case DESCENDING:
                        return UIManager.getIcon("Table.descendingSortIcon");
                }
            }
            return null;
        }

        protected SortKey getSortKey(JTable table, int column) {
            RowSorter rowSorter = table.getRowSorter();
            if (rowSorter == null) {
                return null;
            }

            List sortedColumns = rowSorter.getSortKeys();
            if (sortedColumns.size() > 0) {
                return (SortKey) sortedColumns.get(0);
            }
            return null;
        }
    }
}

A slight variant: as I read the question, the main problem is the header not updating on column selection change. Having a custom header listen to row selection changes doesn't help much for that scenario.

In fact, a JTableHeader already is listening to the ColumnModel and the model's change notification includes selection changes. Only the columnSelectionChange method is intentionally implemented to do nothing:

// --Redrawing the header is slow in cell selection mode.
// --Since header selection is ugly and it is always clear from the
// --view which columns are selected, don't redraw the header.

A custom header can simply implement to repaint (here lazy me does it in the table's factory method just to spare me the wiring to the table, you can easily make it a stand-alone class :-).

final JTable table = new JTable(new AncientSwingTeam()) {

    @Override
    protected JTableHeader createDefaultTableHeader() {
        // subclassing to take advantage of super's auto-wiring
        // as ColumnModelListener
        JTableHeader header = new JTableHeader(getColumnModel()) {

            @Override
            public void columnSelectionChanged(ListSelectionEvent e) {
                repaint();
            }

        };
        return header;
    }

};
table.setCellSelectionEnabled(true);
table.getTableHeader().setDefaultRenderer(new ColumnHeaderRenderer());

Also tweaked Mad's renderer a bit, using table api:

/**
 * Slightly adjusted compared to @Mad
 * - use table's selectionBackground
 * - use table's isColumnSelected to decide on highlight
 */
public static class ColumnHeaderRenderer extends DefaultTableCellHeaderRenderer {

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean selected,  
        boolean focused, int row, int column) {
        super.getTableCellRendererComponent(table, value, selected, focused, row, column); 
        if (table.isColumnSelected(column)) {
            setBackground(table.getSelectionBackground());
        }
        return this;
    }
}

As to the observation:

always says isSelected is false

The reason is a slight quirk in BasicTableHeaderUI:

ui.selected != columnModel.selected

uiSelected is the column which will be accessible to keybindings - if the laf supports it and the header is focusOwner. Doesn't really make sense to me, but fully defining the semantics of ui and columnModel selection fell into the excitement about the new babe fx, that is got forgotten ;-)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!