【问题标题】:how to handle WM_NCHITTEST in a naked event loop?如何在裸事件循环中处理 WM_NCHITTEST?
【发布时间】:2017-08-29 19:22:30
【问题描述】:

我正在使用 Windows API 试图了解它的行为方式,我意识到我可以完全删除 WNDPROC 并使用裸事件循环来处理事情,如下所示:

#include <Windows.h>

static struct {
    HWND desktop;
    HWND window;
} global;

int main(int argc, char **argv)
{
    /* anonymous scope: register window class */
    {
        WNDCLASSEXW wcx;
        wcx.cbSize = sizeof(wcx);
        wcx.style = CS_HREDRAW | CS_VREDRAW;
        wcx.lpfnWndProc = DefWindowProc;
        wcx.cbClsExtra = sizeof(void *);
        wcx.cbWndExtra = sizeof(void *);
        wcx.hInstance = (HINSTANCE)GetModuleHandle(NULL);
        wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
        wcx.hbrBackground = (HBRUSH)COLOR_WINDOWFRAME;
        wcx.lpszMenuName = NULL;
        wcx.lpszClassName = L"MyWindow";
        wcx.hIconSm = wcx.hIcon;
        RegisterClassExW(&wcx);
    }

    global.desktop = GetDesktopWindow();

    global.window = CreateWindowExW (
        0,
        L"MyWindow",
        NULL,
        WS_POPUPWINDOW | WS_VISIBLE,
        0,
        0,
        320,
        200,
        global.desktop,
        NULL,
        (HINSTANCE)GetModuleHandle(NULL),
        NULL
    );

    /* anonymous scope, event loop */
    {
        MSG msg;

        while (GetMessage(&msg, NULL, 0, 0) > 0) {
            TranslateMessage(&msg);

            if(msg.hwnd == global.window) {
                if (msg.message == WM_PAINT) {
                    PAINTSTRUCT ps;
                    HDC hdc;

                    hdc = BeginPaint(msg.hwnd, &ps);

                    SelectObject(hdc, GetStockObject(BLACK_BRUSH));
                    Rectangle(hdc, 0, 0, 320, 200);

                    EndPaint(msg.hwnd, &ps);
                } else {
                    DispatchMessage(&msg);
                }
            } else {
                DispatchMessage(&msg);
            }
        }    
    }


    return 0;
}

我想更进一步,尝试使用this 技术使此窗口可移动,但我感到困惑,因为我无法以我习惯的方式从消息循环中“返回”(声明 @987654326 @) 在这种情况下没有意义。

我是这样开始的,然后感到困惑:

#include <Windows.h>

static struct {
    HWND desktop;
    HWND window;
} global;

int main(int argc, char **argv)
{
    /* anonymous scope: register window class */
    {
        WNDCLASSEXW wcx;
        wcx.cbSize = sizeof(wcx);
        wcx.style = CS_HREDRAW | CS_VREDRAW;
        wcx.lpfnWndProc = DefWindowProc;
        wcx.cbClsExtra = sizeof(void *);
        wcx.cbWndExtra = sizeof(void *);
        wcx.hInstance = (HINSTANCE)GetModuleHandle(NULL);
        wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
        wcx.hbrBackground = (HBRUSH)COLOR_WINDOWFRAME;
        wcx.lpszMenuName = NULL;
        wcx.lpszClassName = L"MyWindow";
        wcx.hIconSm = wcx.hIcon;
        RegisterClassExW(&wcx);
    }

    global.desktop = GetDesktopWindow();

    global.window = CreateWindowExW (
        0,
        L"MyWindow",
        NULL,
        WS_POPUPWINDOW | WS_VISIBLE,
        0,
        0,
        320,
        200,
        global.desktop,
        NULL,
        (HINSTANCE)GetModuleHandle(NULL),
        NULL
    );

    /* anonymous scope, event loop */
    {
        MSG msg;

        while (GetMessage(&msg, NULL, 0, 0) > 0) {
            TranslateMessage(&msg);

            if(msg.hwnd == global.window) {
                if (msg.message == WM_PAINT) {
                    PAINTSTRUCT ps;
                    HDC hdc;

                    hdc = BeginPaint(msg.hwnd, &ps);

                    SelectObject(hdc, GetStockObject(BLACK_BRUSH));
                    Rectangle(hdc, 0, 0, 320, 200);

                    EndPaint(msg.hwnd, &ps);
                } else if (msg.message == WM_NCHITTEST) {
                    LRESULT hit = DefWindowProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
                    if (hit == HTCLIENT) {
                        hit = HTCAPTION;
                    }
                    // return hit; // makes no sense here
                } else {
                    DispatchMessage(&msg);
                }
            } else {
                DispatchMessage(&msg);
            }
        }    
    }


    return 0;
}

如何模拟从 WM_NCHITTEST 条件返回的“命中”,以便它像这里的解决方案一样移动窗口:https://stackoverflow.com/a/7773941/2012715

PS:我知道使用映射(如 std::unordered_map)而不是长 if/switch 以获得可伸缩性和可读性更好,但我想让示例更直接。

