Как включить ESC-Close для JPopupMenu, если базовое окно закрывается по ESC?

Представьте себе две общие ситуации: JDialog (или JFrame), который закрывается по VK_ESCAPE (устанавливается как привязка клавиш на корневой панели), и внутренний JPopupMenu, который также должен закрываться по ESC. Проблема в том, что нажатие escape всегда закрывает диалоговое окно - событие, если всплывающее окно видно. По-видимому, всплывающее окно даже не получает ключевое событие, поэтому оно не может быть использовано всплывающим окном. Есть ли способ заставить это работать правильно, чтобы при первом ESC-событии всплывающее окно закрывалось, а при втором закрывалось диалоговое окно? Кстати: он работает с JComboBox, который по умолчанию закрывается при нажатии escape.


person tigger    schedule 28.09.2010    source источник


Ответы (2)


Найти универсальное решение было непросто. Мы должны рассмотреть, когда:

  1. используется легкое всплывающее окно
  2. используется всплывающее окно тяжелого веса

Я определил, что в обоих случаях корневая панель фактически получает фокус при нажатии клавиши escape.

В первом случае я просто просматриваю корневую панель, чтобы увидеть, было ли добавлено JPopupMenu в графический интерфейс. Если это так, то мы можем просто закрыть всплывающее окно.

Во втором случае создается окно, содержащее JPopupMenu, поэтому я выполняю поиск, чтобы увидеть, отображается ли видимое пользовательское всплывающее окно. Если да, то я избавляюсь от окна.

Если ни один из двух вышеперечисленных случаев не соответствует действительности, вы можете просто закрыть диалоговое окно.

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 );
    }
}

Вам также потребуется загрузить класс Swing Utils.

person camickr    schedule 29.09.2010
comment
Большое тебе спасибо! Работает идеально! - person tigger; 29.09.2010

Я тоже столкнулся с проблемой. Решение, предложенное @camickr, мне не очень понравилось. Итак, я проверил, что происходит под капотом. При открытии всплывающего окна компонент, вызывающий всплывающее окно, теряет фокус. Фокус переходит к JRootPane JDialog, который затем делегируется всплывающему окну. Поэтому вместо регистрации ярлыка «ESC» в JRootPane я просто поместил его в ContentPane. Таким образом, диалоговое окно закрывается только по ESC, когда фокус находится на чем-либо, кроме JRootPane.

    final JComponent contentPane = (JComponent) dialog.getContentPane();
    contentPane.getInputMap( JComponent.WHEN_IN_FOCUSED_WINDOW ).put( KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0 ), "EXIT" );
    contentPane.getActionMap().put( "EXIT", new AbstractAction()
    {
      @Override
      public void actionPerformed( final ActionEvent e )
      {
        dialog.dispatchEvent( new WindowEvent( dialog, WindowEvent.WINDOW_CLOSING ) );
      }
    } );
person Marcel    schedule 08.06.2020
comment
@camickr, не могли бы вы высказать свое мнение по этому поводу? :D - person Marcel; 08.06.2020