如何区分物理鼠标点击与代码触发点击?自动点击器钩子问题
自动点击器钩子误触发问题解决方案
问题描述
开发自动点击器时,使用低级鼠标钩子检测左键按下/抬起事件来控制自动点击定时器,但调用Core.LeftClick()模拟点击时,钩子会捕获到该模拟操作,导致LeftButtonStatus被错误修改,定时器意外停止。相关代码如下:
主窗体代码
private bool LeftButtonStatus; private void AutoClicker_Load(object sender, EventArgs e) { core.mouseHook.LeftButtonDown += new MouseHook.MouseHookCallback(mouseHook_LeftKeyDown); core.mouseHook.LeftButtonUp += new MouseHook.MouseHookCallback(mouseHook_LeftKeyUp); } private void mouseHook_LeftKeyDown(MouseHook.MSLLHOOKSTRUCT ma) { LeftButtonStatus = true; StartClicking(); } private void mouseHook_LeftKeyUp(KeyboardHook.VKeys key) { LeftButtonStatus = false; StartClicking(); } private void StartClicking() { if (LeftButtonStatus) LeftButtonTimer.Start(); else LeftButtonTimer.Stop(); } private void LeftButtonTimer_Tick(object sender, EventArgs e) { Core.LeftClick(); }
Core类点击实现
[DllImport("user32.dll")] private static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo); public static void LeftClick() { mouse_event(((int)KeyStates.LeftDown) | ((int)KeyStates.LeftDown), 0, 0, 0, 0); }
解决方案
方法1:添加模拟点击标记,忽略钩子捕获
在全局(或Core类)添加标记变量,模拟点击时设置标记,钩子回调中检查标记并忽略模拟产生的事件。
修改Core类
[DllImport("user32.dll")] private static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo); public static bool IsSimulatingClick { get; private set; } public static void LeftClick() { try { IsSimulatingClick = true; // 修正原代码重复LeftDown的问题,改为LeftDown | LeftUp完成完整点击 mouse_event(((int)KeyStates.LeftDown) | ((int)KeyStates.LeftUp), 0, 0, 0, 0); } finally { IsSimulatingClick = false; } }
修改钩子回调方法
private void mouseHook_LeftKeyDown(MouseHook.MSLLHOOKSTRUCT ma) { if (Core.IsSimulatingClick) return; LeftButtonStatus = true; StartClicking(); } private void mouseHook_LeftKeyUp(KeyboardHook.VKeys key) { if (Core.IsSimulatingClick) return; LeftButtonStatus = false; StartClicking(); }
方法2:利用钩子消息的注入标记过滤
低级鼠标钩子的MSLLHOOKSTRUCT结构包含flags字段,其中LLMHF_INJECTED(值为0x00000001)表示该事件是模拟输入产生的。直接在钩子回调中检查该标记,忽略注入的事件。
修改钩子回调方法
// 定义注入事件标识常量 private const int LLMHF_INJECTED = 0x00000001; private void mouseHook_LeftKeyDown(MouseHook.MSLLHOOKSTRUCT ma) { // 过滤模拟产生的事件 if ((ma.flags & LLMHF_INJECTED) != 0) return; LeftButtonStatus = true; StartClicking(); } // 注意:原LeftButtonUp回调参数类型可能有误,需修正为MSLLHOOKSTRUCT private void mouseHook_LeftKeyUp(MouseHook.MSLLHOOKSTRUCT ma) { if ((ma.flags & LLMHF_INJECTED) != 0) return; LeftButtonStatus = false; StartClicking(); }
方法3:改用SendInput替代mouse_event(更推荐)
mouse_event是已废弃API,推荐使用SendInput,同时可通过INPUT结构的dwExtraInfo传递自定义标识,在钩子中检查该标识忽略模拟事件。
修改Core类的LeftClick实现
[StructLayout(LayoutKind.Sequential)] private struct INPUT { public uint type; public MOUSEINPUT mi; } [StructLayout(LayoutKind.Sequential)] private struct MOUSEINPUT { public int dx; public int dy; public uint mouseData; public uint dwFlags; public uint time; public IntPtr dwExtraInfo; } [DllImport("user32.dll", SetLastError = true)] private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); // 自定义标识,用于区分模拟点击 private static readonly IntPtr SimulatedClickMarker = new IntPtr(0x12345678); public static void LeftClick() { INPUT[] inputs = new INPUT[2]; // 左键按下 inputs[0].type = 0; // INPUT_MOUSE inputs[0].mi.dwFlags = (uint)KeyStates.LeftDown; inputs[0].mi.dwExtraInfo = SimulatedClickMarker; // 左键抬起 inputs[1].type = 0; inputs[1].mi.dwFlags = (uint)KeyStates.LeftUp; inputs[1].mi.dwExtraInfo = SimulatedClickMarker; SendInput(2, inputs, Marshal.SizeOf(typeof(INPUT))); }
修改钩子回调方法
private void mouseHook_LeftKeyDown(MouseHook.MSLLHOOKSTRUCT ma) { if (ma.dwExtraInfo == Core.SimulatedClickMarker) return; LeftButtonStatus = true; StartClicking(); } private void mouseHook_LeftKeyUp(MouseHook.MSLLHOOKSTRUCT ma) { if (ma.dwExtraInfo == Core.SimulatedClickMarker) return; LeftButtonStatus = false; StartClicking(); }
注意点
- 原
LeftClick方法重复使用LeftDown,需改为LeftDown | LeftUp完成完整点击,否则会导致左键持续处于按下状态。 - 方法2需确保钩子封装的
MSLLHOOKSTRUCT包含flags字段,可根据实际封装调整代码。
内容的提问来源于stack exchange,提问作者Harabe Kowalski
相关产品推荐
相关产品推荐

