Swing/ShowOrHideWithoutClickingPopupMenu の変更点
- 追加された行はこの色です。
- 削除された行はこの色です。
- Swing/ShowOrHideWithoutClickingPopupMenu へ行く。
- Swing/ShowOrHideWithoutClickingPopupMenu の差分を削除
--- 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