Dmytro
Dmytro

Reputation: 5213

how to handle WM_NCHITTEST in a naked event loop?

I'm playing with Windows API trying to understand how it behaves, and I realized that I can remove WNDPROC altogether and handle things with a naked event loop, like this:

#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;
}

I wanted to go one step further and try making this window moveable using this technique, and got confused because I can't "return" from a message loop in the way i'm used to(the statement return hit;) does not make sense in this context.

Here is how I started, and got confused:

#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;
}

How can I simulate returning "hit" from the WM_NCHITTEST condition so that it moves the window like in the solution here: https://stackoverflow.com/a/7773941/2012715 ?

PS: I know that it's better to use a map(like std::unordered_map) rather than a long if/switch for scalability and readability, but I wanted to keep the example more direct.

Upvotes: 1

Views: 522

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 598011

You can't.

The code you have shown will NEVER work, because (Get|Peek)Message() returns only QUEUED messages. You cannot reply to a queued message, because the sender is not waiting for a reply, it put the message in the queue and moved on to other things. Only NON-QUEUED messages that are sent with the SendMessage...() family of functions can be replied to. (Get|Peek)Message() will NEVER return a sent message, but will internally dispatch it to the target window's message procedure (only messages sent from another thread will be dispatched, messages sent to a window by the same thread that owns the window will bypass the message queue completely).

WM_PAINT is a queued message, so your event loop sees it. But WM_NCHITTEST is not queued, so your message loop will NEVER see it directly, it can only be seen in a message procedure.

What you have shown is NOT the right way to handle a Windows UI message loop. Since you are creating a UI window, you MUST provide it with a message procedure (if not by RegisterClass/Ex(), then by SetWindowLong/Ptr(GWL_WNDPROC) or SetWindowSubclass()). But DO NOT use DefWindowProc() for that procedure if you need to process messages manually. Provide your own message procedure that calls DefWindowProc() (or CallWindowProc() in the case of GWL_WNDPROC, or DefSubclassProc() in the case of SetWindowSubclass()) for any unhandled messages, eg:

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;
}

Upvotes: 4

Related Questions