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

前端 未结 3 1859
深忆病人
深忆病人 2020-11-22 00:12

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 us

相关标签:
3条回答
  • 2020-11-22 00:22

    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
    0 讨论(0)
  • 2020-11-22 00:32

    enter image description here

    Use a custom TableCellRenderer:

        // column 1
        col = table.getColumnModel().getColumn(1);
        col.setHeaderRenderer(new EditableHeaderRenderer( new JButton("Button")));
        // column 2     
        col = table.getColumnModel().getColumn(2);
        col.setHeaderRenderer(new EditableHeaderRenderer( new JToggleButton("Toggle")));
        // column 3
        col = table.getColumnModel().getColumn(3);
        col.setHeaderRenderer(new EditableHeaderRenderer( new JCheckBox("CheckBox")));
    
    
    
    class EditableHeaderRenderer implements TableCellRenderer {
    
        private JTable table = null;
        private MouseEventReposter reporter = null;
        private JComponent editor;
    
        EditableHeaderRenderer(JComponent editor) {
            this.editor = editor;
            this.editor.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
        }
    
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
            if (table != null && this.table != table) {
                this.table = table;
                final JTableHeader header = table.getTableHeader();   
                if (header != null) {   
                    this.editor.setForeground(header.getForeground());   
                    this.editor.setBackground(header.getBackground());   
                    this.editor.setFont(header.getFont());
                    reporter = new MouseEventReposter(header, col, this.editor);
                    header.addMouseListener(reporter);
                }
            }
    
            if (reporter != null) reporter.setColumn(col);
    
            return this.editor;
        }
    
        static public class MouseEventReposter extends MouseAdapter {
    
            private Component dispatchComponent;
            private JTableHeader header;
            private int column  = -1;
            private Component editor;
    
            public MouseEventReposter(JTableHeader header, int column, Component editor) {
                this.header = header;
                this.column = column;
                this.editor = editor;
            }
    
            public void setColumn(int column) {
                this.column = column;
            }
    
            private void setDispatchComponent(MouseEvent e) {
                int col = header.getTable().columnAtPoint(e.getPoint());
                if (col != column || col == -1) return;
    
                Point p = e.getPoint();
                Point p2 = SwingUtilities.convertPoint(header, p, editor);
                dispatchComponent = SwingUtilities.getDeepestComponentAt(editor, p2.x, p2.y);
            }
    
            private boolean repostEvent(MouseEvent e) {
                if (dispatchComponent == null) {
                    return false;
                }
                MouseEvent e2 = SwingUtilities.convertMouseEvent(header, e, dispatchComponent);
                dispatchComponent.dispatchEvent(e2);
                return true;
            }
    
            @Override
            public void mousePressed(MouseEvent e) {
                if (header.getResizingColumn() == null) {
                    Point p = e.getPoint();
    
                    int col = header.getTable().columnAtPoint(p);
                    if (col != column || col == -1) return;
    
                    int index = header.getColumnModel().getColumnIndexAtX(p.x);
                    if (index == -1) return;
    
                    editor.setBounds(header.getHeaderRect(index));
                    header.add(editor);
                    editor.validate();
                    setDispatchComponent(e);
                    repostEvent(e);
                }
            }
    
            @Override
            public void mouseReleased(MouseEvent e) {
                repostEvent(e);
                dispatchComponent = null;
                header.remove(editor);
            }
        }
    }
    

    Please note that components with popupmenu (e.g. JComboBox or JMenu) don't work well. See: JComboBox fails to expand in JTable TableHeader). But you can use a MenuButton in the TableHeader:

    enter image description here

    class MenuButtonTableHeaderRenderer extends JPanel implements TableCellRenderer {
    
        private int     column  = -1;
        private JTable  table   = null;
        private MenuButton b;
    
        MenuButtonTableHeaderRenderer(String name, JPopupMenu menu) {
            super(new BorderLayout());
            b = new MenuButton(ResourceManager.ARROW_BOTTOM, menu);
            b.setBorder(BorderFactory.createEmptyBorder(1,1,1,1));
            JLabel l = new JLabel(name);
            l.setFont(l.getFont().deriveFont(Font.PLAIN));
            l.setBorder(BorderFactory.createEmptyBorder(1,5,1,1));
            add(b, BorderLayout.WEST);
            add(l, BorderLayout.CENTER);
            setBorder(UIManager.getBorder("TableHeader.cellBorder"));
        }
    
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
    
            if (table != null && this.table != table) {
                this.table = table;
                final JTableHeader header = table.getTableHeader();   
                if (header != null) {   
                    setForeground(header.getForeground());   
                    setBackground(header.getBackground());   
                    setFont(header.getFont());
    
                    header.addMouseListener(new MouseAdapter() {
    
                        @Override
                        public void  mouseClicked(MouseEvent e) {
                            int col = header.getTable().columnAtPoint(e.getPoint());
                            if (col != column || col == -1) return;
    
                            int index = header.getColumnModel().getColumnIndexAtX(e.getPoint().x);
                            if (index == -1) return;
    
                            setBounds(header.getHeaderRect(index));
                            header.add(MenuButtonTableHeaderRenderer.this);
                            validate();
    
                            b.doClick();
    
                            header.remove(MenuButtonTableHeaderRenderer.this);
    
                            header.repaint();   
                        }
                    });
                }
            }
            column = col;
            return this;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 00:44

    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();
        }
    }
    
    0 讨论(0)
提交回复
热议问题