鼠标滚轮事件与悬停控件一起使用

Posted

技术标签:

【中文标题】鼠标滚轮事件与悬停控件一起使用【英文标题】:Mouse wheel event to work with hovered control 【发布时间】:2012-06-17 13:26:23 【问题描述】:

在我的 C# 3.5 Windows 窗体应用程序中,我有几个 SplitContainer。每个内部都有一个列表控件(码头填充)。当焦点位于其中一个控件上并且我移动鼠标滚轮时,现在焦点所在的列表会滚动。

我的任务是滚动当前被鼠标悬停的列表,而不是被选中的列表。是否可以在 Windows 窗体中使用?如果没有,是否可以使用 PInvoke?

【问题讨论】:

似乎他们在 Windows 10 中将“滚动鼠标光标所在的位置”作为标准行为。实际上,在大多数情况下这有点烦人。 【参考方案1】:

看起来您可以使用 IMessageFilter 和 PInvoke 来处理此问题。 VB 中的示例可以在Redirect Mouse Wheel Events to Unfocused Windows Forms Controls 找到。您应该能够轻松地将其转换为 C#。

兴趣点

此类对给定任务使用以下技术:

监听控件的 MouseEnter 和 MouseLeave 事件以确定鼠标指针何时悬停在控件上。 实现 IMessageFilter 以捕获应用程序中的 WM_MOUSEWHEEL 消息。 PInvoke Windows API 调用 SendMessage 将 WM_MOUSEWHEEL 消息重定向到控件的句柄。 IMessageFilter 对象实现为 MouseWheelRedirector 类的单例,并由共享成员 Attach、Detach 和 Active 访问。

使用a VB.NET to C# converter,你会得到这样的结果:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;

using System.Windows.Forms;
using System.Runtime.InteropServices;

public class MouseWheelRedirector : IMessageFilter

    private static MouseWheelRedirector instance = null;
    private static bool _active = false;
    public static bool Active
    
       get  return _active; 
       set
        
          if (_active != value) 
          
             _active = value;
             if (_active)
             
                if (instance == null)
                
                    instance = new MouseWheelRedirector();
                
                Application.AddMessageFilter(instance);
             
             else
             
                if (instance != null)
                
                   Application.RemoveMessageFilter(instance);
                
             
          
       
    

    public static void Attach(Control control)
    
       if (!_active)
          Active = true;
       control.MouseEnter += instance.ControlMouseEnter;
       control.MouseLeave += instance.ControlMouseLeaveOrDisposed;
       control.Disposed += instance.ControlMouseLeaveOrDisposed;
    

    public static void Detach(Control control)
    
       if (instance == null)
          return;
       control.MouseEnter -= instance.ControlMouseEnter;
       control.MouseLeave -= instance.ControlMouseLeaveOrDisposed;
       control.Disposed -= instance.ControlMouseLeaveOrDisposed;
       if (object.ReferenceEquals(instance.currentControl, control))
          instance.currentControl = null;
    

    private MouseWheelRedirector()
    
    


    private Control currentControl;
    private void ControlMouseEnter(object sender, System.EventArgs e)
    
       var control = (Control)sender;
       if (!control.Focused)
       
          currentControl = control;
       
       else
       
          currentControl = null;
       
    

    private void ControlMouseLeaveOrDisposed(object sender, System.EventArgs e)
    
       if (object.ReferenceEquals(currentControl, sender))
       
          currentControl = null;
       
    

    private const int WM_MOUSEWHEEL = 0x20a;
    public bool PreFilterMessage(ref System.Windows.Forms.Message m)
    
       if (currentControl != null && m.Msg == WM_MOUSEWHEEL)
       
          SendMessage(currentControl.Handle, m.Msg, m.WParam, m.LParam);
          return true;
       
       else
       
          return false;
       
    

    [DllImport("user32.dll", SetLastError = false)]
    private static extern IntPtr SendMessage(
       IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
 

【讨论】:

这似乎不适用于大多数使用右边缘或双指滚动等方法的常见触摸板。看起来他们的驱动程序做了一些黑魔法,将滚动消息直接发送到控件的滚动条,而不是像他们应该的那样将WM_MOUSEWHEEL 发送到控件。关于如何解决这个问题的任何想法? 嗯。看起来 NumericUpDown 不想听这个。 您需要将处理程序添加到可以传递触摸板或数字导航按钮的“PreFilterMessage”。目前,它只传递滚轮消息。 @blackholeearth0_gmail 嗯。正如我在 三年 前对问题本身所评论的那样,这在 Windows 10 上都无关紧要,因为滚动悬停控件是 win10 上的默认行为。您甚至都需要任何代码;无论如何它都可以工作。【参考方案2】:

我有类似的问题并找到了这个帖子......所以将我迟来的答案发布给可能找到这个帖子的其他人。就我而言,我只希望鼠标滚轮事件转到光标下的任何控件...就像右键单击一样(如果右键单击转到焦点控件而不是光标下的控件,那将是令人困惑的...我认为鼠标滚轮也是如此,只是我们已经习惯了)。

无论如何,答案非常简单。只需向您的应用程序添加一个 PreFilterMessage 并将鼠标滚轮事件重定向到鼠标下的控件:

    public bool PreFilterMessage(ref Message m)
    
        switch (m.Msg)
        
            case WM_MOUSEWHEEL:   // 0x020A
            case WM_MOUSEHWHEEL:  // 0x020E
                IntPtr hControlUnderMouse = WindowFromPoint(new Point((int)m.LParam));
                if (hControlUnderMouse == m.HWnd)
                    return false; // already headed for the right control
                else
                
                    // redirect the message to the control under the mouse
                    SendMessage(hControlUnderMouse, m.Msg, m.WParam, m.LParam);
                    return true;
                 
             default: 
                return false; 
            

【讨论】:

这里少了一点。您需要 DllImport WindowFromPoint() 和 SendMessage:[DllImport("user32.dll")] static extern IntPtr WindowFromPoint(Point p);[DllImport("user32.dll", CharSet = CharSet.Auto)] static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam); 此外,PreFilterMessage() 来自 IMessageFilter,并且该实现需要传递给 ApplicationAddMessageFilter()。完成后,我的应用程序中的所有面板都可以在鼠标下滚动。但是,双击不再突出显示文本。奇数。【参考方案3】:

