Stopping JPopupMenu stealing the focus

focus, java, jpopupmenu, swing

Solution

The technical answer is to set the popup's focusable property to false:

popup.setFocusable(false);

The implication is that the textField has to take over all keyboard and mouse-triggered actions that are normally handled by the list itself, 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");

As your context is very similar to a JComboBox, you might consider having a look into the sources of BasicComboBoxUI and BasicComboPopup.

Edit

Just for fun, the following is not answering the focus question :-) Instead, it demonstrates how to use a sortable/filterable JXList to show only the options in the dropdown which correspond to the typed text (here with a starts-with rule)

// 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's RowFilter on changes to the model's 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's 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);

Problem

I have a `JTextField` for which I'm hoping to suggest results to match the user's input. I'm displaying these suggestions in a `JList` contained within a `JPopupMenu`. However, when opening the popup menu programmatically via `show(Component invoker, int x, int y)`, the focus is getting taken from the `JTextField`. Strangely enough, if I call `setVisible(true)` instead, the focus is not stolen; but then the `JPopupMenu` is not attached to any panel, and when minimizing the application whilst the box is open, it stays painted on the window. I've also tried to reset the focus to the `JTextField` using `requestFocus()`, but then I have to restore the caret position using `SwingUtilities.invokeLater()`, and the invoke later side of things is giving the user a slight margin to mess around with the existing contents / overwrite it / or do other unpredictable things. The code I've got is effectively: ``` 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()); } }); ``` Can anyone suggest the best avenue to go down to show the `JPopupMenu` programmatically whilst preserving the focus on the `JTextField`?

Original source