如何将 MouseListener 留在 ChildComponent 上但正确跟踪鼠标在父级上的进入和退出?

Posted

技术标签:

【中文标题】如何将 MouseListener 留在 ChildComponent 上但正确跟踪鼠标在父级上的进入和退出?【英文标题】:How to leave MouseListener on ChildComponent but correctly keep track of mouse enter and exit on parent? 【发布时间】:2020-04-28 13:11:00 【问题描述】:

我有一个带有 CardLayout 和两张卡片的 JPanel。我希望布局在每次鼠标进入或退出面板时翻转卡片。 除非其中一张卡是侦听鼠标事件的组件,否则这可以正常工作。考虑以下示例:

JPanel cardLayoutPanel = new JPanel(layout);
JButton button = new JButton("listening!");
JLabel label = new JLabel("not listening.");
cardLayoutPanel.add(button);
cardLayoutPanel.add(label);
layout.last(cardLayoutPanel);
cardLayoutPanel.addMouseListener(new MouseAdapter() 
    @Override
    public void mouseEntered(MouseEvent e) 
        System.out.println("entered!");
        layout.next(cardLayoutPanel);
    

    @Override
    public void mouseExited(MouseEvent e) 
        System.out.println("exited!");
        layout.next(cardLayoutPanel);
    
);

问题在于,如果 MouseEvent 被子组件捕获,则在与此主题相关的许多 SO 问题中,父组件不会将其处理为读取。 我尝试了不同的方法,例如重新调度事件,或者如果事件坐标仍在面板中,则忽略退出事件。

第一个解决方案根本不起作用,第二个解决方案从那时起鼠标输入事件不再发生。

我该如何解决这个问题?

我现在看到的唯一解决方案是从子组件中完全删除侦听器,并在父母鼠标侦听器中自行执行碰撞和事件处理,但这会是一团糟而不是预期的方式要做到这一点,我猜。

任何帮助或想法表示赞赏。

编辑:这是一个完整的简短可编译示例:

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class CardLayoutTest 

    public static void main(String[] args) 
        JFrame frame = new JFrame();

        JPanel content = new JPanel();
        content.setBorder(new EmptyBorder(new Insets(50, 50, 50, 50)));

        CardLayout layout = new CardLayout();
        JPanel cardLayoutPanel = new JPanel(layout);
        JButton button = new JButton("listening!");
        JLabel label = new JLabel("not listening.");
        cardLayoutPanel.add(button);
        cardLayoutPanel.add(label);
        layout.last(cardLayoutPanel);
        cardLayoutPanel.addMouseListener(new MouseAdapter() 
            @Override
            public void mouseEntered(MouseEvent e) 
                System.out.println("entered!");
                layout.next(cardLayoutPanel);
            


            @Override
            public void mouseExited(MouseEvent e) 
                System.out.println("exited!");
                layout.next(cardLayoutPanel);
            
        );

        content.add(cardLayoutPanel);
        frame.add(content);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    

【问题讨论】:

鼠标事件很痛苦。只需添加鼠标侦听器即可防止事件沿组件树传播!在有 PL&F 的地方,您无法确定发生了什么。重新调度为“玻璃窗格”应该可以工作。 我知道,这正是这里的问题。 PL&F 是什么意思?您所说的“重新调度为‘玻璃窗格’”是什么意思? PL&F Plugable Look and Feel - 使 GUI 看起来像特定平台的代码,无论是 Windows、Metal、Gtk 还是其他平台。应该有很多关于玻璃窗格的示例,它是容器顶部的一个组件,可以在鼠标事件经过时拦截它们。 好的,谢谢,我看看能不能用这种方法运行 【参考方案1】:

我稍微修改了您的示例并使其正常工作。关键(至少在这个例子中)是让按钮与cardLayoutPanel 共享相同的mouseListener。我不知道这是否是一个通用的解决方案,但它确实在这里工作。我还进行了以下更改:

    为 mouseListener 添加了一个私有内部类。 增加了 cardLayoutPanel 的大小。 为标签和按钮添加了彩色边框。

    import java.awt.CardLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Insets;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;

    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.border.EmptyBorder;
    import javax.swing.border.LineBorder;

    public class CardLayoutTest 

        JButton button = new JButton("listening!");
        JPanel cardLayoutPanel = new JPanel();
        CardLayout layout = new CardLayout();

        public static void main(String[] args) 
            new CardLayoutTest().start();
        

        public void start() 
            JFrame frame = new JFrame();

            JPanel content = new JPanel();
            content.setBorder(new EmptyBorder(
                    new Insets(50, 50, 50, 50)));

            cardLayoutPanel.setLayout(layout);
            cardLayoutPanel
                    .setPreferredSize(new Dimension(200, 200));
            button.addActionListener(
                    ae -> System.out.println("IT WORKS!"));
            button.removeMouseMotionListener(
                    button.getMouseMotionListeners()[0]);
            button.removeMouseListener(
                    button.getMouseListeners()[0]);

            MyMouseListener ml = new MyMouseListener();
            button.addMouseListener(ml);
            button.setBorder(new LineBorder(Color.red, 2));
            JLabel label = new JLabel("not listening.");
            label.setBorder(new LineBorder(Color.blue, 2));
            cardLayoutPanel.add(button);
            cardLayoutPanel.add(label);
            layout.last(cardLayoutPanel);
            cardLayoutPanel.addMouseListener(ml);

            content.add(cardLayoutPanel);
            frame.add(content);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setDefaultCloseOperation(
                    JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        

        private class MyMouseListener extends MouseAdapter 

            @Override
            public void mouseEntered(MouseEvent e) 
                    System.out.println("entered!");
                    layout.next(cardLayoutPanel);

            
            public void mouseClicked(MouseEvent e) 
                if (e.getSource() instanceof JButton) 
                    JButton b = (JButton)(e.getSource());
                    b.doClick();
                
            

            @Override
            public void mouseExited(MouseEvent e) 
                    System.out.println("exited!");
                    layout.next(cardLayoutPanel);
            
        
    

【讨论】:

非常感谢您的努力!我会看看是否可以让您的解决方案明天运行。 好的。一项观察。我在主程序中使用了button 实例来click 按钮。更好的方法是从MouseEvent 获取源并将其转换为JButton,然后使用它来执行doClick。这样他们就可以共享相同的mouseListener 实际上,我只是尝试了一下以确保语法,所以我会修改我的答案。【参考方案2】:

这不是一个好的解决方案,但我设法通过在 进入 父组件时调用翻转事件来实现我想要的。 这是因为我有足够的空间在具有此属性的面板之间。

然而,我并没有按照评论中的建议用玻璃板实现这种效果。也许我做错了,但是事件没有正确到达组件,甚至一些 instanceof 检查也突然失败了。

如果有人知道如何正确执行此操作,我会很高兴看到。

【讨论】:

以上是关于如何将 MouseListener 留在 ChildComponent 上但正确跟踪鼠标在父级上的进入和退出?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 mouselistener 访问一个组件而不是另一个组件

除非与mouselistener一起使用,否则JLabel不会显示

repaint 导致 mouseListener 注册没有发生的点击

当在子组件中添加其他 MouseListener 时,父组件的 MouseListener 在子组件中不起作用

MouseListener 不调用 mouseClicked 方法

使用 acm.graphics 时 mouseListener 的语法是啥