You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将GlassPane的鼠标事件转发至JPopupMenu

解决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);
            }
        });
    }
}

关键修改说明

  1. 新增popupRoot检查:通过rootPane.getLayeredPane().getComponent(0)获取存储JPopupMenu的容器,优先在该容器中查找目标组件;
  2. 简化参数传递:不再依赖外部传入的contentPane,直接通过JRootPane获取,避免层级混乱;
  3. 坐标转换优化:分别将GlassPane的鼠标坐标转换到popupRoot和contentPane的坐标系,确保组件查找准确;
  4. 事件分发逻辑调整:dispatch方法不再需要传入contentPane,统一调用修改后的find方法。

内容的提问来源于stack exchange,提问作者user22999105

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 01:53:11