2017-08-01 18 views
1

オートコンプリート機能を実装したいと思います。現在、私はJTextFieldを含むJPanelを持っており、ユーザーが入力を開始すると、いくつかのオプションを含むオートコンプリート(JPopupMenu)が表示されます。jtextfieldとjpopupmenuでオートコンプリートを実装する

問題は、テキストフィールドからフォーカスを引き、ユーザーが入力できなくなることです。フォーカスをテキストフィールドに戻すと、ユーザーはオプション間でのナビゲーション(上下のボタンを使用)がなくなりました。 また、メニューに焦点を当てるとKeyListenerを傍受することができません(理由はわかりません)、テキストフィールド側で入力を処理しようとすると、メニュー項目を選択しようとする際に問題が発生します。だから私は持っていたいと

  1. ユーザーがまだアクティブなメニューを持つ、テキストフィールドにテキストを変更したときに動的に変化オプションを持つポップアップメニュー
  2. ユーザーは、最大使用してオプションの間と下矢印キーを移動することができますオプションを使用するためのEnterキーとEscキー、それぞれポップアップを閉じることができます。

キーボードイベントをメニューで処理し、入力イベントをテキストフィールドに戻すことはできますか?

私の問題に近づく適切な方法は何ですか?

以下のコードがあります。前もって感謝します!

import javax.swing.*; 
import java.awt.*; 
import java.awt.event.KeyEvent; 
import java.awt.event.KeyListener; 


class TagVisual extends JPanel { 

    private JTextField editField; 

    public TagVisual() { 

     FlowLayout layout = new FlowLayout(); 
     layout.setHgap(0); 
     layout.setVgap(0); 
     setLayout(layout); 

     editField = new JTextField(); 
     editField.setBackground(Color.RED); 

     editField.setPreferredSize(new Dimension(200, 20)); 

     editField.addKeyListener(new KeyListener() { 
      @Override 
      public void keyTyped(KeyEvent e) { 
       JPopupMenu menu = new JPopupMenu(); 
       menu.add("Item 1"); 
       menu.add("Item 2"); 
       menu.add("Item 3"); 
       menu.addKeyListener(new KeyListener() { 
        @Override 
        public void keyTyped(KeyEvent e) { 
         JOptionPane.showMessageDialog(TagVisual.this, "keyTyped"); 
        } 

        @Override 
        public void keyPressed(KeyEvent e) { 
         JOptionPane.showMessageDialog(TagVisual.this, "keyPressed"); 
        } 

        @Override 
        public void keyReleased(KeyEvent e) { 
         JOptionPane.showMessageDialog(TagVisual.this, "keyReleased"); 
        } 
       }); 
       menu.show(editField, 0, getHeight()); 
      } 

      @Override 
      public void keyPressed(KeyEvent e) { 

      } 

      @Override 
      public void keyReleased(KeyEvent e) { 

      } 
     }); 

     add(editField, FlowLayout.LEFT); 
    } 

    public void place(JPanel panel) { 
     panel.add(this); 

     editField.grabFocus(); 
    } 
} 

public class MainWindow { 

    private JPanel mainPanel; 
    private JFrame frame; 

    public MainWindow(JFrame frame) { 

     mainPanel = new JPanel(new FlowLayout()); 
     TagVisual v = new TagVisual(); 
     v.place(mainPanel); 

     this.frame = frame; 
    } 

    public static void main(String[] args) { 
     JFrame frame = new JFrame("TextFieldPopupIssue"); 

     frame.setContentPane(new MainWindow(frame).mainPanel); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.pack(); 
     frame.setVisible(true); 
    } 
} 
+0

またはサードパーティのライブラリを使用してください。たとえばhttps://stackoverflow.com/q/14186955/1076463を参照してください。 – Robin

答えて

0

最も簡単な解決策は、フォーカス可能ではないメニュー作っている:エディタでキーを

menu.setFocusable(false); 

をして扱う

