Given a JTable
with a column of type Boolean.class
, the default renderer is a JCheckBox
. It's easy enough to select individual cells based on a user selection, but it may be convenient to select all or none of the check boxes, too. These recent examples mentioned using JCheckBox
in the table header, but the implementation was awkward and unappealing. If I don't need to sort the column, how can I put a well-behaved control in the JTableHeader
?
Addendum: For convenience, I've added my sscce as an answer, but I'd be pleased to accept an answer that addresses the well-behaved aspect of the problem.
We can add or insert a JButton to JTable cell by customizing the code either in DefaultTableModel or AbstractTableModel and we can also customize the code by implementing TableCellRenderer interface and need to override getTableCellRendererComponent() method.
jTableAssignments = new javax. swing. JTable() { public boolean isCellEditable(int rowIndex, int colIndex) { return editable; }};
We can hide the table header of a JTable by unchecking the JCheckBox and show the table header of a JTable by clicking the JCheckBox.
A JTable is a subclass of JComponent class for displaying complex data structures. A JTable can follow the Model View Controller (MVC) design pattern for displaying the data in rows and columns. The DefaultTableModel class is a subclass of AbstractTableModel and it can be used to add the rows and columns to a JTable dynamically.
The DefaultTableModel class is a subclass of AbstractTableModel and it can be used to add the rows and columns to a JTable dynamically. The DefaultTableCellRenderer class can extend JLabel class and it can be used to add images, colored text and etc. inside the JTable cell.
Given a JTable with a column of type Boolean.class, the default renderer is a JCheckBox. It's easy enough to select individual cells based on a user selection, but it may be convenient to select all or none of the check boxes, too.
The article How to Use Tables: Using Custom Renderers offers TableSorter
as an example of how to detect mouse events on a column header. Using a similar approach, SelectAllHeader extends JToggleButton
and implements TableCellRenderer
in the example below to achieve a similar effect. A TableModelListener
is used to condition the toggle button when all the check boxes are in a uniform state.
import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.*; /** * @see http://stackoverflow.com/questions/7137786 * @see http://stackoverflow.com/questions/7092219 * @see http://stackoverflow.com/questions/7093213 */ public class SelectAllHeaderTest { private static final int BOOLEAN_COL = 2; private static final Object colNames[] = {"Column 1", "Column 2", ""}; private DefaultTableModel model = new DefaultTableModel(null, colNames) { @Override public Class<?> getColumnClass(int columnIndex) { if (columnIndex == BOOLEAN_COL) { return Boolean.class; } else { return String.class; } } }; private JTable table = new JTable(model); public void create() { for (int x = 1; x < 6; x++) { model.addRow(new Object[]{ "Row " + x + ", Col 1", "Row " + x + ", Col 2", false }); } table.setAutoCreateRowSorter(true); table.setPreferredScrollableViewportSize(new Dimension(320, 160)); TableColumn tc = table.getColumnModel().getColumn(BOOLEAN_COL); tc.setHeaderRenderer(new SelectAllHeader(table, BOOLEAN_COL)); JFrame f = new JFrame(); f.add(new JScrollPane(table)); f.pack(); f.setLocationRelativeTo(null); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new SelectAllHeaderTest().create(); } }); } } /** * A TableCellRenderer that selects all or none of a Boolean column. * * @param targetColumn the Boolean column to manage */ class SelectAllHeader extends JToggleButton implements TableCellRenderer { private static final String ALL = "✓ Select all"; private static final String NONE = "✓ Select none"; private JTable table; private TableModel tableModel; private JTableHeader header; private TableColumnModel tcm; private int targetColumn; private int viewColumn; public SelectAllHeader(JTable table, int targetColumn) { super(ALL); this.table = table; this.tableModel = table.getModel(); if (tableModel.getColumnClass(targetColumn) != Boolean.class) { throw new IllegalArgumentException("Boolean column required."); } this.targetColumn = targetColumn; this.header = table.getTableHeader(); this.tcm = table.getColumnModel(); this.applyUI(); this.addItemListener(new ItemHandler()); header.addMouseListener(new MouseHandler()); tableModel.addTableModelListener(new ModelHandler()); } @Override public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { return this; } private class ItemHandler implements ItemListener { @Override public void itemStateChanged(ItemEvent e) { boolean state = e.getStateChange() == ItemEvent.SELECTED; setText((state) ? NONE : ALL); for (int r = 0; r < table.getRowCount(); r++) { table.setValueAt(state, r, viewColumn); } } } @Override public void updateUI() { super.updateUI(); applyUI(); } private void applyUI() { this.setFont(UIManager.getFont("TableHeader.font")); this.setBorder(UIManager.getBorder("TableHeader.cellBorder")); this.setBackground(UIManager.getColor("TableHeader.background")); this.setForeground(UIManager.getColor("TableHeader.foreground")); } private class MouseHandler extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { viewColumn = header.columnAtPoint(e.getPoint()); int modelColumn = tcm.getColumn(viewColumn).getModelIndex(); if (modelColumn == targetColumn) { doClick(); } } } private class ModelHandler implements TableModelListener { @Override public void tableChanged(TableModelEvent e) { if (needsToggle()) { doClick(); header.repaint(); } } } // Return true if this toggle needs to match the model. private boolean needsToggle() { boolean allTrue = true; boolean allFalse = true; for (int r = 0; r < tableModel.getRowCount(); r++) { boolean b = (Boolean) tableModel.getValueAt(r, targetColumn); allTrue &= b; allFalse &= !b; } return allTrue && !isSelected() || allFalse && isSelected(); } }
There are two parts of the problem (as I see it :-)
Usability: inventing UI-interaction/elements is prone to confusing users. In no particular order:
So even if interaction analysis comes out with a clear we-do-need/want-it,
Technical aspects
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With