鼠标在内部组件悬停时退出

Posted

技术标签:

【中文标题】鼠标在内部组件悬停时退出【英文标题】:Mouse exited on inner component hover 【发布时间】:2015-04-28 00:27:19 【问题描述】:

我创建了一个 JFrame,其 JPanel 包含不同的组件,例如,当鼠标位于 JPanel 的边界内时,我希望 JPanel 具有可见边框和可见图像。我的问题是,只要鼠标悬停在 JPanel 内的“可交互”组件上,它就会在鼠标退出 JPanel 时注册。我希望它在 JPanel 的边界内绘制这些东西,并且当鼠标退出 JPanel 的边界时,边框和图像“消失”。有什么方法可以实现吗?

这是一个小演示:

public class Test 

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) 
        new TestFrame();
    

    static class TestFrame extends JFrame
        JPanel panel;
        JButton hoverButton;
        JButton appearingButton;
        public TestFrame() 
            super();
            this.setDefaultCloseOperation(EXIT_ON_CLOSE);
            panel = new JPanel();
            panel.setBackground(Color.red);
            hoverButton = new JButton("Hover me!");
            appearingButton = new JButton("I appeared!");
            appearingButton.setVisible(false);
            panel.add(hoverButton);
            panel.add(appearingButton);
            panel.addMouseListener(new java.awt.event.MouseAdapter() 
                public void mouseEntered(java.awt.event.MouseEvent evt) 
                    System.out.println("Entered!");
                    appearingButton.setVisible(true);
                
                public void mouseExited(java.awt.event.MouseEvent evt) 
                    System.out.println("Exited!");
                    appearingButton.setVisible(false);
                
            );
            add(panel);
            setSize(new Dimension(200, 200));
            setVisible(true);
        

    

当鼠标进入 JPanel(覆盖整个 JFrame)时,第二个按钮将出现。然而,将鼠标悬停在第一个按钮上会使第二个按钮消失。只要您在 JPanel 的范围内,我希望显示第二个按钮。

【问题讨论】:

【参考方案1】:

这实际上比听起来要困难得多。您需要能够监视容器子组件的所有鼠标事件。不幸的是,你要么得到一个全有或全无的解决方案。也就是说,你要么得到你现在遇到的问题,一旦另一个组件开始捕获它们,MouseListener 就会停止报告鼠标事件(这是鼠标侦听器 API 的工作方式),或者你可以看到系统的所有鼠标事件处理。

这使您需要提供某种过滤过程,以便您可以过滤掉那些您不感兴趣的事件,例如...

    Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() 
        @Override
        public void eventDispatched(AWTEvent event) 
            Object source = event.getSource();
            if (source instanceof JComponent) 
                JComponent comp = (JComponent) source;
                if (SwingUtilities.isDescendingFrom(parent, comp)) 
                    // The mouse is in the house...
                
            
        
    , AWTEvent.MOUSE_MOTION_EVENT_MASK);

(父容器是你的主容器)

这基本上将AWTEventListener 附加到主事件处理框架中,它将告诉您所有已处理的特定类型的事件。然后,在采取适当措施之前,您需要检查相关事件是否确实发生在您感兴趣的上下文中(您自己或其中一个孩子)...

Java 10 (~ 8+?)/2018

自从我写下原始答案以来,事件机制的工作方式似乎发生了一些变化(我也犯了一些小错误??)

为了让AWTListener 生成事件,所有“感兴趣”的组件都需要注册鼠标事件

我做了一个非常基本的测试,创建了一个普通的旧 JPanel(和一个按钮)并将它们添加到父容器并使用...

panel.addMouseListener(new MouseAdapter() );
panel.addMouseMotionListener(new MouseAdapter() );
add(panel);
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() 
    @Override
    public void eventDispatched(AWTEvent event) 
        Object source = event.getSource();
        if (source instanceof JComponent) 
            JComponent comp = (JComponent) source;
            System.out.println(comp);
            if (SwingUtilities.isDescendingFrom(comp, TestPane.this)) 
                // The mouse is in the house...
                System.out.println("Mouse in the house");
            
        
    
, AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK);

这会为按钮和面板生成事件

【讨论】:

