全局鼠标事件处理程序

Posted

技术标签:

【中文标题】全局鼠标事件处理程序【英文标题】:Global mouse event handler 【发布时间】:2012-07-21 08:53:06 【问题描述】:

我有以下代码,我从某个地方获得来捕获鼠标事件。我修改了它并制作了一个事件处理程序,以便我可以订阅它。鼠标事件被正确捕获。但它永远不会触发事件处理程序。有人能弄清楚代码有什么问题吗?

public static class MouseHook

    public static event EventHandler MouseAction = delegate  ;

    public static void Start() => _hookID = SetHook(_proc);
    public static void stop() => UnhookWindowsHookEx(_hookID);

    private static LowLevelMouseProc _proc = HookCallback;
    private static IntPtr _hookID = IntPtr.Zero;

    private static IntPtr SetHook(LowLevelMouseProc proc)
    
        using (Process curProcess = Process.GetCurrentProcess())
        using (ProcessModule curModule = curProcess.MainModule)
        
            return SetWindowsHookEx(WH_MOUSE_LL, proc,
              GetModuleHandle(curModule.ModuleName), 0);
        
    

    private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    
        if (nCode >= 0 && MouseMessages.WM_LBUTTONDOWN == (MouseMessages)wParam)
        
           MSLLHOOKSTRUCT hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));  
           MouseAction(null,new EventArgs());
        
        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    

    private const int WH_MOUSE_LL = 14;

    private enum MouseMessages
    
        WM_LBUTTONDOWN = 0x0201,
        WM_LBUTTONUP   = 0x0202,
        WM_MOUSEMOVE   = 0x0200,
        WM_MOUSEWHEEL  = 0x020A,
        WM_RBUTTONDOWN = 0x0204,
        WM_RBUTTONUP   = 0x0205
    

    [StructLayout(LayoutKind.Sequential)]
    private struct POINT
    
        public int x;
        public int y;
    

    [StructLayout(LayoutKind.Sequential)]
    private struct MSLLHOOKSTRUCT
    
        public POINT pt;
        public uint mouseData, flags, time;
        public IntPtr dwExtraInfo;
    

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr SetWindowsHookEx(int idHook,
      LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
      IntPtr wParam, IntPtr lParam);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetModuleHandle(string lpModuleName);

我是这样订阅的。

MouseHook.Start();  
MouseHook.MouseAction += new EventHandler(Event);

接收事件的函数。

private void Event(object sender, EventArgs e) => Console.WriteLine("Left mouse click!"); 

更新: 我将工作代码放在open source nuget package for user action hooks.

【问题讨论】:

这不能在控制台模式应用程序中工作,您的程序必须泵送消息循环。 Application.Run() 是必需的。 我实际上在我的 WPF 应用程序中使用了上面的代码。我从 App.cs 的 Onstartup 方法中调用 MouseHook 类。 对于所有感觉这会导致鼠标拖动的人,请在单独的提升进程中运行它并使用单独的线程来处理事件。 【参考方案1】:
return SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle(curModule.ModuleName), 0);

当您在 Windows 8 之前的 Windows 版本上的 .NET 4 上运行此代码时,该代码将失败。CLR 不再模拟托管程序集的非托管模块句柄。您无法在代码中检测到此故障,因为它缺少所需的错误检查。在 GetModuleHandle 和 SetWindowsHookEx 上。 pinvoke 时不要跳过错误检查,winapi 不会抛出异常。检查它们是否返回 IntPtr.Zero 并在返回时简单地抛出 Win32Exception。

修复很简单,SetWindowsHookEx() 需要一个有效的模块句柄,但在设置低级鼠标挂钩时实际上并没有使用它。因此,任何句柄都可以,您可以传递 user32.dll 的句柄,该句柄始终加载在 .NET 应用程序中。修复:

IntPtr hook = SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle("user32"), 0);
if (hook == IntPtr.Zero) 

    throw new System.ComponentModel.Win32Exception();

return hook;

【讨论】:

谢谢。但我用修复修改了代码。当我在 HookCallback 函数中使用 Console.WriteLine(hookStruct.pt.x + ", " + hookStruct.pt.y) 时,它曾经显示坐标。现在它不工作了。 呃,等等,这是从“它不工作”开始的。它实际上在工作吗?您至少可以记录您正在使用的 .NET 和 Windows 版本吗?您是否在“输出”窗口中看到第一次机会异常? 是的,我昨天工作时它正在给鼠标坐标。实际上,我检查了是否使用调试路径跟踪触发了事件。是的,它确实!但是 Console.WriteLine("something") 没有显示任何输出。我认为 console.writeline 工作不正常。奇怪 呃,等等,你现在是说事件实际上被触发了?听起来我的建议解决了你的问题。在您第一次检查 Debug.Print() 也不起作用之后,开始另一个关于 Console.WriteLine() 的问题。 是的!上面的建议很有用。但我发现 Visual Studio 设置不在 x86 调试模式下。我改变了它,现在它可以工作了!谢谢【参考方案2】:

