c# 全局监听鼠标(鼠标钩子)

封装类

internal class MouseHook
{
    #region 常量
    public const int WM_MOUSEMOVE = 0x200; // 鼠标移动
    public const int WM_LBUTTONDOWN = 0x201;// 鼠标左键按下
    public const int WM_RBUTTONDOWN = 0x204;// 鼠标右键按下
    public const int WM_MBUTTONDOWN = 0x207;// 鼠标中键按下
    public const int WM_LBUTTONUP = 0x202;// 鼠标左键抬起
    public const int WM_RBUTTONUP = 0x205;// 鼠标右键抬起
    public const int WM_MBUTTONUP = 0x208;// 鼠标中键抬起
    public const int WM_LBUTTONDBLCLK = 0x203;// 鼠标左键双击
    public const int WM_RBUTTONDBLCLK = 0x206;// 鼠标右键双击
    public const int WM_MBUTTONDBLCLK = 0x209;// 鼠标中键双击
    public const int WH_MOUSE_LL = 14; //可以截获整个系统所有模块的鼠标事件。

    public const int WM_WHEELDOWN = 519; // 按下鼠标滑轮
    public const int WM_WHEELUP = 520; // 松开鼠标滑轮
    public const int WM_WHEELSCROLL = 522; // 滚动滑轮

    public const int WM_SCROLLBACK = -0x780000;// 向后滚动滑轮
    public const int WM_SCROLLFORWARD = 0x780000;// 向前滚动滑轮
    #endregion

    #region 成员变量、回调函数、事件
    /// <summary>
    /// 钩子回调函数
    /// </summary>
    /// <param name="nCode">如果代码小于零,则挂钩过程必须将消息传递给CallNextHookEx函数,而无需进一步处理,并且应返回CallNextHookEx返回的值。此参数可以是下列值之一。(来自官网手册)</param>
    /// <param name="wParam">记录了按下的按钮</param>
    /// <param name="lParam"></param>
    /// <returns></returns>
    public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
    /// <summary>
    /// 全局的鼠标事件
    /// </summary>
    /// <param name="wParam"> 代表发生的鼠标的事件 </param>
    /// <param name="mouseMsg">钩子的结构体,存储着鼠标的位置及其他信息</param>
    public delegate void MyMouseEventHandler(Int32 wParam, MouseHookStruct mouseMsg);
    private event MyMouseEventHandler OnMouseActivity;
    // 声明鼠标钩子事件类型
    private HookProc _mouseHookProcedure;
    private static int _hMouseHook = 0; // 鼠标钩子句柄
    // 锁
    private readonly object lockObject = new object();
    // 当前状态,是否已经启动
    private bool isStart = false;
    #endregion

    #region Win32的API
    /// <summary>
    /// 钩子结构体
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public class MouseHookStruct
    {
        public POINT pt; // 鼠标位置
        public int hWnd;
        public int wHitTestCode;
        public int dwExtraInfo;
    }

    //声明一个Point的封送类型  
    [StructLayout(LayoutKind.Sequential)]
    public class POINT
    {
        public int x;
        public int y;
    }



    // 装置钩子的函数
    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

    // 卸下钩子的函数
    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern bool UnhookWindowsHookEx(int idHook);

    // 下一个钩挂的函数
    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);
    #endregion


    #region 构造(单例模式)与析构函数
    private static volatile MouseHook MyMouseHook;
    private readonly static object createLock = new object();
    private MouseHook() { }

    public static MouseHook GetMouseHook()
    {
        if (MyMouseHook == null)
        {
            lock (createLock)
            {
                if (MyMouseHook == null)
                {
                    MyMouseHook = new MouseHook();
                }
            }
        }
        return MyMouseHook;
    }

    /// <summary>
    /// 析构函数
    /// </summary>
    ~MouseHook()
    {
        Stop();
    }
    #endregion


    /// <summary>
    /// 启动全局钩子
    /// </summary>
    public void Start()
    {
        if (isStart)
        {
            return;
        }
        lock (lockObject)
        {
            if (isStart)
            {
                return;
            }
            if (OnMouseActivity == null)
            {
                throw new Exception("Please set handler first!Then run Start");
            }
            // 安装鼠标钩子
            if (_hMouseHook == 0)
            {
                // 生成一个HookProc的实例.
                _mouseHookProcedure = new HookProc(MouseHookProc);
                _hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, _mouseHookProcedure, Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]), 0);
                //假设装置失败停止钩子
                if (_hMouseHook == 0)
                {
                    Stop();
                    throw new Exception("SetWindowsHookEx failed.");
                }
            }
            isStart = true;
        }
    }

    /// <summary>
    /// 停止全局钩子
    /// </summary>
    public void Stop()
    {
        if (!isStart)
        {
            return;
        }
        lock (lockObject)
        {
            if (!isStart)
            {
                return;
            }
            bool retMouse = true;
            if (_hMouseHook != 0)
            {
                retMouse = UnhookWindowsHookEx(_hMouseHook);
                _hMouseHook = 0;
            }
            // 假设卸下钩子失败
            if (!(retMouse))
                throw new Exception("UnhookWindowsHookEx failed.");
            // 删除所有事件
            OnMouseActivity = null;
            // 标志位改变
            isStart = false;
        }
    }

    /// <summary>
    /// 鼠标钩子回调函数
    /// </summary>
    private int MouseHookProc(int nCode, Int32 wParam, IntPtr lParam)
    {
        // 假设正常执行而且用户要监听鼠标的消息
        if ((nCode >= 0) && (OnMouseActivity != null))
        {
            MouseHookStruct MyMouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
            OnMouseActivity(wParam, MyMouseHookStruct);
        }
        // 启动下一次钩子
        return CallNextHookEx(_hMouseHook, nCode, wParam, lParam);
    }

    /// <summary>
    /// 注册全局鼠标事件
    /// </summary>
    /// <param name="handler"></param>
    public void AddMouseHandler(MyMouseEventHandler handler)
    {
        OnMouseActivity += handler;
    }

    /// <summary>
    /// 注销全局鼠标事件
    /// </summary>
    /// <param name="handler"></param>
    public void RemoveMouseHandler(MyMouseEventHandler handler)
    {
        if (OnMouseActivity != null)
        {
            OnMouseActivity -= handler;
        }
    }
}

