TITLE:JComboBoxで候補一覧を表示
Posted by terai at 2004-12-06

JComboBoxで候補一覧を表示

JComboBoxに入力候補の一覧表示機能*1を追加します。
  • category: swing folder: ComboBoxSuggestion title: JComboBoxで候補一覧を表示 tags: [JComboBox, KeyListener, JPopupMenu] author: aterai pubdate: 2004-12-06T09:47:32+09:00 description: JComboBoxに入力候補の一覧表示機能(補完機能、コードアシスト、コンテンツアシスト)を追加します。 image: https://lh3.googleusercontent.com/_9Z4BYR88imo/TQTJwW_1EoI/AAAAAAAAAU4/ENqthfUJCsc/s800/ComboBoxSuggestion.png hreflang:
       href: https://java-swing-tips.blogspot.com/2009/01/create-auto-suggest-jcombobox.html
       lang: en

概要

JComboBoxに入力候補の一覧表示機能(補完機能、コードアシスト、コンテンツアシスト)を追加します。

#screenshot

サンプルコード

#spanend
#spanadd
String[] array = {
#spanend
    "aaaa", "aaaabbb", "aaaabbbcc", "aaaabbbccddd",
    "abcde", "abefg", "bbb1", "bbb12"};
#spanadd
JComboBox combo = new JComboBox(array);
#spanend
#spanadd
combo.setEditable(true);
#spanend
#spanadd
combo.setSelectedIndex(-1);
#spanend
#spanadd
JTextField field = (JTextField) combo.getEditor().getEditorComponent();
#spanend
#spanadd
field.setText("");
#spanend
#spanadd
field.addKeyListener(new ComboKeyHandler(combo));
#spanend

