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 static void columnSort(ActionEvent e, SortOrder order) {
  Object o = e.getSource();
  if (o instanceof JTable) {
    JTable table = (JTable) o;
    JTableHeader header = table.getTableHeader();
    if (header != null) {
      table.getActionMap().get("focusHeader").actionPerformed(e);
      int col = table.getSelectedColumn();
      sort(table, col, order);
      int id = ActionEvent.ACTION_PERFORMED;
      String cmd = "focusTable";
      ActionEvent ae = new ActionEvent(header, id, cmd);
      header.getActionMap().get(cmd).actionPerformed(ae);
    }
  } else if (o instanceof JTableHeader) {
    JTableHeader header = (JTableHeader) o;
    JTable table = header.getTable();
    int col = getSelectedColumnIndex(header);
    sort(table, col, order);
  }
}

private static void sort(JTable table, int col, SortOrder order) {
  if (col >= 0) {
    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からfocusTableアクションを取得、実行してJTableにフォーカスを戻す
    • 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