JPopupMenuをマウスクリックなしで自動的に開閉する
Total: 376, Today: 2, Yesterday: 1
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
- 左:
MouseListenerJPopupMenuに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();
}
});
- 右:
AWTEventListenerJPopupMenuにPopupMenuListenerを追加し、popupMenuWillBecomeVisibleイベントでToolkit.getDefaultToolkit().addAWTEventListener(...)でアプリケーション全体のマウスイベントを取得するAWTEventListenerをToolkitに追加- この
AWTEventListenerはpopupMenuWillBecomeInvisibleイベントで削除する
- この
JToolTipJMenuから開くサブJPopupMenuと同じ手法?AWTEventListener#eventDispatched(...)イベントでJButtonやJPopupMenu領域外にマウスカーソルが移動したらJPopupMenuを閉じるJPopupMenuがJButtonの親JFrame外にはみ出してHeavyWeightWindowで表示されている状態でJPopupMenu領域から直接アプリケーション領域外にマウスカーソルを移動するとMouseEvent.MOUSE_MOVEDイベントが発生しないため、代わりにMouseEvent.MOUSE_EXITEDイベントでアプリケーション領域外にマウスカーソルが存在するかを調査している