#spandel
**サンプルコード [#c1c19fbc]
#spanend
#spandel
#code{{
#spanend
#spandel
combo.setEditable(true);
#spanend
#spandel
field = (JTextField) combo.getEditor().getEditorComponent();
#spanend
#spandel
field.addKeyListener(new KeyAdapter() {
#spanend
  @Override
  public void keyTyped(KeyEvent e) {
    keyTypedInCombo(e);
#spanadd
// ...
#spanend
#spanadd
class ComboKeyHandler extends KeyAdapter {
#spanend
  private final JComboBox<String> comboBox;
  private final List<String> list = new ArrayList<>();
  private boolean shouldHide;
#spanadd

#spanend
  public ComboKeyHandler(JComboBox<String> combo) {
    super();
    this.comboBox = combo;
    for (int i = 0; i < comboBox.getModel().getSize(); i++) {
      list.add(comboBox.getItemAt(i));
    }
  }
  @Override
  public void keyPressed(KeyEvent e) {
    keyPressedInCombo(e);
#spanadd

#spanend
  @Override public void keyTyped(final KeyEvent e) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        String text = ((JTextField) e.getComponent()).getText();
        ComboBoxModel<String> m;
        if (text.isEmpty()) {
          String[] array = list.toArray(new String[list.size()]);
          m = new DefaultComboBoxModel<String>(array);
          setSuggestionModel(comboBox, m, "");
          comboBox.hidePopup();
        } else {
          m = getSuggestedModel(list, text);
          if (m.getSize() == 0 || shouldHide) {
            comboBox.hidePopup();
          } else {
            setSuggestionModel(comboBox, m, text);
            comboBox.showPopup();
          }
        }
      }
    });
  }
  //public void keyReleased(KeyEvent e) {
#spandel
});
#spanend
#spandel
View in GitHub: Java, Kotlin
#spanend
#spandel
private boolean hide_flag = false;
#spanend
#spandel
private void keyPressedInCombo(KeyEvent ke) {
#spanend
  String text = field.getText();
  int code = ke.getKeyCode();
  if(code==KeyEvent.VK_ENTER) {
    if(!model.contains(text)) {
      model.addElement(text);
      Collections.sort(model);
      setModel(getSuggestedModel(model, text), text);
    }
    hide_flag = true; //combo.hidePopup();
  }else if(code==KeyEvent.VK_ESCAPE) {
    hide_flag = true; //combo.hidePopup();
  }else if(code==KeyEvent.VK_RIGHT) {
    for(int i=0;i<model.size();i++) {
      String str = model.elementAt(i);
      if(str.startsWith(text)) {
        combo.setSelectedIndex(-1);
        field.setText(str);
        return;
  @Override public void keyPressed(KeyEvent e) {
    JTextField textField = (JTextField) e.getComponent();
    String text = textField.getText();
    shouldHide = false;
    switch (e.getKeyCode()) {
    case KeyEvent.VK_RIGHT:
      for (String s : list) {
        if (s.startsWith(text)) {
          textField.setText(s);
          return;
        }
      }
      break;
    case KeyEvent.VK_ENTER:
      if (!list.contains(text)) {
        list.add(text);
        Collections.sort(list);
        setSuggestionModel(comboBox, getSuggestedModel(list, text), text);
      }
      shouldHide = true;
      break;
    case KeyEvent.VK_ESCAPE:
      shouldHide = true;
      break;
    default:
      break;
    }
  }
#spandel
}
#spanend
#spandel
private void keyTypedInCombo(final KeyEvent ke) {
#spanend
  EventQueue.invokeLater(new Runnable() {
    public void run() {
      String text = field.getText();
      if(text.length()==0) {
        combo.hidePopup();
        setModel(new DefaultComboBoxModel(model), "");
      }else{
        DefaultComboBoxModel m = getSuggestedModel(model, text);
        if(m.getSize()==0 || hide_flag) {
          combo.hidePopup();
          hide_flag = false;
        }else{
          setModel(m, text);
          combo.showPopup();
        }
#spanadd

#spanend
  private static void setSuggestionModel(
      JComboBox<String> comboBox, ComboBoxModel<String> mdl, String str) {
    comboBox.setModel(mdl);
    comboBox.setSelectedIndex(-1);
    ((JTextField) comboBox.getEditor().getEditorComponent()).setText(str);
  }
#spanadd

#spanend
  private static ComboBoxModel<String> getSuggestedModel(List<String> list, String text) {
    DefaultComboBoxModel<String> m = new DefaultComboBoxModel<>();
    for (String s : list) {
      if (s.startsWith(text)) {
        m.addElement(s);
      }
    }
  });
    return m;
  }
}

解説

解説

上記のサンプルでは、次のキー操作に対応しています。
  • 上下キー
    • ポップアップ表示
  • ESCキー
    • ポップアップ非表示
  • 右キー
    • 補完
  • リターンキー
    • 選択or追加
  • 文字入力
    • 候補をポップアップ
JComboBox#showPopup()とJComboBox#hidePopup()*2を使って、候補のポップアップメニュー表示を制御します。
  • Up, Downキー
    • ポップアップ表示
  • Escキー
    • ポップアップ非表示
  • Rightキー
    • 補完
  • Enterキー
    • 選択、または追加
  • 文字入力
    • 候補をポップアップ
JComboBox#setSelectedIndex(-1)で、項目の選択をクリアしないと動作がおかしくなる場合があります。
  • -
  • JComboBox#showPopup()JComboBox#hidePopup()を使って候補のポップアップメニュー表示を制御
    • 候補を表示するときJComboBox#setSelectedIndex(-1)で項目の選択を一旦クリアしないと動作がおかしくなる場合がある

コメント

  • 変換途中の日本語も、問題がないともっといいですね。 -- toshi
  • あー、日本語のこと全然考えてなかったです…。 -- terai
  • タイトルなどを変更するとしたら AutoCompletion に? -- terai
  • 日本語を考えるとKeyReleasedよりKeyTypedのほうがよさそうです -- foggi
    • ご指摘ありがとうございます。keyTyped に変更してみました*3。 -- terai
      • -
  • JComboBoxではなくSwingSet3JHistoryTextField.java のようにJTextField+JPopupMenuを使用することも可能だが、画面の下側で候補数が変更された場合のJPopupMenuの位置更新が面倒
  • 以下のようにArrowButtonを非表示にしてJTextField風に見せかける方法もある
#spanend
#spanadd
// UIManager.put("ComboBox.squareButton", Boolean.FALSE);
#spanend
#spanadd
JComboBox = new JComboBox(model) {
#spanend
  @Override public void updateUI() {
    super.updateUI();
    setUI(new javax.swing.plaf.basic.BasicComboBoxUI() {
      @Override protected JButton createArrowButton() {
        JButton button = new JButton() {
          @Override public int getWidth() {
            return 0;
          }
        };
        button.setBorder(BorderFactory.createEmptyBorder());
        button.setVisible(false);
        return button;
      }
#spanadd

#spanend
      @Override public void configureArrowButton() {}
    });
    for (MouseListener ml: getMouseListeners()) {
      removeMouseListener(ml);
    }
  }
#spanadd
};
#spanend
#spanadd

参考リンク

コメント