如何将控件放在 JTable 的 JTableHeader 中?

给定一个带有类型列的 JTable默认的呈现器是 。根据用户选择选择单个单元格非常简单,但选中所有复选框或不选中所有复选框也可能很方便。这些最近的例子提到了在表头中使用,但实现很尴尬,没有吸引力。如果我不需要对列进行排序,如何在JTableHeader中放置一个行为良好的控件?Boolean.classJCheckBoxJCheckBox

附录:为了方便起见,我添加了我的sscce作为答案,但我很乐意接受一个答案,解决问题的良好方面。


答案 1

如何使用表:使用自定义呈现器一文提供了 TableSorter 作为如何检测列标题上的鼠标事件的示例。使用类似的方法,并在下面的示例中达到类似的效果。A 用于在所有复选框都处于统一状态时调节切换按钮。SelectAllHeader extends JToggleButtonimplements TableCellRendererTableModelListener

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();
    }
}

答案 2

问题有两个部分(正如我所看到的:-)

可用性:发明UI交互/元素容易使用户感到困惑。排名不分先后:

  • 列标题标题旨在描述列的内容,当用操作描述替换内容描述时,内容描述会丢失
  • 它不是立即(对我来说,地球上最愚蠢的用户:-)清除标题单元格具有切换按钮的功能。意外单击它将失去该列中所有早期内容状态

因此,即使交互分析得出了明确的“我们确实需要/想要它”,但

  • 仅对内容执行操作
  • 使用更清晰的小部件(例如,三态复选框全部删除/选择,混合内容)。此外,必须从混合内容中进行去/选择。仔细想想,复选框可能也不是最佳选择,没有进一步挖掘
  • 尽量减少意外的可能性(只为我:-)更改批量状态,(例如,通过清晰地将活动区域 - 复选框图标)与“正常标题”区域分开。

技术方面

  • TableHeader 不是为“实时”组件设计的。任何想要的东西都必须由我们自己控制
  • 示例就在附近(例如,JIDE网格支持添加组件)
  • 摆弄标题往往看起来没有吸引力,因为更改渲染器同时保持LAF提供的外观并非易事

推荐