概要

JTableのセルに行選択チェックボックスを設定してキー操作なしで複数行選択を可能にします。

サンプルコード

class CheckBoxTable extends JTable {
  private static final int CHECKBOX_COLUMN = 2;
  private transient MouseListener handler;
  private transient ListSelectionListener listener;
  private int checkedIndex = -1;

  protected CheckBoxTable(TableModel model) {
    super(model);
  }

  @Override public void updateUI() {
    addMouseListener(handler);
    getSelectionModel().removeListSelectionListener(listener);
    super.updateUI();
    setSelectionModel(new CheckBoxListSelectionModel());
    handler = new CheckBoxListener();
    addMouseListener(handler);
    listener = e -> {
      if (checkedIndex < 0) { // e.getValueIsAdjusting();
        ListSelectionModel sm = (ListSelectionModel) e.getSource();
        TableModel model = getModel();
        for (int i = 0; i < getRowCount(); i++) {
          model.setValueAt(sm.isSelectedIndex(i), i, CHECKBOX_COLUMN);
        }
      }
    };
    getSelectionModel().addListSelectionListener(listener);
  }

  @Override public Component prepareEditor(
      TableCellEditor editor, int row, int column) {
    Component c = super.prepareEditor(editor, row, column);
    if (c instanceof JCheckBox) {
      JCheckBox b = (JCheckBox) c;
      boolean selected = getSelectionModel().isSelectedIndex(row);
      b.setBackground(
        selected ? getBackground() : getSelectionBackground());
      checkedIndex = row;
    } else {
      checkedIndex = -1;
    }
    return c;
  }

  private final class CheckBoxListSelectionModel
      extends DefaultListSelectionModel {
    @Override public void setSelectionInterval(int anchor, int lead) {
      if (checkedIndex < 0) {
        super.setSelectionInterval(anchor, lead);
      } else {
        EventQueue.invokeLater(() -> {
          if (checkedIndex >= 0
              && lead == anchor
              && checkedIndex == anchor) {
            super.addSelectionInterval(checkedIndex, checkedIndex);
          } else {
            super.setSelectionInterval(anchor, lead);
          }
        });
      }
    }

    @Override public void removeSelectionInterval(
        int index0, int index1) {
      if (checkedIndex < 0) {
        super.removeSelectionInterval(index0, index1);
      } else {
        EventQueue.invokeLater(() ->
          super.removeSelectionInterval(index0, index1));
      }
    }
  }

  private final class CheckBoxListener extends MouseAdapter {
    @Override public void mousePressed(MouseEvent e) {
      JTable table = (JTable) e.getComponent();
      Point pt = e.getPoint();
      if (table.columnAtPoint(pt) == CHECKBOX_COLUMN) {
        int row = table.rowAtPoint(pt);
        checkedIndex = row;
        ListSelectionModel sm = table.getSelectionModel();
        if (sm.isSelectedIndex(row)) {
          sm.removeSelectionInterval(row, row);
        } else {
          sm.addSelectionInterval(row, row);
        }
      } else {
        checkedIndex = -1;
      }
      table.repaint();
    }
  }
}
View in GitHub: Java, Kotlin

解説

  • JTable#prepareEditor(...)をオーバーライドしてJCheckBoxをセルレンダラー、セルエディタとして使用する列がクリックされたかを判断
    • 対象列がクリックされた場合はその行、そうでない場合は-1checkedIndexに記憶
  • ListSelectionListener
    • JTable#getSelectionModel()で取得した行選択モデルListSelectionModelListSelectionListenerを追加し、JCheckBoxの選択状態と行選択状態を同期させる
    • checkedIndex < 0で以外のセルが選択された場合、全行にTableModel#setValueAt(ListSelectionModel#isSelectedIndex(i), i, CHECKBOX_COLUMN)を実行することで同期
  • DefaultListSelectionModel
    • DefaultListSelectionModel#setSelectionInterval(...)DefaultListSelectionModel#removeSelectionInterval(...)をオーバーライドしてJCheckBoxが設定された対象列が選択された場合(checkedIndex >= 0)の動作を変更
    • これらの選択状態の設定、削除をEventQueue.invokeLater(...)であとで実行することで通常の選択動作を上書き
    • DefaultListSelectionModel#setSelectionInterval(...)での選択状態設定をCtrl+クリックと同じになるようDefaultListSelectionModel#addSelectionInterval(...)に変更して他の行の選択状態を維持したままクリックした行を選択状態に追加
  • MouseListener
    • JTableMouseListenerを追加し、JCheckBoxの選択状態と行選択状態を同期させる
    • MouseListener#mousePressed(...)をオーバーライドして行が選択状態ならDefaultListSelectionModel#removeSelectionInterval(...)、未選択状態ならDefaultListSelectionModel#addSelectionInterval(...)を実行

参考リンク

コメント