You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何区分物理鼠标点击与代码触发点击?自动点击器钩子问题

自动点击器钩子误触发问题解决方案

问题描述

开发自动点击器时,使用低级鼠标钩子检测左键按下/抬起事件来控制自动点击定时器,但调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 06:25:17