---
category: swing
folder: ShowOrHideWithoutClickingPopupMenu
title: JPopupMenuをマウスクリックなしで自動的に開閉する
tags: [JPopupMenu, JButton, MouseListener, AWTEventListener]
author: aterai
pubdate: 2025-06-16T00:17:25+09:00
description: JButton領域内にマウスカーソルが入ったら自動的にJPopupMenuを開き、JButtonやJPopupMenu領域外にマウスカーソルが出たら自動的にそれを閉じるようイベントリスナーを設定します。
image: https://drive.google.com/uc?id=1odXX2KrOXAp4sWUzbhLpi1bp4SxjIaaY
hreflang:
    href: https://java-swing-tips.blogspot.com/2025/06/automatically-open-and-close-jpopupmenu.html
    lang: en
---
* Summary [#summary]
`JButton`領域内にマウスカーソルが入ったら自動的に`JPopupMenu`を開き、`JButton`や`JPopupMenu`領域外にマウスカーソルが出たら自動的にそれを閉じるようイベントリスナーを設定します。

#download(https://drive.google.com/uc?id=1odXX2KrOXAp4sWUzbhLpi1bp4SxjIaaY)

* Source Code Examples [#sourcecode]
#code(link){{
class AutoClosePopupMenu extends JPopupMenu {
  private transient PopupMenuListener listener;

  @Override public void updateUI() {
    removePopupMenuListener(listener);
    super.updateUI();
    listener = new AwtPopupMenuListener();
    addPopupMenuListener(listener);
  }

  private void checkAutoClose(MouseEvent e) {
    Component c = e.getComponent();
    Rectangle r = getBounds();
    r.grow(0, 5);
    Point pt = SwingUtilities.convertPoint(c, e.getPoint(), this);
    if (!r.contains(pt) && !(c instanceof JButton)) {
      setVisible(false);
    }
  }

  private final class AwtPopupMenuListener implements PopupMenuListener {
    private final AWTEventListener handler = e -> {
      if (e instanceof MouseEvent) {
        int id = e.getID();
        if (id == MouseEvent.MOUSE_MOVED || id == MouseEvent.MOUSE_EXITED) {
          checkAutoClose((MouseEvent) e);
        }
      }
    };

    @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
      long mask = AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK;
      Toolkit.getDefaultToolkit().addAWTEventListener(handler, mask);
    }

    @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
      Toolkit.getDefaultToolkit().removeAWTEventListener(handler);
    }

    @Override public void popupMenuCanceled(PopupMenuEvent e) {
      /* not needed */
    }
  }
}
}}

* Description [#description]
- 左: `MouseListener`
-- `JPopupMenu`に`MouseListener`を追加し、`mouseExited`イベントで`JPopupMenu`を閉じる
-- `JPopupMenu`から内部の`JMenuItem`にマウスカーソルが移動した際にも`mouseExited`イベントが発生するので、`mouseExited`イベントが発生かつエイム状態の`JMenuItem`が存在しない場合は`JPopupMenu`を閉じるよう設定
-- マウスカーソルを高速移動すると`mouseExited`イベントが発生せず、`JPopupMenu`が閉じない場合がある
--- `JPopupMenu`の親`Window`を半透明化、サイズ拡張して`mouseExited`イベントが発生しやすくすれば回避が可能?

#code{{
JPopupMenu popup = new JPopupMenu();
initPopupMenu(popup);
popup.addMouseListener(new MouseAdapter() {
  @Override public void mouseExited(MouseEvent e) {
    EventQueue.invokeLater(() -> {
    popup.addMouseListener(new MouseAdapter() {
      @Override public void mouseExited(MouseEvent e) {
        EventQueue.invokeLater(() -> {
          boolean isArmed = Stream.of(popup.getSubElements())
              .filter(AbstractButton.class::isInstance)
              .map(AbstractButton.class::cast)
              .map(AbstractButton::getModel)
              .anyMatch(ButtonModel::isArmed);
          if (!isArmed) {
            popup.setVisible(false);
          }
        });
      }
    });
  }
});
JButton button = new JButton(UIManager.getIcon("FileChooser.listViewIcon"));
button.setFocusPainted(false);
button.addActionListener(e -> {
  popup.show(button, 0, button.getHeight());
  popup.requestFocusInWindow();
});
button.addMouseListener(new MouseAdapter() {
  @Override public void mouseEntered(MouseEvent e) {
    ((AbstractButton) e.getComponent()).doClick();
  }
});
}}

- 右: `AWTEventListener`
-- `JPopupMenu`に`PopupMenuListener`を追加し、`popupMenuWillBecomeVisible`イベントで`Toolkit.getDefaultToolkit().addAWTEventListener(...)`でアプリケーション全体のマウスイベントを取得する`AWTEventListener`を`Toolkit`に追加
--- この`AWTEventListener`は`popupMenuWillBecomeInvisible`イベントで削除する
-- %%`JToolTip`%% `JMenu`から開くサブ`JPopupMenu`と同じ手法?
-- `AWTEventListener#eventDispatched(...)`イベントで`JButton`や`JPopupMenu`領域外にマウスカーソルが移動したら`JPopupMenu`を閉じる
-- `JPopupMenu`が`JButton`の親`JFrame`外にはみ出して`HeavyWeightWindow`で表示されている状態で`JPopupMenu`領域から直接アプリケーション領域外にマウスカーソルを移動すると`MouseEvent.MOUSE_MOVED`イベントが発生しないため、代わりに`MouseEvent.MOUSE_EXITED`イベントでアプリケーション領域外にマウスカーソルが存在するかを調査している

* Reference [#reference]
- [[AWTEventを取得して入力イベントを監視>Swing/DispatchEvent]]
- [[JMenuの領域内にマウスカーソルでポップアップメニューを表示する>Swing/PopupWithoutClickOnMenu]]

* Comment [#comment]
#comment
#comment