【问题讨论】:

  • 致反对票的人:如果您反对票,您应该解释原因,否则毫无意义。这个问题是可以理解的,甚至有一个 MCVE,所以我真的看不出有理由投反对票。
  • @zett42:这个问题是问随机更改代码引起的问题,OP不明白。这对一般大众来说几乎没有用处,他们往往想要解决实际问题,而不是人为的问题。 OP 应该 提出的有用问题是:DispatchMessage 的目的是什么?” 如所问,这个问题没有用。
  • 随机更改代码我不懂?我对如何将 NCHITTEST 发送到窗口感到困惑。结果是window proc可以看到,但线程消息队列看不到;了解这是如何发生的很有用(线程消息队列可以看到 WM_PAINT 和 WM_MOUSEMOVE 等一些消息,而 NCHITTEST 则没有)。我不明白你的担心。
  • 按理说,你不了解注册窗口过程的后果,因为你不了解Windows应用程序中的message routing。所有这些都记录在案并且随时可用。询问如何最好地对抗系统的问题通常没有用。
  • 我了解注册窗口过程的后果。我不明白排队和非排队消息的概念。现在很有意义。

标签: c winapi


【解决方案1】:

你不能。

您显示的代码永远不会起作用,因为(Get|Peek)Message() 只返回QUEUED 消息。您无法回复排队的消息,因为发件人没有等待回复,而是将消息放入队列并转移到其他事情上。只能回复使用SendMessage...() 系列函数发送的NON-QUEUED 消息。 (Get|Peek)Message() 永远不会返回已发送的消息,但会在内部将其分派到目标窗口的消息过程(只会分派从另一个线程发送的消息,由拥有窗口的同一线程发送到窗口的消息将绕过消息队列完全)。

WM_PAINT 是一个排队的消息,所以你的事件循环会看到它。但是WM_NCHITTEST 没有排队,所以你的消息循环永远不会直接看到它,它只能在消息过程中看到。

您所展示的不是处理 Windows UI 消息循环的正确方法。由于您正在创建一个 UI 窗口,因此您必须为它提供一个消息过程(如果不是由 RegisterClass/Ex() 提供,则由 SetWindowLong/Ptr(GWL_WNDPROC)SetWindowSubclass() 提供)。但是,如果您需要手动处理消息,请不要在该过程中使用DefWindowProc()。为任何未处理的消息提供您自己的消息过程,该过程调用DefWindowProc()(或CallWindowProc(),在GWL_WNDPROC 的情况下,或DefSubclassProc(),在SetWindowSubclass() 的情况下),例如:

LRESULT WINAPI MyWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg) {
        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);

            SelectObject(hdc, GetStockObject(BLACK_BRUSH));
            Rectangle(hdc, 0, 0, 320, 200);

            EndPaint(hwnd, &ps);
            return 0;
        }

        case WM_NCHITTEST: {
            LRESULT hit = DefWindowProc(hwnd, uMsg, wParam, lParam);
            if (hit == HTCLIENT) {
                hit = HTCAPTION;
            }
            return hit;
        }
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

int main(int argc, char **argv)
{
    /* anonymous scope: register window class */
    {
        WNDCLASSEXW wcx;
        wcx.cbSize = sizeof(wcx);
        wcx.style = CS_HREDRAW | CS_VREDRAW;
        wcx.lpfnWndProc = MyWndProc; // <--
        wcx.cbClsExtra = sizeof(void *);
        wcx.cbWndExtra = sizeof(void *);
        wcx.hInstance = (HINSTANCE)GetModuleHandle(NULL);
        wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
        wcx.hbrBackground = (HBRUSH)COLOR_WINDOWFRAME;
        wcx.lpszMenuName = NULL;
        wcx.lpszClassName = L"MyWindow";
        wcx.hIconSm = wcx.hIcon;
        RegisterClassExW(&wcx);
    }

    global.desktop = GetDesktopWindow();

    global.window = CreateWindowExW (
        0,
        L"MyWindow",
        NULL,
        WS_POPUPWINDOW | WS_VISIBLE,
        0,
        0,
        320,
        200,
        global.desktop,
        NULL,
        (HINSTANCE)GetModuleHandle(NULL),
        NULL
    );

    /* anonymous scope, event loop */
    {
        MSG msg;

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

    return 0;
}

【讨论】:

  • 我知道我可以在回调中轻松做到这一点。我只是好奇是否有办法以这种方式处理 NCHITTEST。令我惊讶的是,即使有回调,循环本身似乎也从未收到 WM_NCHITTEST 消息。似乎 WndProc 在消息循环之前收到了消息,所以我在考虑它。这种也解释了为什么如果 STATIC 窗口没有得到其他窗口所做的许多事件,它的默认进程会在循环看到它们之前消耗它们,我的窗口足够慷慨,可以将它们传递给循环看到.
  • 我仍然不确定为什么我的 proc 从来没有通过 NCHITTEST 循环查看,真的没有办法暴露它吗?为什么循环可以看到 MOUSEMOVE,而 NCHITTEST 没有?
  • @Dmitry:正如我在回答中解释的那样,WM_NCHITTEST 不是排队消息,它不会通过消息队列,因此消息循环永远不会看到它。另一方面,WM_MOUSMOVE 是排队消息,因此它会通过消息队列并被消息循环看到。请阅读我的答案中的链接,它们解释了这种差异。操作系统(或其他发送者)依赖于回复的事情没有排队。通知和其他不依赖于回复的事情通常会排队。
  • 哦,所以有些消息是排队的,而有些消息是直接通过 CallWindowProc 而不是 SendMessage 发送的?
  • @Dmitry:你在混合苹果和橙子。 SendMessage()CallWindowProc() 是用于不同目的的非常不同的东西。似乎您需要对 Windows 的消息传递系统的实际工作方式、不同的组件是什么以及它们所扮演的角色进行一些研究。 MSDN 有关于这个主题的非常详细的文档。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-13
  • 1970-01-01
相关资源
最近更新 更多