Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I put a control in the JTableHeader of a JTable?

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.

like image 883
trashgod Avatar asked Aug 21 '11 10:08

trashgod


People also ask

Can I add buttons in JTable?

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.

How do you make a JTable cell editable?

jTableAssignments = new javax. swing. JTable() { public boolean isCellEditable(int rowIndex, int colIndex) { return editable; }};

How to hide the table header of a JTable in JTable?

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.

What is a JTable in MVC?

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.

What is the use of default table model in JTable?

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.

What is the default renderer for a boolean column in JTable?

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.


Video Answer


2 Answers

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.

enter image description here

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();     } } 
like image 160
trashgod Avatar answered Sep 22 '22 04:09

trashgod


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:

  • the column header title is meant to describe the content of the column, that content description is lost when in replacing it with an action description
  • it's not immediately (for me, the dumbest user on earth :-) clear that the header cell has the function of a toggle button. Accidentally clicking it will loose all earlier content state in that column

So even if interaction analysis comes out with a clear we-do-need/want-it,

  • action only in-addition to the content
  • use a widget that's clearer (e.g. a tri-state checkbox all-de-/selected, mixed content). Also, de-/selecting must both be possible from mixed content. On second thought, a checkbox probably isn't the best choice either, didn't dig further
  • minimize the possibility to accidentally (just for me :-) change bulk state, (e.g. by a clear visual separation of an active area - the checkbox icon) from the "normal header" region.

Technical aspects

  • TableHeader is not designed for "live" components. Whatever is wanted has to be controlled by ourselves
  • examples are around (e.g. JIDE grid supports adding components)
  • fiddling with header tends to look unattractive because it's not trivial to change the renderer and at the same time keep the LAF provided appearance
like image 43
kleopatra Avatar answered Sep 22 '22 04:09

kleopatra