如何使用 Win32 API 阻止来自透明窗口的鼠标输入?

Posted

技术标签:

【中文标题】如何使用 Win32 API 阻止来自透明窗口的鼠标输入?【英文标题】:How to block mouse input from transparent window with Win32 API? 【发布时间】:2017-05-23 00:38:07 【问题描述】:

我正在创建的程序不拥有进程中的主窗口。我正在使用Windows Hook 将 DLL 注入此进程,以便将子窗口添加到此主窗口。

我的最终目标是创建一个WS_EX_LAYERED 窗口,它允许我创建一个内部彩色边框,但允许中心部分是透明的并允许鼠标​​点击。这部分工作完美。

WNDCLASS wndClass = ;
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = OverlayProc;
wndClass.hInstance = g_TargetInstance;
wndClass.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(0, 255, 255));
wndClass.lpszClassName = "OVERLAY";

RegisterClass(&wndClass);
g_Window = CreateWindowEx(WS_EX_LAYERED | WS_EX_TRANSPARENT, "OVERLAY", nullptr,
    WS_CHILDWINDOW, rect.left, rect.top, rect.right+1, rect.bottom+1, data->hwnd, nullptr, g_TargetInstance, nullptr);

SetLayeredWindowAttributes(g_Window, RGB(0, 255, 255), 0, LWA_COLORKEY);

ShowWindow(g_Window, SW_SHOW);
UpdateWindow(g_Window);

第二部分是我想有条件地阻止所有鼠标输入到父窗口。我无法使用 WS_EX_LAYERED 窗口的透明部分执行此操作,因此我尝试创建第二个透明 STATIC 控件作为主窗口的子窗口,但这也不会阻止鼠标输入。

我还通过调用PostMessage 将模拟鼠标点击发送到父窗口,传递WM_LBUTTONDOWNWM_LBUTTONUP。如何通过透明窗口阻止所有鼠标输入到父窗口?

【问题讨论】:

您可以将 SetLayeredWindowAttributes 的第三个参数设置为 1 而不是 0。 你不想使用 SetWindowsHookEx,是吗? “我还通过调用 PostMessage 将模拟的鼠标点击发送到父窗口” - 这不是模拟输入。这是装的。它也在做a damn poor job。 @IInspectable 我不确定你是否理解simulate 这个词的含义。模拟输入与“伪造”输入完全相同。另外,我不是也没有说我在“伪造”键盘输入,但需要明确的是,我确实有一个使用 PostMessage 来“伪造”键盘输入的测试功能,在我的情况下,它可以完美可靠地运行,所以说这是一个“该死的糟糕工作”是相当主观的。 @KonstantinL 我正在使用 SetWindowsHookEx,我确实想使用它,但仅用于将我自己的消息处理程序和窗口注入目标进程的车辆。 【参考方案1】:

这似乎不可能用一个简单的透明窗口绘制在同级控件上。我最终做的是使用SetWindowHookExWH_GETMESSAGE 钩子添加到我用来替换主窗口的WndProc 函数并拦截鼠标消息的进程中。我在wParam 参数中使用特定值标记我的模拟鼠标消息,因此proc 现在将模拟并删除该值,并将其传递给父窗口。

如果它没有在点击消息中检测到我的“标签”值,它会吞下鼠标消息,而不是将其传递给原始的 WndProc 函数。

注入的 WndProc 替换

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)

    switch (uMsg)
    
        case WM_LBUTTONDOWN:
            wParam -= 11141008;
            if (wParam != MK_LBUTTON && !g_Paused)
                return 0;
            break;

        case WM_LBUTTONUP:
            wParam -= 11141008;
            if (wParam != 0 && !g_Paused)
                return 0;
            break;

        case WM_MOUSEHOVER:
        case WM_MOUSEMOVE:
            if (!g_Paused)
                return 0;
    

    return DefSubclassProc(hWnd, uMsg, wParam, lParam);

来自 Windows Hook 函数的片段

//...
switch (data->message)

    case (WM_USER + 1):
    
        g_Paused = FALSE;
        //...
        SetWindowSubclass(data->hwnd, WndProc, 1, 0);
        break;
    

    case (WM_USER + 2):
    
        RemoveWindowSubclass(data->hwnd, WndProc, 1);
        //...
        break;
    

//...

窗口挂钩函数中的代码用于子类化主进程窗口并注入我自己的 WndProc 函数,该函数反过来按我想要的方式处理鼠标输入。

这是用于“模拟”鼠标单击而不实际单击窗口的代码。请注意添加到 wParam 的值,以标识此点击是模拟的,而不是由用户生成的。

void Window::LeftClick(DWORD x, DWORD y, DWORD delayMillis)

    LPARAM lparam = MAKELPARAM(x, y);

    lock_guard<mutex> lock(this->m_ClickMutex);

    PostMessage(this->m_Window, WM_LBUTTONDOWN, 11141008 + MK_LBUTTON, lparam);
    this_thread::sleep_for(std::chrono::milliseconds(delayMillis));
    PostMessage(this->m_Window, WM_LBUTTONUP, 11141008, lparam);

另外,对于那些嘲笑我选择simulated这个词的人,以及对使用PostMessage模拟键盘输入的批评,这是我的键盘输入测试方法(出于我的目的)工作完美且非常可靠

void GameWindow::KeyPress(UINT vkCode) const

    UINT scanCode = MapVirtualKey(vkCode, MAPVK_VK_TO_VSC);
    LPARAM lparam1 = MAKELPARAM(1, scanCode);
    LPARAM lparam2 = MAKELPARAM(1, 0xC000 | scanCode);

    PostMessage(this->m_Window, WM_KEYDOWN, vkCode, lparam1);
    this_thread::sleep_for(chrono::milliseconds(25));
    PostMessage(this->m_Window, WM_KEYUP, vkCode, lparam2);

【讨论】:

这个人没有“嘲笑[你的]选择的作品simulated。他解释说,您的输入根本没有被模拟,而是伪造的。该人还提供了一个链接,其中包含大量信息、问题所在以及如何解决。 @IInspectable 您只提供了有关键盘输入的信息,这根本不是我要求的。 类似的问题也适用于鼠标输入。根本问题是相同的:您的伪造输入没有得到处理

以上是关于如何使用 Win32 API 阻止来自透明窗口的鼠标输入?的主要内容,如果未能解决你的问题,请参考以下文章

Win32纯API创建半透明窗口

Win32纯API创建半透明窗口

如何用纯Win32 API写模态窗口?

win32 窗口阻止键盘布局更改

专题:DUILIB Win32 透明效果

如何在WIN32 API中“通知”父窗口有关子窗口“列表框”控件的“滚动事件”?