Highlighting a column header of a JTable
highlight, java, jtable, jtableheader, swing
Solution
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;
}
}
}
Problem
i'm currently building a little JTable, and want to highlight the column header (and row headers - the row-header part is actually working) when a cell is selected to make it easier to find the associated names with this cell. Here is a picture: I already tried switching out the renderer for the header with this: ``` table.getTableHeader().setDefaultRenderer(new ColumnHeaderRenderer()); ``` But it's only called when i click on the header and always says isSelected is false. This is the code i use for the row-names, including the highlight inside the renderer - code is not by me, i just modified 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 relevant part to create 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 class costTableModel, just for completeness sake: ``` 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(); } } ``` Thank you for your help in advance!