JPopupMenuをマウスクリックなしで自動的に開閉する
Total: 104
, Today: 1
, Yesterday: 4
Posted by aterai at
Last-modified:
Summary
JButton
領域内にマウスカーソルが入ったら自動的にJPopupMenu
を開き、JButton
やJPopupMenu
領域外にマウスカーソルが出たら自動的にそれを閉じるようイベントリスナーを設定します。
Screenshot

Advertisement
Source Code Examples
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 */
}
}
}
View in GitHub: Java, KotlinDescription
- 左:
MouseListener
JPopupMenu
にMouseListener
を追加し、mouseExited
イベントでJPopupMenu
を閉じるJPopupMenu
から内部のJMenuItem
にマウスカーソルが移動した際にもmouseExited
イベントが発生するので、mouseExited
イベントが発生かつエイム状態のJMenuItem
が存在しない場合はJPopupMenu
を閉じるよう設定- マウスカーソルを高速移動すると
mouseExited
イベントが発生せず、JPopupMenu
が閉じない場合があるJPopupMenu
の親Window
を半透明化、サイズ拡張してmouseExited
イベントが発生しやすくすれば回避が可能?
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
イベントでアプリケーション領域外にマウスカーソルが存在するかを調査している