• category: swing folder: TableSortActionMap title: JTableのソートをキー入力で実行する tags: [JTable, JTableHeader, TableRowSorter, ActionMap, InputMap] author: aterai pubdate: 2025-01-20T02:05:16+09:00 description: JTableやJTableHeaderにフォーカスが存在する場合、マウスクリックではなくキー入力でソート可能になるよう設定します。 image: https://drive.google.com/uc?id=1kqcmLvCkScEmE0J8tuGG14Kasonb3XOZ

Summary

JTableJTableHeaderにフォーカスが存在する場合、マウスクリックではなくキー入力でソート可能になるよう設定します。

Source Code Examples

private void initMap(JTable table, String key, String ks, SortOrder order) {
  Action a = new AbstractAction() {
    @Override public void actionPerformed(ActionEvent e) {
      columnSort(e, order);
    }
  };
  table.getActionMap().put(key, a);
  InputMap im1 = table.getInputMap(WHEN_FOCUSED);
  im1.put(KeyStroke.getKeyStroke(ks), key);
  JTableHeader header = table.getTableHeader();
  header.getActionMap().put(key, a);
  InputMap im2 = header.getInputMap(WHEN_FOCUSED);
  im2.put(KeyStroke.getKeyStroke(ks), key);
}

private void columnSort(ActionEvent e, SortOrder order) {
  JTable table = null;
  int col = -1;
  Object o = e.getSource();
  if (o instanceof JTable) {
    table = (JTable) o;
    JTableHeader header = table.getTableHeader();
    if (header != null) {
      table.getActionMap().get("focusHeader").actionPerformed(e);
      col = table.getSelectedColumn();
    }
  } else if (o instanceof JTableHeader) {
    JTableHeader header = (JTableHeader) o;
    table = header.getTable();
    col = getSelectedColumnIndex(header);
  }
  if (col >= 0 && table != null) {
    RowSorter.SortKey sortKey = new RowSorter.SortKey(col, order);
    table.getRowSorter().setSortKeys(Collections.singletonList(sortKey));
  }
}

private int getSelectedColumnIndex(JTableHeader header) {
  int col = -1;
  TableColumnModel cm = header.getColumnModel();
  for (int i = 0; i < cm.getColumnCount(); i++) {
    TableCellRenderer r = cm.getColumn(i).getHeaderRenderer();
    if (r instanceof ColumnHeaderRenderer) {
      col = ((ColumnHeaderRenderer) r).getSelectedColumnIndex();
      if (col >= 0) {
        break;
      }
    }
  }
  return col;
}
View in GitHub: Java, Kotlin

Explanation

  • JTableHeaderにフォーカスが存在する場合SPACEキーでtoggleSortOrderアクションが実行されてソートが可能だが、方向指定したソートは不可能
    • JTableにフォーカスが存在する場合は、F8キーでfocusHeaderアクションを実行してJTableHeaderにフォーカス移動してからtoggleSortOrderアクションを実行する必要がある
  • JTableJTableHeaderActionMapInputMapにそれぞれ以下のソートActionKeyStrokeを追加
    • ascendantアクション、ctrl+↑キー(KeyStroke.getKeyStroke("ctrl UP"))
    • descendantアクション、ctrl+↓キー(KeyStroke.getKeyStroke("ctrl DOWN"))
    • unsortedアクション、F9キー(KeyStroke.getKeyStroke("F9"))
  • ソートActionはソート方向を保持しJTableJTableHeaderのどちらでイベントが実行されたかを判断してソートを実行する
    • JTableにフォーカスが存在する状態の場合、focusHeaderアクションを実行してJTableHeaderにフォーカス移動してからJTable#getSelectedColumn()で取得した列でソート
    • JTableHeaderにフォーカスが存在する状態の場合、現在選択されている列を取得する必要があるがBasicTableHeaderUI#getSelectedColumnIndex()メソッドはprivateで使用不可能のため、フォーカスが存在するTableColumnのインデックスを保持するだけのTableCellRendererTableColumn#setHeaderRenderer(...)ですべての列に設定
      • TableCellRenderer#getTableCellRendererComponent(...)以下のようにフォーカスが存在する場合の列インデックスを保持する(選択状態isSelectedは列選択モデルでは常に-1で使用不可能)
class ColumnHeaderRenderer implements TableCellRenderer {
  private int selectedIndex = -1;

  @Override public Component getTableCellRendererComponent(
      JTable table, Object value, boolean isSelected, boolean hasFocus,
      int row, int column) {
    if (hasFocus) {
      selectedIndex = column;
    }
    TableCellRenderer r = table.getTableHeader().getDefaultRenderer();
    return r.getTableCellRendererComponent(
        table, value, isSelected, hasFocus, row, column);
  }

  public int getSelectedColumnIndex() {
    return selectedIndex;
  }
}

Reference

Comment