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

11
在我的C# 3.5 Windows Forms应用程序中,我有几个SplitContainer。每个容器内都有一个列表控件(填充模式)。当鼠标焦点在其中一个控件上并滚动鼠标滚轮时,当前获得焦点的列表会被滚动。
我的任务是滚动当前由鼠标悬停所在的列表,而不是选择的列表。在Windows Forms中是否可能?如果不行,是否可以通过PInvoke实现?

在Windows 10中,他们似乎将“滚动鼠标光标所在位置”的行为作为标准行为。实际上,在大多数情况下这有点烦人。 - Nyerguds
4个回答

8
看起来您可以使用IMessageFilter和PInvoke来处理这个问题。在Redirect Mouse Wheel Events to Unfocused Windows Forms Controls中可以找到VB的示例。您应该可以轻松将其转换为C#。
兴趣点:
此类针对给定任务使用以下技术:
- 监听控件的MouseEnter和MouseLeave事件,以确定鼠标指针是否位于控件上方。 - 实现IMessageFilter以捕获应用程序中的WM_MOUSEWHEEL消息。 - PInvoke Windows API调用SendMessage将WM_MOUSEWHEEL消息重定向到控件的句柄。 - IMessageFilter对象作为MouseWheelRedirector类的单例实现,并通过共享成员Attach、Detach和Active访问。
使用VB.NET转C#转换工具,您最终会得到以下内容:
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给控件。有什么办法可以解决这个问题吗? - Nyerguds
嗯,看起来NumericUpDown不想听这个。 - Nyerguds
1
您需要添加处理程序到“PreFilterMessage”,以便可以传递触摸板或数字导航按钮。目前,它只能传递滚轮消息。 - HackSlash
@blackholeearth0_gmail 嗯。我在三年前就在问题本身上发表了评论,因为在Windows 10上,滚动悬停控件是默认行为,所以这一切都与此无关。你甚至不需要任何代码;它会自动工作。 - Nyerguds

6
我有一个类似的问题,并在这个帖子中找到了答案...所以我为其他可能会发现这个帖子的人发布我的迟来的答案。在我的情况下,我只想让鼠标滚轮事件传递给光标下的任何控件...就像右键点击一样(如果右键点击焦点控件而不是光标下的控件,那将令人困惑...我认为鼠标滚轮也是如此,只不过我们已经习惯了)。
无论如何,答案非常简单。只需向应用程序添加一个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()。完成后,我的应用程序中的所有面板都可以在鼠标下滚动。然而,双击不再突出显示文本。奇怪。 - Hank Schultz

5

以下是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);
    }
}

1
这个完美地运行了,无论是在远程桌面、多屏幕等方面都没有问题。感谢您将这些答案结合在一起。 - Hoss

3

5
有时候在不让控件获取焦点的情况下滚动它是很方便的,例如当焦点在文本框中时。 - Nyerguds

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接