如何将GlassPane的鼠标事件转发至JPopupMenu
问题描述
我正在尝试通过GlassPane捕获所有鼠标事件,再根据逻辑分发到对应组件。目前方案对Button组件有效,但完全无法处理JPopupMenu——点击弹出的JPopupMenu时,触发的是下方JPanel的点击事件,始终无法获取JPopupMenu或其子菜单项的引用。
最小复现步骤
- 点击按钮,功能正常;
- 点击弹出的JPopupMenu,会触发JPanel的点击事件。
原代码
public class GlassPaneExample2 { public static void main(String[] args) { JFrame frame = new JFrame("Glass Pane Example"); frame.setSize(400, 300); JPanel mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); JButton button = new JButton("Button 2 txt"); button.addActionListener(e -> showPopupMenu(button)); mainPanel.add(button, BorderLayout.LINE_START); frame.add(mainPanel); GlassPane glassPane = new GlassPane(frame.getContentPane()); frame.setGlassPane(glassPane); glassPane.setVisible(true); frame.setVisible(true); } private static void showPopupMenu(Container container) { JPopupMenu jPopupMenu = new JPopupMenu(); JMenuItem menuItem1 = new JMenuItem("Menu Item 1"); JMenuItem menuItem2 = new JMenuItem("Menu Item 2"); menuItem1.addActionListener(e -> JOptionPane.showMessageDialog(container, "Menu Item 1 selected")); menuItem2.addActionListener(e -> JOptionPane.showMessageDialog(container, "Menu Item 2 selected")); jPopupMenu.add(menuItem1); jPopupMenu.add(menuItem2); jPopupMenu.show(container, 100, 10); } } class GlassPane extends JComponent { static Component findComponentUnderGlassPane(JComponent glassPane, Point glassPaneEventPoint, Container contentPane) { Point containerPoint = SwingUtilities.convertPoint(glassPane, glassPaneEventPoint, contentPane); if (containerPoint.y >= 0) { return SwingUtilities.getDeepestComponentAt(contentPane, containerPoint.x, containerPoint.y); } return null; } void dispatch(MouseEvent mouseEvent, Container container) { EventQueue.invokeLater(() -> { Component component = findComponentUnderGlassPane(GlassPane.this, mouseEvent.getPoint(), container); if (component != null && !component.equals(GlassPane.this)) { System.out.println("Dispatching to " + component.getClass().getSimpleName() + "#" + component.hashCode()); component.dispatchEvent(SwingUtilities.convertMouseEvent(GlassPane.this, mouseEvent, component)); } }); } public GlassPane(Container contentPane) { addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e){ dispatch(e, contentPane); } @Override public void mouseReleased(MouseEvent e){ dispatch(e, contentPane); } @Override public void mouseClicked(MouseEvent e) { dispatch(e, contentPane); } @Override public void mouseEntered(MouseEvent e) { dispatch(e, contentPane); } @Override public void mouseExited(MouseEvent e) { dispatch(e, contentPane); } @Override public void mouseMoved(MouseEvent e) { dispatch(e, contentPane); } @Override public void mouseDragged(MouseEvent e) { dispatch(e, contentPane); } }); } }
问题分析
JPopupMenu在Swing中有特殊的层级结构:它并不属于JFrame的contentPane容器,而是被添加到JRootPane的popupRoot容器中(一个独立的顶层弹出层)。原代码仅在contentPane中查找目标组件,因此无法定位到JPopupMenu及其子菜单项,导致事件被错误分发到下方的JPanel。
解决方案
修改findComponentUnderGlassPane方法,同时检查popupRoot和contentPane两个容器,优先处理弹出菜单的事件:
修改后的完整代码
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class GlassPaneExample2 { public static void main(String[] args) { JFrame frame = new JFrame("Glass Pane Example"); frame.setSize(400, 300); JPanel mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); JButton button = new JButton("Button 2 txt"); button.addActionListener(e -> showPopupMenu(button)); mainPanel.add(button, BorderLayout.LINE_START); frame.add(mainPanel); GlassPane glassPane = new GlassPane(frame.getContentPane()); frame.setGlassPane(glassPane); glassPane.setVisible(true); frame.setVisible(true); } private static void showPopupMenu(Container container) { JPopupMenu jPopupMenu = new JPopupMenu(); JMenuItem menuItem1 = new JMenuItem("Menu Item 1"); JMenuItem menuItem2 = new JMenuItem("Menu Item 2"); menuItem1.addActionListener(e -> JOptionPane.showMessageDialog(container, "Menu Item 1 selected")); menuItem2.addActionListener(e -> JOptionPane.showMessageDialog(container, "Menu Item 2 selected")); jPopupMenu.add(menuItem1); jPopupMenu.add(menuItem2); jPopupMenu.show(container, 100, 10); } } class GlassPane extends JComponent { static Component findComponentUnderGlassPane(JComponent glassPane, Point glassPaneEventPoint) { // 获取当前的JRootPane JRootPane rootPane = SwingUtilities.getRootPane(glassPane); if (rootPane == null) { return null; } // 1. 先检查popupRoot容器(存储JPopupMenu的地方) Component popupRoot = rootPane.getLayeredPane().getComponent(0); Point popupPoint = SwingUtilities.convertPoint(glassPane, glassPaneEventPoint, popupRoot); Component popupComponent = SwingUtilities.getDeepestComponentAt(popupRoot, popupPoint.x, popupPoint.y); if (popupComponent != null) { return popupComponent; } // 2. 再检查contentPane容器 Container contentPane = rootPane.getContentPane(); Point contentPoint = SwingUtilities.convertPoint(glassPane, glassPaneEventPoint, contentPane); if (contentPoint.y >= 0 && contentPoint.x >=0) { return SwingUtilities.getDeepestComponentAt(contentPane, contentPoint.x, contentPoint.y); } return null; } void dispatch(MouseEvent mouseEvent) { EventQueue.invokeLater(() -> { Component component = findComponentUnderGlassPane(GlassPane.this, mouseEvent.getPoint()); if (component != null && !component.equals(GlassPane.this)) { System.out.println("Dispatching to " + component.getClass().getSimpleName() + "#" + component.hashCode()); component.dispatchEvent(SwingUtilities.convertMouseEvent(GlassPane.this, mouseEvent, component)); } }); } public GlassPane(Container contentPane) { addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e){ dispatch(e); } @Override public void mouseReleased(MouseEvent e){ dispatch(e); } @Override public void mouseClicked(MouseEvent e) { dispatch(e); } @Override public void mouseEntered(MouseEvent e) { dispatch(e); } @Override public void mouseExited(MouseEvent e) { dispatch(e); } @Override public void mouseMoved(MouseEvent e) { dispatch(e); } @Override public void mouseDragged(MouseEvent e) { dispatch(e); } }); } }
关键修改说明
- 新增popupRoot检查:通过
rootPane.getLayeredPane().getComponent(0)获取存储JPopupMenu的容器,优先在该容器中查找目标组件; - 简化参数传递:不再依赖外部传入的contentPane,直接通过JRootPane获取,避免层级混乱;
- 坐标转换优化:分别将GlassPane的鼠标坐标转换到popupRoot和contentPane的坐标系,确保组件查找准确;
- 事件分发逻辑调整:dispatch方法不再需要传入contentPane,统一调用修改后的find方法。
内容的提问来源于stack exchange,提问作者user22999105
相关产品推荐
相关产品推荐