editField.addKeyListener(new KeyAdapter() { 
      @Override 
      public void keyPressed(KeyEvent e) { 
       if(KeyEvent.VK_DOWN == e.getKeyCode()) { 
        ... 
0

私は個人的には、ポップアップまたはカスタマイズJWindowの代わりを使用することをお勧めしJPopupMenuは最初はメニュー項目のみを表示するためのものです。それは他のもののために一般的に機能しますが、それを別の方法で使用するのはベストプラクティスではありません。

たとえば、いくつかのメニュー項目がオートコンプリートオプションとして表示されています。ほんのわずかの結果があれば問題ありません。しかし、もし彼らが10人いるとすれば?もし50?または500?何らかの理由で、スクロールペインにアイテムを入れたり、見栄えが悪いと思われるアイテムにアイテムを入れたり、最適なオプションではないいくつかの結果をカットしたりするなど、別の回避策を作成する必要があります。

私はのポップアップとしてJWindowを使用して小さな例を作成しました。それは非常にシンプルですが、あなたはそれから期待しても、あなたが言及しているものをいくつかの基本的な事柄ん:

import javax.swing.*; 
import javax.swing.border.EmptyBorder; 
import javax.swing.event.DocumentEvent; 
import javax.swing.event.DocumentListener; 
import java.awt.*; 
import java.awt.event.FocusEvent; 
import java.awt.event.FocusListener; 
import java.awt.event.KeyEvent; 
import java.awt.event.KeyListener; 
import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.List; 
import java.util.function.Function; 
import java.util.stream.Collectors; 

/** 
* @author Mikle Garin 
* @see https://stackoverflow.com/questions/45439231/implementing-autocomplete-with-jtextfield-and-jpopupmenu 
*/ 

public final class AutocompleteField extends JTextField implements FocusListener, DocumentListener, KeyListener 
{ 
    /** 
    * {@link Function} for text lookup. 
    * It simply returns {@link List} of {@link String} for the text we are looking results for. 
    */ 
    private final Function<String, List<String>> lookup; 

    /** 
    * {@link List} of lookup results. 
    * It is cached to optimize performance for more complex lookups. 
    */ 
    private final List<String> results; 

    /** 
    * {@link JWindow} used to display offered options. 
    */ 
    private final JWindow popup; 

    /** 
    * Lookup results {@link JList}. 
    */ 
    private final JList list; 

    /** 
    * {@link #list} model. 
    */ 
    private final ListModel model; 

    /** 
    * Constructs {@link AutocompleteField}. 
    * 
    * @param lookup {@link Function} for text lookup 
    */ 
    public AutocompleteField (final Function<String, List<String>> lookup) 
    { 
     super(); 
     this.lookup = lookup; 
     this.results = new ArrayList<>(); 

     final Window parent = SwingUtilities.getWindowAncestor (this); 
     popup = new JWindow (parent); 
     popup.setType (Window.Type.POPUP); 
     popup.setFocusableWindowState (false); 
     popup.setAlwaysOnTop (true); 

     model = new ListModel(); 
     list = new JList (model); 

     popup.add (new JScrollPane (list) 
     { 
      @Override 
      public Dimension getPreferredSize() 
      { 
       final Dimension ps = super.getPreferredSize(); 
       ps.width = AutocompleteField.this.getWidth(); 
       return ps; 
      } 
     }); 

     addFocusListener (this); 
     getDocument().addDocumentListener (this); 
     addKeyListener (this); 
    } 

    /** 
    * Displays autocomplete popup at the correct location. 
    */ 
    private void showAutocompletePopup() 
    { 
     final Point los = AutocompleteField.this.getLocationOnScreen(); 
     popup.setLocation (los.x, los.y + getHeight()); 
     popup.setVisible (true); 
    } 

    /** 
    * Closes autocomplete popup. 
    */ 
    private void hideAutocompletePopup() 
    { 
     popup.setVisible (false); 
    } 

    @Override 
    public void focusGained (final FocusEvent e) 
    { 
     SwingUtilities.invokeLater (() -> { 
      if (results.size() > 0) 
      { 
       showAutocompletePopup(); 
      } 
     }); 
    } 

    private void documentChanged() 
    { 
     SwingUtilities.invokeLater (() -> { 
      // Updating results list 
      results.clear(); 
      results.addAll (lookup.apply (getText())); 

      // Updating list view 
      model.updateView(); 
      list.setVisibleRowCount (Math.min (results.size(), 10)); 

      // Selecting first result 
      if (results.size() > 0) 
      { 
       list.setSelectedIndex (0); 
      } 

      // Ensure autocomplete popup has correct size 
      popup.pack(); 

      // Display or hide popup depending on the results 
      if (results.size() > 0) 
      { 
       showAutocompletePopup(); 
      } 
      else 
      { 
       hideAutocompletePopup(); 
      } 
     }); 
    } 

    @Override 
    public void focusLost (final FocusEvent e) 
    { 
     SwingUtilities.invokeLater (this::hideAutocompletePopup); 
    } 

    @Override 
    public void keyPressed (final KeyEvent e) 
    { 
     if (e.getKeyCode() == KeyEvent.VK_UP) 
     { 
      final int index = list.getSelectedIndex(); 
      if (index != -1 && index > 0) 
      { 
       list.setSelectedIndex (index - 1); 
      } 
     } 
     else if (e.getKeyCode() == KeyEvent.VK_DOWN) 
     { 
      final int index = list.getSelectedIndex(); 
      if (index != -1 && list.getModel().getSize() > index + 1) 
      { 
       list.setSelectedIndex (index + 1); 
      } 
     } 
     else if (e.getKeyCode() == KeyEvent.VK_ENTER) 
     { 
      final String text = (String) list.getSelectedValue(); 
      setText (text); 
      setCaretPosition (text.length()); 
     } 
     else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) 
     { 
      hideAutocompletePopup(); 
     } 
    } 

    @Override 
    public void insertUpdate (final DocumentEvent e) 
    { 
     documentChanged(); 
    } 

    @Override 
    public void removeUpdate (final DocumentEvent e) 
    { 
     documentChanged(); 
    } 

    @Override 
    public void changedUpdate (final DocumentEvent e) 
    { 
     documentChanged(); 
    } 

    @Override 
    public void keyTyped (final KeyEvent e) 
    { 
     // Do nothing 
    } 

    @Override 
    public void keyReleased (final KeyEvent e) 
    { 
     // Do nothing 
    } 

    /** 
    * Custom list model providing data and bridging view update call. 
    */ 
    private class ListModel extends AbstractListModel 
    { 
     @Override 
     public int getSize() 
     { 
      return results.size(); 
     } 

     @Override 
     public Object getElementAt (final int index) 
     { 
      return results.get (index); 
     } 

     /** 
     * Properly updates list view. 
     */ 
     public void updateView() 
     { 
      super.fireContentsChanged (AutocompleteField.this, 0, getSize()); 
     } 
    } 

    /** 
    * Sample {@link AutocompleteField} usage. 
    * 
    * @param args run arguments 
    */ 
    public static void main (final String[] args) 
    { 
     final JFrame frame = new JFrame ("Sample autocomplete field"); 

     // Sample data list 
     final List<String> values = Arrays.asList ("Frame", "Dialog", "Label", "Tree", "Table", "List", "Field"); 

     // Simple lookup based on our data list 
     final Function<String, List<String>> lookup = text -> values.stream() 
       .filter (v -> !text.isEmpty() && v.toLowerCase().contains (text.toLowerCase()) && !v.equals (text)) 
       .collect (Collectors.toList()); 

     // Autocomplete field itself 
     final AutocompleteField field = new AutocompleteField (lookup); 
     field.setColumns (15); 

     final JPanel border = new JPanel (new BorderLayout()); 
     border.setBorder (new EmptyBorder (50, 50, 50, 50)); 
     border.add (field); 
     frame.add (border); 

     frame.setDefaultCloseOperation (WindowConstants.EXIT_ON_CLOSE); 
     frame.pack(); 
     frame.setLocationRelativeTo (null); 
     frame.setVisible (true); 
    } 
} 

は、したがって、この例ではJWindow自体をポップアップが(フォーカスされていない)アクティブではなく、そのようフォーカスを得ることができません強制的にそのように構成されています。それで、私たちはJTextFieldの中に焦点を置いて、タイピングを続けます。

この例では、オートコンプリートの結果をナビゲートするために、フィールド内の上矢印または下矢印のようなキーイベントも取得します。また、ENTERとESCAPEを使用して結果の選択を受け入れ/キャンセルします。

また、このコードはわずかオートコンプリートのポップアップウィンドウのソースとしてスイングPopupFactoryを使用するように書き換えることができ、単にJWindowを拡張し、いくつかの設定を追加し、それはまだPopupFactoryで使用HeavyWeightWindow以来のエッセンスでも同じだろう。

関連する問題