使用

启动监听

MouseHook mouseHook = GetMouseHook();
private void startListenMouse(MyMouseEventHandler handler)
{
    mouseHook.AddMouseHandler(handler);
    mouseHook.Start();
}

停止监听

// 停止钩子
public void stopListenMouse()
{
    mouseHook.Stop();
}

监听回调函数

提示:虽然有双击判断,但是实际上不生效。要通过两次触发事件的时间间隔进行判断

private void mouseHandler(Int32 wParam, MouseHookStruct mouseMsg)
{
    //taskTxtBx.AppendText("wParam:" + wParam + "\r\n");
    switch (wParam)
    {
        case WM_MOUSEMOVE:
            // 鼠标移动
            int x = mouseMsg.pt.x;
            int y = mouseMsg.pt.y;
            taskTxtBx.AppendText(x + "," + y+"\r\n");
            break;
        case WM_LBUTTONDOWN:
            // 鼠标左键
            taskTxtBx.AppendText("按下鼠标左键\r\n");
            break;
        case WM_LBUTTONUP:
            taskTxtBx.AppendText("松开鼠标左键\r\n");

            break;
        case WM_LBUTTONDBLCLK:
            taskTxtBx.AppendText("双击鼠标左键\r\n");

            break;
        case WM_RBUTTONDOWN:
            taskTxtBx.AppendText("按下鼠标右键\r\n");

            break;
        case WM_RBUTTONUP:
            taskTxtBx.AppendText("松开鼠标右键\r\n");

            break;
        case WM_RBUTTONDBLCLK:
            taskTxtBx.AppendText("双击鼠标右键\r\n");

            break;
        case WM_WHEELDOWN:
            taskTxtBx.AppendText("按下鼠标滑轮\r\n");

            break;
        case WM_WHEELUP:
            taskTxtBx.AppendText("松开鼠标滑轮\r\n");

            break;

        case WM_WHEELSCROLL:
            taskTxtBx.AppendText("滚动滑轮\r\n");
            if(mouseMsg.hWnd == WM_SCROLLBACK)
            {
                taskTxtBx.AppendText("向后滚动滑轮:"+mouseMsg.hWnd + "," + mouseMsg.wHitTestCode + "," + mouseMsg.dwExtraInfo + "\r\n");
            }else if(mouseMsg.hWnd == WM_SCROLLFORWARD)
            {
                taskTxtBx.AppendText("向前滚动滑轮:" + mouseMsg.hWnd + "," + mouseMsg.wHitTestCode + "," + mouseMsg.dwExtraInfo + "\r\n");
            }

            break;
        default:
            taskTxtBx.AppendText("未知操作"+ wParam + "\r\n");
            break;
    }
}

错误:获取鼠标位置可能错误

详见:https://www.malaoshi.top/show_1IX8FifIbtNn.html

解决:

获取分辨率缩放

float screenScale = ScreenScale.GetScreenScale();

将鼠标坐标除以缩放比例:

case WM_MOUSEMOVE:
    // 鼠标移动
    int x = (int) ( mouseMsg.pt.x / screenScale);
    int y = (int)(mouseMsg.pt.y / screenScale);
    taskTxtBx.AppendText("mouse move " + x + "," + y + "\r\n");
    break;

参考:
https://blog.csdn.net/qq_43851684/article/details/113096306