我刚刚将您的代码复制到一个简单的 Windows 窗体中,并且它的工作方式与您描述的一样。你是如何使用它的?您从哪里开始和附加活动?

为了完整起见,这是我最终得到的代码 - 从一个简单的 C# 表单模板开始

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

namespace WindowsFormsApplication1

    public partial class Form1 : Form
    
        public Form1()
        
            InitializeComponent();

            MouseHook.Start();
            MouseHook.MouseAction += new EventHandler(Event);
        

        private void Event(object sender, EventArgs e)  Console.WriteLine("Left mouse click!"); 
    

    public static class MouseHook
    
        public static event EventHandler MouseAction = delegate  ;

        public static void Start()
        
            _hookID = SetHook(_proc);


        
        public static void stop()
        
            UnhookWindowsHookEx(_hookID);
        

        private static LowLevelMouseProc _proc = HookCallback;
        private static IntPtr _hookID = IntPtr.Zero;

        private static IntPtr SetHook(LowLevelMouseProc proc)
        
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            
                return SetWindowsHookEx(WH_MOUSE_LL, proc,
                  GetModuleHandle(curModule.ModuleName), 0);
            
        

        private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);

        private static IntPtr HookCallback(
          int nCode, IntPtr wParam, IntPtr lParam)
        
            if (nCode >= 0 && MouseMessages.WM_LBUTTONDOWN == (MouseMessages)wParam)
            
                MSLLHOOKSTRUCT hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
                MouseAction(null, new EventArgs());
            
            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        

        private const int WH_MOUSE_LL = 14;

        private enum MouseMessages
        
            WM_LBUTTONDOWN = 0x0201,
            WM_LBUTTONUP = 0x0202,
            WM_MOUSEMOVE = 0x0200,
            WM_MOUSEWHEEL = 0x020A,
            WM_RBUTTONDOWN = 0x0204,
            WM_RBUTTONUP = 0x0205
        

        [StructLayout(LayoutKind.Sequential)]
        private struct POINT
        
            public int x;
            public int y;
        

        [StructLayout(LayoutKind.Sequential)]
        private struct MSLLHOOKSTRUCT
        
            public POINT pt;
            public uint mouseData;
            public uint flags;
            public uint time;
            public IntPtr dwExtraInfo;
        

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook,
          LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
          IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);


    

【讨论】:

如果我想知道mouseClicks的坐标怎么办? 适用于除第 5 个按钮之外的所有鼠标按钮事件。实际上第 4 个和第 5 个按钮返回相同的代码。 我刚做了这个。公共静态 int x = 0; // 进入 Program.cs 和 MyConsoleApp.Program.x = hookStruct.pt.x; // 在 HookCallback 中找到 它可以工作,但对我来说,它会导致我的应用在启动后的前 10 秒内显着变慢(即鼠标抖动)。 @Masum 您可以通过使用 lParam[10] 条目来区分第 4 个和第 5 个鼠标键(对于事件类型 523 [down] 和 524 [up])。对于第 4 个/前进键,条目 = 2;对于第 5 个/返回键,条目 = 1。【参考方案3】:

对于任何未来的访客:

我已经实现了一个线程级鼠标钩子。

_process = Process.Start(@"c:\windows\notepad.exe");
//_process = Process.Start(@"c:\windows\syswow64\notepad.exe"); // works also with 32-bit

_mouseHook = new MouseHook(_process.Id);
_mouseHook.MessageReceived += MouseHook_MessageReceived;
_mouseHook.Install();

...

private void MouseHook_MessageReceived(object sender, MouseMessageEventArgs e)

    Debug.WriteLine($"Mouse Message Code: e.MessageCode; X: e.X; Y: e.Y; Delta: e.Delta");

【讨论】:

以上是关于全局鼠标事件处理程序的主要内容,如果未能解决你的问题,请参考以下文章

c#全局鼠标事件以及鼠标事件模拟

QT学习之事件处理

CocosCreator-如何处理鼠标事件

使用Robot类模拟鼠标键盘事件

拦截(可能取消)页面的鼠标/键盘事件处理程序

Mac 鼠标/键盘事件的监听和模拟