JTable column header highlighting

I am currently creating a small JTable and want to highlight a column heading (and row headers - part of the row header actually works) when a cell is selected to make it easier to find related names with this cell. Here is the image:

enter image description here

I already tried disabling rendering for the header using this:

table.getTableHeader().setDefaultRenderer(new ColumnHeaderRenderer()); 

But it only gets called when I click on the title and always says isSelected is false.

This is the code that I use for line names, including the selection inside the renderer - the code is not by me, I changed it a little:

 /* * Use a JTable as a renderer for row numbers of a given main table. * This table must be added to the row header of the scrollpane that * contains the main table. */ public class RowNameTable extends JTable implements ChangeListener, PropertyChangeListener { private JTable main; public RowNameTable(JTable table) { main = table; main.addPropertyChangeListener(this); setFocusable(false); setAutoCreateColumnsFromModel(false); setModel(main.getModel()); setSelectionModel(main.getSelectionModel()); TableColumn column = new TableColumn(); column.setHeaderValue(" "); addColumn(column); column.setCellRenderer(new RowNameRenderer(main)); getColumnModel().getColumn(0).setPreferredWidth(table.getColumnModel().getColumn(0).getPreferredWidth()); setPreferredScrollableViewportSize(getPreferredSize()); } @Override public void addNotify() { super.addNotify(); Component c = getParent(); // Keep scrolling of the row table in sync with the main table. if (c instanceof JViewport) { JViewport viewport = (JViewport) c; viewport.addChangeListener(this); } } /* * Delegate method to main table */ @Override public int getRowCount() { return main.getRowCount(); } @Override public int getRowHeight(int row) { return main.getRowHeight(row); } /* * This table does not use any data from the main TableModel, * so just return a value based on the row parameter. */ @Override public Object getValueAt(int row, int column) { return Integer.toString(row + 1); } /* * Don't edit data in the main TableModel by mistake */ @Override public boolean isCellEditable(int row, int column) { return false; } // // Implement the ChangeListener // public void stateChanged(ChangeEvent e) { // Keep the scrolling of the row table in sync with main table JViewport viewport = (JViewport) e.getSource(); JScrollPane scrollPane = (JScrollPane) viewport.getParent(); scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y); } // // Implement the PropertyChangeListener // public void propertyChange(PropertyChangeEvent e) { // Keep the row table in sync with the main table if ("selectionModel".equals(e.getPropertyName())) { setSelectionModel(main.getSelectionModel()); } if ("model".equals(e.getPropertyName())) { setModel(main.getModel()); } } /* * Borrow the renderer from JDK1.4.2 table header */ private static class RowNameRenderer extends DefaultTableCellRenderer { private JTable main; public RowNameRenderer(JTable main) { this.main = main; setHorizontalAlignment(JLabel.CENTER); } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (table != null) { JTableHeader header = table.getTableHeader(); if (header != null) { setForeground(header.getForeground()); setBackground(header.getBackground()); setFont(header.getFont()); } } if (isSelected) { setFont(getFont().deriveFont(Font.BOLD)); } setText((value == null) ? "" : main.getColumnName(row)); setBorder(UIManager.getBorder("TableHeader.cellBorder")); return this; } } } 

And here we have the corresponding part for creating the table:

  costTableModel = new CostTableModel(costCalc); table = new JTable(costTableModel); table.setPreferredScrollableViewportSize(table.getPreferredSize()); table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); table.setCellSelectionEnabled(true); scrollPane = new JScrollPane(table); RowNameTable nameTable = new RowNameTable(table); scrollPane.setRowHeaderView(nameTable); 

And the costTableModel class, just for the sake of completeness:

 public class CostTableModel extends AbstractTableModel { private CostCalculator costCalc; public CostTableModel(CostCalculator costCalc) { this.costCalc = costCalc; } @Override public int getRowCount() { return costCalc.getPersonsList().size(); } @Override public int getColumnCount() { return costCalc.getPersonsList().size(); } @Override public String getColumnName(int col) { return costCalc.getPersonsList().get(col).getName(); } @Override public Object getValueAt(int rowIndex, int columnIndex) { Person debtor = costCalc.getPersonsList().get(rowIndex); Person debtee = costCalc.getPersonsList().get(columnIndex); return costCalc.getAmountOwed(debtor, debtee); } @Override public Class getColumnClass(int c) { return getValueAt(0, c).getClass(); } } 

Thanks for your help in advance!

+6
source share
2 answers

The main problem I ran into was the lack of a connection between the table header and the selection change. In fact, the headline is really smart, it is reviewing ...

I ended up providing my own title, which connected the listener to the table selection model and repainted the title when changing.

 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; } } } 
+4
source

A small option: when I read the question, the main problem is the header, which does not update when the column selection changes. Having a custom header to listen to row selection changes doesn't help much in this scenario.

In fact, the JTableHeader is already listening on the ColumnModel, and notification of a model change 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 be implemented for redrawing (here, lazy, it does this in the factory table method, to save me the wiring to the table, you can easily make it a standalone class :-).

 final JTable table = new JTable(new AncientSwingTeam()) { @Override protected JTableHeader createDefaultTableHeader() { // subclassing to take advantage of super 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 slightly modified Mad renderer using the api table:

 /** * Slightly adjusted compared to @Mad * - use table selectionBackground * - use table 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; } } 

Regarding the observation:

always says isSelected is false

The reason is a small quirk in BasicTableHeaderUI:

 ui.selected != columnModel.selected 

uiSelected is the column that will be available for key binding - if laf supports it and the heading focusOwner. Actually this does not make sense, but by fully defining the semantics of ui and columnModel, you are thrilled about the new babe fx that is forgotten; -)

+8
source

All Articles