'Editable JComboBox: Determining whether enter is pressed while ignoring it in drop down selection?

I have a panel with a couple of text fields and an editable JComboBox used to perform a search. I want all of these to act as if I press the search button underneath when I press the enter key. This works fine for a JTextField with an ActionListener. However, I only want the JComboBox to start the search when enter is pressed and the drop down list isn't shown. I.e. if the user presses enter to select an option in the drop down nothing should happen.

How would I go about achieving this behaviour?

I tried checking getActionCommand() and it either shows "comboBoxEdited" or "comboBoxChanged" when the event is fired. "comboBoxEdited" is fired when enter is pressed after selecting an option as well as when pressing enter in the editable field. "comboBoxChanged" is fired when moving between options, as well as just before "comboBoxEdited" when enter is pressed after editing the text.

I tried an ugly hack where I store the previous ActionCommand, but it isn't perfect as the user will have to press enter twice after entering text manually.

public void actionPerformed(ActionEvent e) {
    if (e.getActionCommand().equals("comboBoxEdited") &&
        !combohack.equals("comboBoxChanged")) {

        combohack="";
        //PERFORM SEARCH!
    }
    combohack=e.getActionCommand();
}

I furthermore tried to make my hack even uglier by adding a KeyListener as well to reset the string when actual letters where pressed, but that didn't help.

Any ideas?



Solution 1:[1]

Okay, this is a little heavy handed, but what this does is replaces the KeyEvent.VK_ENTER Action with our own Action

So basically, when the user presses the Enter key, our Action is notified, but the combo box is not (so the JComboBox won't trigger an ActionEvent for it - but will for selection changes)

import java.awt.Component;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.ComboBoxEditor;
import javax.swing.InputMap;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new GridBagLayout());
            final JComboBox<String> cb = new JComboBox<>(new String[]{"Apples", "Bananas", "Pears"});
            cb.setEditable(true);
            SimpleComboBoxEditor editor = new SimpleComboBoxEditor();
            InputMap im = editor.getInputMap();
            ActionMap am = editor.getActionMap();
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "enter");
            am.put("enter", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (!cb.isPopupVisible()) {
                        System.out.println("Editor did action");
                    }
                    cb.hidePopup();
                }
            });
            cb.setEditor(editor);
            cb.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println("Combobox did action");
                }
            });
            add(cb);
        }

        public class SimpleComboBoxEditor extends JTextField implements ComboBoxEditor {

            @Override
            public Component getEditorComponent() {
                return this;
            }

            @Override
            public void setItem(Object anObject) {
                if (anObject != null) {
                    setText(anObject.toString());
                } else {
                    setText(null);
                }
            }

            @Override
            public Object getItem() {
                return getText();
            }

        }

    }

}

Thanks for the effort! It doesn't really work as I want it to though. It still fires the event with "comboBoxEdited" when [Enter] is pressed to choose the highlighted item instead of just closing the drop down leaving the chosen text in the editor. It should only fire the event when the drop down is closed and the user presses [Enter]. So [Enter] to choose an item, [Enter] again to fire the action.

So, I finally got to run the code on a Windows machine and basically, in the Action for the key binding, I made a check for isPopupVisible, when it's not, it will print the "editor did action" event, otherwise it does nothing

Solution 2:[2]

There is actually a rather simple and elegant solution:

combo.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() {
    @Override
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode()==KeyEvent.VK_ENTER && !combo.isPopupVisible()) {
            System.out.println("SEARCH!");
        }
    }
});

Note that the KeyListener won't work if added to the JComboBox directly, but has to be added to its EditorComponent.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Kilgore