Остановка JPopupMenu, крадущего фокус

У меня есть JTextField, для которого я надеюсь предложить результаты в соответствии с пользовательским вводом. Я показываю эти предложения в JList, содержащемся в JPopupMenu.

Однако при открытии всплывающего меню программно через show(Component invoker, int x, int y) фокус берется из JTextField.

Как ни странно, если я назову setVisible(true), фокус не будет украден; но затем JPopupMenu не прикреплен к какой-либо панели, и при минимизации приложения, пока поле открыто, оно остается окрашенным в окне.

Я также попытался reset сосредоточиться на JTextField с помощью requestFocus(), но затем мне нужно восстановить позицию каретки с помощью SwingUtilities.invokeLater(), а вызывать последующую сторону вещей дает пользователю небольшой запас, чтобы обходиться с существующим содержимым/перезаписывать его/или делать другие непредсказуемые вещи.

Код, который у меня есть, эффективен:

JTextField field = new JTextField();
JPopupMenu menu = new JPopupMenu();

field.addKeyListener(new KeyAdapter() {
    public void keyTyped(KeyEvent e) {
        JList list = getAListOfResults();

        menu.add(list);
        menu.show(field, 0, field.getHeight());
    }
});

Может ли кто-нибудь предложить лучший способ пойти вниз, чтобы показать JPopupMenu программно, сохраняя фокус на JTextField?

Ответ 1

Технический ответ: установить для свойства popup focusable значение false:

popup.setFocusable(false);

Импликация заключается в том, что textField должен взять на себя все действия с клавиатурой и мышью, которые обычно обрабатываются самим списком, sosmething like:

final JList list = new JList(Locale.getAvailableLocales());
final JPopupMenu popup = new JPopupMenu();
popup.add(new JScrollPane(list));
popup.setFocusable(false);
final JTextField field = new JTextField(20);
Action down = new AbstractAction("nextElement") {

    @Override
    public void actionPerformed(ActionEvent e) {
       int next = Math.min(list.getSelectedIndex() + 1,
               list.getModel().getSize() - 1);
       list.setSelectedIndex(next);
       list.ensureIndexIsVisible(next);
    }
};
field.getActionMap().put("nextElement", down);
field.getInputMap().put(
        KeyStroke.getKeyStroke("DOWN"), "nextElement");

Поскольку ваш контекст очень похож на JComboBox, вы можете подумать о том, чтобы изучить источники BasicComboBoxUI и BasicComboPopup.

Edit

Просто для удовольствия, следующее не отвечает на заданный вопрос:-) Вместо этого он демонстрирует, как использовать сортируемый/фильтруемый JXList, чтобы показывать только параметры в раскрывающемся списке, соответствующие типизированному тексту (здесь, с правилом)

// instantiate a sortable JXList
final JXList list = new JXList(Locale.getAvailableLocales(), true);
list.setSortOrder(SortOrder.ASCENDING);

final JPopupMenu popup = new JPopupMenu();
popup.add(new JScrollPane(list));
popup.setFocusable(false);
final JTextField field = new JTextField(20);

// instantiate a PatternModel to map text --> pattern 
final PatternModel model = new PatternModel();
model.setMatchRule(PatternModel.MATCH_RULE_STARTSWITH);
// listener which to update the list RowFilter on changes to the model pattern property  
PropertyChangeListener modelListener = new PropertyChangeListener() {

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if ("pattern".equals(evt.getPropertyName())) {
            updateFilter((Pattern) evt.getNewValue());
        }
    }

    private void updateFilter(Pattern newValue) {
        RowFilter<Object, Integer> filter = null;
        if (newValue != null) {
            filter = RowFilters.regexFilter(newValue);
        }
        list.setRowFilter(filter);
    }
};
model.addPropertyChangeListener(modelListener);

// DocumentListener to update the model rawtext property on changes to the field
DocumentListener documentListener = new DocumentListener() {

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

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

    private void updateAfterDocumentChange() {
        if (!popup.isVisible()) {
            popup.show(field, 0, field.getHeight());
        } 
        model.setRawText(field.getText());
    }

    @Override
    public void changedUpdate(DocumentEvent e) {
    }
};
field.getDocument().addDocumentListener(documentListener);

Ответ 2

Я смотрю прямо на меня. Добавьте следующий

field.requestFocus();

после

 menu.add(list);
 menu.show(field, 0, field.getHeight());

Конечно, вам нужно будет указать, когда нужно скрыть всплывающее окно и т.д. в зависимости от того, что происходит с JTextField.

т.е.;

 menu.show(field, field.getX(), field.getY()+field.getHeight());
 menu.setVisible(true);
 field.requestFocus();

Ответ 3

Вы можете взглянуть на JXSearchField, который является частью xswingx