这是 Brian Kennedy 的回答,并附有 Hank Schultz 评论:

首先你应该让一个类实现IMessageFilter

public class MessageFilter : IMessageFilter

    private const int WM_MOUSEWHEEL = 0x020A;
    private const int WM_MOUSEHWHEEL = 0x020E;

    [DllImport("user32.dll")]
    static extern IntPtr WindowFromPoint(Point p);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

    public bool PreFilterMessage(ref Message m)
    
        switch (m.Msg)
        
            case WM_MOUSEWHEEL:
            case WM_MOUSEHWHEEL:
                IntPtr hControlUnderMouse = WindowFromPoint(new Point((int)m.LParam));
                if (hControlUnderMouse == m.HWnd)
                
                    //Do nothing because it's already headed for the right control
                    return false;
                
                else
                
                    //Send the scroll message to the control under the mouse
                    uint u = Convert.ToUInt32(m.Msg);   
                    SendMessage(hControlUnderMouse, u, m.WParam, m.LParam);
                    return true;
                
            default:
                return false;
        
    

示例用法:

public partial class MyForm : Form

    MessageFilter mf = null;

    public MyForm
    
        Load += MyForm_Load;
        FormClosing += MyForm_FormClosing;
    

    private void MyForm_Load(object sender, EventArgs e)
    
        mf= new MessageFilter();
        Application.AddMessageFilter(mf);
    

    private void MyForm_FormClosing(object sender, FormClosingEventArgs e)
    
        Application.RemoveMessageFilter(mf);
    

【讨论】:

这很完美,在远程桌面、multimon 等方面都没有问题。感谢您结合这些答案。【参考方案4】:

使用Control.MouseEnter Event 将焦点设置到控件。例如。使用ActiveControl Property

【讨论】:

有时滚动控件而不聚焦它很方便,例如,如果它把焦点从文本字段移开。

以上是关于鼠标滚轮事件与悬停控件一起使用的主要内容,如果未能解决你的问题,请参考以下文章

水平鼠标滚轮事件? (WinAPI)

需要鼠标滚动滚动用户控件

VC 鼠标滚轮事件控制绘图的问题

如何处理 WPF 中的鼠标滚轮单击事件?

由于主线程正忙,Angular2 对“鼠标滚轮”输入事件的处理延迟了 x 毫秒

opencv与mfc显示图片操作,MFC的鼠标响应在opencv图片上失效,opencv滚轮事件没有响应问题描述解决。