如何检测子窗口上的 WM_KEYDOWN?

Posted

技术标签:

【中文标题】如何检测子窗口上的 WM_KEYDOWN?【英文标题】:How do I detect a WM_KEYDOWN on child window? 【发布时间】:2021-07-23 11:04:54 【问题描述】:

对于像按钮、编辑等标准控件,我可以将其子类化,但是对于子窗口(我用作容器)如何完成?我知道WM_KEYDOWN 已发送到父窗口,但我无法直接从相应的子窗口获取它。我试过子类化它(不确定这是否有意义,因为它是一个窗口并且在WNDCLASSW.lpfnWndProc 成员中已经有自己的窗口过程)但是这个子过程无论如何都没有收到WM_KEYDOWN 消息。我可以通过使用GetCursorPos()WindowFromPoint() 从光标位置(我也需要考虑键盘焦点)获取控件的hwnd,如下所示,但我觉得它很hacky。这样做的正确方法是什么?

目前看起来是这样的:

    case WM_KEYUP:
    case WM_KEYDOWN:
    
        POINT p;
        if(GetCursorPos(&p))
        
            HWND control = WindowFromPoint(p);
            if(control)
            
                // just testing it
                int len = GetWindowTextLength(control);
                wchar_t buffer[len + 1];
                GetWindowText(control, buffer, sizeof(buffer));
                MessageBox(NULL, buffer, L"", MB_OK);
            
        
    
    break;

窗口如下所示:

目标是在用户键入内容的相应窗口上捕获 WM_KEYDOWN,然后调用一个函数来处理此事件,例如window1_onKeyDown()window2_onKeyDown() 等。

这里是完整的代码:

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "Comctl32.lib")
#pragma comment(lib, "Gdi32.lib")

#define WIN32_LEAN_AND_MEAN
#define UNICODE
#define _UNICODE

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WndProc1(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK WndProc2(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
void createWindow1(HWND);
void createWindow2(HWND);

HBRUSH hBrush1, hBrush2;

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) 

    MSG  msg;    
    WNDCLASSW wc = 0;
    wc.lpszClassName = L"my window";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    hBrush1 = CreateSolidBrush(RGB(173, 164, 237));
    hBrush2 = CreateSolidBrush(RGB(171, 171, 171));

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"window",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  100, 100, 330, 270, NULL, 0, hInstance, 0);

    while (GetMessage(&msg, NULL, 0, 0))
    
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    

    DeleteObject(hBrush1);
    DeleteObject(hBrush2);

    return (int) msg.wParam;


LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) 

    switch(msg)
    

        case WM_CREATE:
            createWindow1(hwnd);
            createWindow2(hwnd);
        break;

      
        case WM_KEYUP:
        case WM_KEYDOWN:
        
            POINT p;
            if(GetCursorPos(&p))
            
                HWND control = WindowFromPoint(p);
                if(control)
                
                    int len = GetWindowTextLength(control);
                    wchar_t buffer[len + 1];
                    GetWindowText(control, buffer, sizeof(buffer));
                    MessageBox(NULL, buffer, L"", MB_OK);
                
            
        
        break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;
    

    return DefWindowProcW(hwnd, msg, wParam, lParam);


void createWindow1(HWND hOwner)

    WNDCLASSW wc = 0;
    wc.lpszClassName = L"window2";
    wc.hInstance     = NULL;
    wc.hbrBackground = hBrush1;
    wc.lpfnWndProc   = WndProc1;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"window2",
                  WS_VISIBLE | WS_TABSTOP | WS_CHILD | WS_EX_CONTROLPARENT,
                  5, 5, 200, 100, 
                  hOwner, 0, NULL, 0);


void createWindow2(HWND hOwner)

    WNDCLASSW wc = 0;
    wc.lpszClassName = L"window3";
    wc.hInstance     = NULL;
    wc.hbrBackground = hBrush2;
    wc.lpfnWndProc   = WndProc2;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"window3",
                  WS_VISIBLE | WS_TABSTOP | WS_CHILD,
                  5, 120, 200, 100, 
                  hOwner, 0, NULL, 0);


LRESULT CALLBACK WndProc1(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)


    switch(msg)
    
        case WM_CREATE:
            CreateWindow(L"Button", L"Button A", 
                         WS_VISIBLE | WS_TABSTOP | WS_CHILD,
                         5, 5, 80, 25,
                         hwnd, 0, NULL, 0);
        break;

        case WM_KEYUP:
        case WM_KEYDOWN:
            MessageBox(NULL, L"hello from proc1", L"", MB_OK);
            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;
    

    return DefWindowProcW(hwnd, msg, wParam, lParam);


LRESULT CALLBACK WndProc2(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 


    switch(msg)
    
        case WM_CREATE:
            CreateWindow(L"Button", L"Button ", 
                         WS_VISIBLE | WS_TABSTOP | WS_CHILD,
                         5, 5, 80, 25,
                         hwnd, 0, NULL, 0);
        break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;
    

    return DefWindowProcW(hwnd, msg, wParam, lParam);

【问题讨论】:

Those who do not understand the dialog manager are doomed to reimplement it, badly. 【参考方案1】:

您可以在子窗口中使用SetFocus

一些代码:

case WM_KEYDOWN:
    
        POINT p;
        HWND control = GetFocus();
        if (control)
        
            int len = GetWindowTextLength(control);
            wchar_t* buffer = new wchar_t[len + 1];
            GetWindowText(control, buffer, len + 1);
            MessageBox(NULL, buffer, L"", MB_OK);
        
    
    break;

LRESULT CALLBACK WndProc1(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)


    switch (msg)
    
    case WM_CREATE:
        CreateWindow(L"Button", L"Button A",
            WS_VISIBLE | WS_TABSTOP | WS_CHILD,
            5, 5, 80, 25,
            hwnd, 0, NULL, 0);
        break;
    case WM_LBUTTONDOWN:
        SetFocus(hwnd);
        break;
    case WM_KEYUP:
    case WM_KEYDOWN:
        MessageBox(NULL, L"hello from proc1", L"", MB_OK);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    

    return DefWindowProcW(hwnd, msg, wParam, lParam);

也可以使用current方法,然后使用PostMessage/SendMessage将WM_KEYDOWN消息转发到子窗口:

    if (control)
    
        int len = GetWindowTextLength(control);
        wchar_t* buffer = new wchar_t[len + 1];
        GetWindowText(control, buffer, len + 1);
        MessageBox(NULL, buffer, L"", MB_OK);
        PostMessage(hwnd_child, msg, wParam, lParam);
    

【讨论】:

Replaying input is not the same as reprocessing it.

以上是关于如何检测子窗口上的 WM_KEYDOWN?的主要内容,如果未能解决你的问题,请参考以下文章

SetFocus 到父窗口,然后回到子窗口

检测鼠标在子窗口外的点击

如何访问 UIWebView 的子窗口上下文

按键精灵,父窗口下很多子窗口怎么找想要的

QT中父窗口响应子窗口动作

如何在父窗口中运行子窗口