javajdialogkeyeventjpopupmenu

How to enable ESC-Close for an JPopupMenu, if the underlying window closes on ESC?


Imagine two common situations combined: A JDialog(or JFrame) which closes on VK_ESCAPE (set as key binding on the root pane) and an inner JPopupMenu which is supposed to close on ESC as well. The problem is: pressing escape always closes the dialog - event if the popup is visible. Apparently the popup doesn't even receive the key event so it can't be consumed by the popup. Is there any way to get this working correctly, so that on the first ESC-event the popup is closed and on the second the dialog closes? By the way: It does work with a JComboBox, which by default closes when escape is pressed.


Solution

  • Finding a generic solution was a bit of a challenge. We need to consider when:

    1. a light weight popup is used
    2. a heavy weight popup is used

    I determined that in both cases the root pane actually has focus when the escape key is pressed.

    In the first case I just search the root pane to see if a JPopupMenu has been added to the GUI. If so, then we can just close the popup.

    In the second case a Window is created to contain the JPopupMenu so I do a search to see if a visible custom popup Window is displayed. If so, then I dispose of the window.

    If neither of the above two cases is true then you can just close the dialog.

    import java.awt.*;
    import java.awt.event.*;
    import java.util.List;
    import javax.swing.*;
    import javax.swing.event.*;
    
    public class DialogEscape extends JDialog
    {
        private JPopupMenu popup;
    
        public DialogEscape()
        {
            popup = new JPopupMenu();
            popup.add( new JMenuItem("SubMenuA") );
            popup.add( new JMenuItem("SubMenuB") );
            popup.add( new JMenuItem("SubMenuC") );
            popup.add( new JMenuItem("SubMenuD") );
    
            String[] items = { "Select Item", "Color", "Shape", "Fruit" };
            JComboBox comboBox = new JComboBox( items );
            add(comboBox, BorderLayout.NORTH);
    
            JTextField textField = new JTextField("Right Click For Popup");
            textField.setComponentPopupMenu(popup);
            add(textField);
    
            KeyStroke escapeKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
            Action escapeAction = new AbstractAction()
            {
                public void actionPerformed(ActionEvent e)
                {
                    boolean openPopup = false;
                    Component c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
    
                    //  Check if light weight popup is being used
    
                    List<JPopupMenu> popups = SwingUtils.getDescendantsOfType(JPopupMenu.class, (Container)c, true);
    
                    for (JPopupMenu p: popups)
                    {
                        p.setVisible( false );
                        openPopup = true;
                    }
    
                    //  Check if a heavy weight popup is being used
    
                    Window window = SwingUtilities.windowForComponent(c);
                    Window[] windows = window.getOwnedWindows();
    
                    for (Window w: windows)
                    {
                        if (w.isVisible()
                        &&  w.getClass().getName().endsWith("HeavyWeightWindow"))
                        {
                            openPopup = true;
                            w.dispose();
                        }
                    }
    
                    //  No popups so close the Window
    
                    if (! openPopup)
    //                  SwingUtilities.windowForComponent(c).setVisible(false);
                        SwingUtilities.windowForComponent(c).dispose();
                }
            };
    
            getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escapeKeyStroke, "ESCAPE");
            getRootPane().getActionMap().put("ESCAPE", escapeAction);
        }
    
        public static void main(String[] args)
        {
            String laf = null;
            laf = "javax.swing.plaf.metal.MetalLookAndFeel";
    //      laf = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
    //      laf = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
    
            try { UIManager.setLookAndFeel(laf); }
            catch (Exception e2) { System.out.println(e2); }
    
            JDialog dialog = new DialogEscape();
            dialog.setDefaultCloseOperation( HIDE_ON_CLOSE );
            dialog.setSize(200, 200);
            dialog.setLocationRelativeTo(null);
            dialog.setVisible( true );
        }
    }
    

    You will also need to download the Swing Utils class.