---
category: swing
folder: TableNextMatchKeyHandler
title: JTableで先頭文字のキー入力による検索を行う
tags: [JTable, KeyListener]
author: aterai
pubdate: 2012-04-16T15:22:21+09:00
description: JTableにフォーカスがある状態でキー入力をした場合、先頭文字が一致する行を検索して選択状態にします。
image: https://lh4.googleusercontent.com/-UATkJ0JfmBQ/T4u5j_rhkGI/AAAAAAAABLc/1rVNsbM9D98/s800/TableNextMatchKeyHandler.png
---
* 概要 [#summary]
`JTable`にフォーカスがある状態でキー入力をした場合、先頭文字が一致する行を検索して選択状態にします。

#download(https://lh4.googleusercontent.com/-UATkJ0JfmBQ/T4u5j_rhkGI/AAAAAAAABLc/1rVNsbM9D98/s800/TableNextMatchKeyHandler.png)

* サンプルコード [#sourcecode]
#code(link){{
// @see javax/swing/plaf/basic/BasicListUI.Handler
// @see javax/swing/plaf/basic/BasicTreeUI.Handler
class TableNextMatchKeyHandler extends KeyAdapter {
  private static final int TARGET_COLUMN = 0;
  private String prefix = "";
  private String typedString = "";
  private long lastTime = 0L;
  private long timeFactor;
  public TableNextMatchKeyHandler() {
    // Long l = (Long) UIManager.get("List.timeFactor");
    timeFactor = 500L; //(l != null) ? l.longValue() : 1000L;
    timeFactor = 500L; // (l != null) ? l.longValue() : 1000L;
  }

  private boolean isNavigationKey(KeyEvent e) {
    JTable t = (JTable) e.getSource();
    InputMap im = t.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
    if (im != null && im.get(key) != null) {
      return true;
    }
    return false;
  }

  @Override public void keyPressed(KeyEvent e) {
    if (isNavigationKey(e)) {
      prefix = "";
      typedString = "";
      lastTime = 0L;
    }
  }

  @Override public void keyTyped(KeyEvent e) {
    JTable src = (JTable) e.getSource();
    int max = src.getRowCount();
    if (max == 0 || e.isAltDown() || isNavigationKey(e)) {
      // Nothing to select
      return;
    }
    boolean startingFromSelection = true;
    char c = e.getKeyChar();
    int increment = e.isShiftDown() ? -1 : 1;
    long time = e.getWhen();
    int startIndex = src.getSelectedRow();
    if (time - lastTime < timeFactor) {
      typedString += c;
      if ((prefix.length() == 1) && (c == prefix.charAt(0))) {
        // Subsequent same key presses move the keyboard focus to the next
        // object that starts with the same letter.
        startIndex += increment;
      } else {
        prefix = typedString;
      }
    } else {
      startIndex += increment;
      typedString = "" + c;
      prefix = typedString;
    }
    lastTime = time;

    if (startIndex < 0 || startIndex >= max) {
      if (e.isShiftDown()) {
        startIndex = max - 1;
      } else {
        startingFromSelection = false;
        startIndex = 0;
      }
    }
    Position.Bias bias = e.isShiftDown() ? Position.Bias.Backward
                         : Position.Bias.Forward;
    int index = getNextMatch(src, prefix, startIndex, bias);
    if (index >= 0) {
      src.getSelectionModel().setSelectionInterval(index, index);
      src.scrollRectToVisible(src.getCellRect(index, TARGET_COLUMN, true));
    } else if (startingFromSelection) { // wrap
      index = getNextMatch(src, prefix, 0, bias);
      if (index >= 0) {
        src.getSelectionModel().setSelectionInterval(index, index);
        src.scrollRectToVisible(src.getCellRect(index, TARGET_COLUMN, true));
      }
    }
  }
  // @see JList#getNextMatch(String prefix, int startIndex, Position.Bias bias)
  // @see JTree#getNextMatch(String prefix, int startIndex, Position.Bias bias)
  public static int getNextMatch(
    JTable table, String prefix, int startingRow, Position.Bias bias) {
    int max = table.getRowCount();
    if (prefix == null) {
      throw new IllegalArgumentException();
    }
    if (startingRow < 0 || startingRow >= max) {
      throw new IllegalArgumentException();
    }
    String uprefix = prefix.toUpperCase(Locale.ENGLISH);
    // start search from the next/previous element froom the
    // selected element
    int increment = (bias == Position.Bias.Forward) ? 1 : -1;
    int row = startingRow;
    do {
      Object value = table.getValueAt(row, TARGET_COLUMN);
      String text = Objects.toString(value, "");
      if (text.toUpperCase(Locale.ENGLISH).startsWith(uprefix)) {
        return row;
      }
      row = (row + increment + max) % max;
    } while (row != startingRow);
    return -1;
  }
}
}}

* 解説 [#explanation]
上記のサンプルでは、キー入力で`0`列目の文字列の先頭文字が一致(大文字小文字は無視)する行を検索、選択し、その行位置までスクロールします。

検索方法は、`BasicListUI.Handler`、`BasicTreeUI.Handler`、`JList#getNextMatch(...)`、`JTree#getNextMatch(...)`のものと同等(KBD{Shift}キーを同時に押すと逆検索)です。
- 検索方法は`JList`の`BasicListUI.Handler`、`JList#getNextMatch(...)`や`JTree`の`BasicTreeUI.Handler`、`JTree#getNextMatch(...)`とほぼ同等
-- ともにKBD{Shift}キーを同時に押すと逆検索
- ただし`JList`などとは異なりデフォルトの`JTable`はキー入力でセルエディタが起動するため、このキー入力による検索を実行するためには`table.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);`としてキー入力による編集開始を無効(マウスクリック、KBD{F2}キーでの編集は可能)にする必要がある

`JList`などとは異なりデフォルトの`JTable`はキー入力でセルエディタが起動するため、このキー入力による検索を実行するためには`table.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);`としてキー入力による編集開始を無効(マウスクリック、KBD{F2}キーでの編集は可能)にする必要があります。

* 参考リンク [#reference]
- [[JListの先頭文字キー入力による検索選択を無効にする>Swing/DisablePrefixMatchSelection]]
- [https://docs.oracle.com/javase/jp/8/docs/api/javax/swing/JList.html#getNextMatch-java.lang.String-int-javax.swing.text.Position.Bias- JList#getNextMatch(...) (Java Platform SE 8)]
- [https://docs.oracle.com/javase/jp/8/docs/api/javax/swing/JTree.html#getNextMatch-java.lang.String-int-javax.swing.text.Position.Bias- JTree#getNextMatch(...) (Java Platform SE 8)]

* コメント [#comment]
#comment
#comment