能否请您说明您必须为parent 启用鼠标事件或添加鼠标侦听器。否则不会触发鼠标事件,另见MouseEvent documentation。 @Marcono1234 这种方法是直接监听事件调度队列 - 不需要“注册”任何其他兴趣,因为你直接在煤面 - 在调用这个监听器之后,事件将被分派给之前对事件有兴趣的组件——这就是重点 你确定吗?当我实现它时(并且没有针对parent 的常规MouseListeners),既没有触发 MOUSE_EXITED 也没有触发 MOUSE_ENTERED 事件。我假设其他鼠标事件类型无关紧要,因为它们与组件没有直接关系。 我认为你应该使用AWTEvent.MOUSE_EVENT_MASK 作为事件掩码。 MouseEvent.MOUSE_MOVED 似乎没有事件掩码,并导致侦听器收到各种事件的通知,包括 ComponentEvent、WindowEvent、... @Marcono1234 该示例使用MouseEvent.MOUSE_MOVED掩码【参考方案2】:

这实际上比你想象的要容易得多:

panel.addMouseListener(new java.awt.event.MouseAdapter() 
     public void mouseEntered(java.awt.event.MouseEvent evt) 
          System.out.println("Entered!");
          appearingButton.setVisible(true);
      
      public void mouseExited(java.awt.event.MouseEvent evt) 
           if( ! panel.contains( evt.getPoint() ) )
                System.out.println("Exited!");
                appearingButton.setVisible(false);
           
      
);

if 语句将过滤掉所有在鼠标仍在面板内时发生的 mouseExited 事件.

一个后果是您最终会收到多个 mouseEntered 事件,而它们之间没有 mouseExited,但是可以轻松避免由此引起的任何问题。

【讨论】:

如果父母和孩子之间没有差距,这将很遗憾。【参考方案3】:

一种解决方案是为 JPanel 中的每个子组件添加一个鼠标侦听器。

使用您的代码,这是一种方法:

package com.ggl.testing;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class HoverTest 

    /**
     * @param args
     *            the command line arguments
     */
    public static void main(String[] args) 
        SwingUtilities.invokeLater(new Runnable() 
            @Override
            public void run() 
                HoverTest hoverTest = new HoverTest();
                hoverTest.new TestFrame();
            
        );
    

    public class TestFrame extends JFrame 
        private static final long serialVersionUID = 6304847277329579360L;

        JPanel panel;

        JButton hoverButton;
        JButton appearingButton;

        public TestFrame() 
            super();
            this.setDefaultCloseOperation(EXIT_ON_CLOSE);
            panel = new JPanel();
            panel.setBackground(Color.red);

            hoverButton = new JButton("Hover me!");

            appearingButton = new JButton("I appeared!");
            appearingButton.setVisible(false);

            ButtonListener listener = new ButtonListener(appearingButton);

            panel.add(hoverButton);
            panel.add(appearingButton);
            panel.addMouseListener(listener);

            hoverButton.addMouseListener(listener);
            appearingButton.addMouseListener(listener);

            add(panel);

            setSize(new Dimension(200, 200));
            setVisible(true);
        

    

    private class ButtonListener extends MouseAdapter 

        private JButton appearingButton;

        public ButtonListener(JButton appearingButton) 
            this.appearingButton = appearingButton;
        

        @Override
        public void mouseEntered(java.awt.event.MouseEvent evt) 
            System.out.println("Entered!");
            appearingButton.setVisible(true);
        

        @Override
        public void mouseExited(java.awt.event.MouseEvent evt) 
            System.out.println("Exited!");
            appearingButton.setVisible(false);
        
    


【讨论】:

仅供参考-这可能会产生内存泄漏,如果子组件被删除,MouseListener 将保持对组件的强引用,防止它被垃圾收集

以上是关于鼠标在内部组件悬停时退出的主要内容,如果未能解决你的问题,请参考以下文章

在菜单组件中鼠标悬停时更改按钮的图标和文本颜色

Java Swing:在鼠标悬停时更改背景颜色

Java Swing:在鼠标悬停时更改背景颜色

Vue JS - 如何更改鼠标悬停时显示的组件的位置

鼠标退出按钮时未触发 MouseExited

Python+matplotlib数据可视化鼠标悬停自动标注功能实现