Search code examples
c++winapi

WM_HOTKEY Message Not Triggering Window Resize in WinMain Message Loop


#ifndef UNICODE
#define UNICODE
#endif

#define HOTKEY_REG_ERR -10

#define HOTKEY_DEC_ID 1
#define HOTKEY_INC_ID 2
#define HOTKEY_CEN_ID 3

#define WND_CHANGE_SIZE 50

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK WindowProcAlt(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
SHORT            ResizeWindow(HWND hwnd, int widthChange, int heightChange);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) {
    // Register the window class.
    LPCWSTR mainWndClass = L"Sample Window Class ";

    WNDCLASS wc = {};

    wc.lpfnWndProc   = WindowProc;
    wc.hInstance     = hInstance;
    wc.lpszClassName = mainWndClass;

    RegisterClass(&wc);

    // Create the window.

    HWND hwnd = CreateWindowEx(
        0,                           // Optional window styles.
        mainWndClass,                // Window class
        L"Learn to Program Windows", // Window text
        WS_OVERLAPPEDWINDOW,         // Window style

        // Size and position
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

        NULL,      // Parent window
        NULL,      // Menu
        hInstance, // Instance handle
        NULL       // Additional application data
    );

    if (hwnd == NULL) {
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);

    // Register hotkeys

    BOOL resultOne   = 0;
    BOOL resultTwo   = 0;
    BOOL resultThree = 0;

    if (!RegisterHotKey(NULL, HOTKEY_DEC_ID, MOD_CONTROL | MOD_SHIFT, '1')) {
        resultOne = -1;
    }
    if (!RegisterHotKey(NULL, HOTKEY_INC_ID, MOD_CONTROL | MOD_SHIFT, '2')) {
        resultTwo = -2;
    }
    if (!RegisterHotKey(NULL, HOTKEY_CEN_ID, MOD_CONTROL | MOD_SHIFT, '3')) {
        resultThree = -3;
    }

    // Run the message loop

    MSG msg = {};

    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        UINT messageCode = msg.message;
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC         hdc = BeginPaint(hwnd, &ps);

            // All painting occurs here, between BeginPaint and EndPaint.

            FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW + 1));

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

        case WM_HOTKEY:
            switch (wParam) {
                case HOTKEY_DEC_ID:
                    ResizeWindow(hwnd, -WND_CHANGE_SIZE, -WND_CHANGE_SIZE);
                    break;

                case HOTKEY_INC_ID:
                    ResizeWindow(hwnd, WND_CHANGE_SIZE, WND_CHANGE_SIZE);
                    break;

                case HOTKEY_CEN_ID:
                    break;

                default:
                    break;
            }
            return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

SHORT ResizeWindow(HWND hwnd, int widthChange, int heightChange) {
    if (hwnd == NULL) {
        return -1;
    }
    int  containerWidth, containerHeight, newWndWidth, newWndHeight, x, y;
    RECT rect;
    if (GetWindowRect(hwnd, &rect)) {
        RECT workArea = {};

        SystemParametersInfo(SPI_GETWORKAREA, NULL, &workArea, NULL);

        containerWidth  = workArea.right - workArea.left;
        containerHeight = workArea.bottom - workArea.top;

        newWndWidth  = rect.right - rect.left + widthChange;
        newWndHeight = rect.bottom - rect.top + heightChange;

        x = (containerWidth - newWndWidth) / 2;
        y = (containerHeight - newWndHeight) / 2;

        MoveWindow(hwnd, x, y, newWndWidth, newWndHeight, TRUE);
    } else {
        // std::cerr << "Failed to get window rect." << std::endl;
    }
}

Initially it looked like the WM_HOTKEY message was not being posted. Lack of window resizing told me this. I confirmed that my keys were correctly registered by looking at the values of resultOne, resultTwo, and resultThree in the debugger, all of which were 0.
I then looked into the message loop, and saw that within the message loop, the msg.message member was 786 = 0x0312 = the WM_HOTKEY message code.
Perhaps there's a threading issue I'm missing? Any help is appreciated, thank you.


Solution

  • Your WindowProc is not receiving the WM_HOTKEY messages because you are not registering the hotkeys with your window.

    The RegisterHotKey documentation says:

    Parameters

    [in, optional] hWnd

    Type: HWND

    A handle to the window that will receive WM_HOTKEY messages generated by the hot key. If this parameter is NULL, WM_HOTKEY messages are posted to the message queue of the calling thread and must be processed in the message loop.

    ...

    Remarks

    When a key is pressed, the system looks for a match against all hot keys. Upon finding a match, the system posts the WM_HOTKEY message to the message queue of the window with which the hot key is associated. If the hot key is not associated with a window, then the WM_HOTKEY message is posted to the thread associated with the hot key.

    A thread message does not have an HWND assigned to it, so there is no window procedure for DispatchMessage() to send the message to.

    As such, you will have to process the WM_HOTKEY messages directly inside your message loop (like the documentation above says to), eg:

    while (GetMessage(&msg, NULL, 0, 0)) {
        if (msg.message == WM_HOTKEY) {
            // do something ...
        }
        else {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    

    Otherwise, you will have to do one of the following so your WindowProc can receive the WM_HOTKEY messages:

    • simply pass your HWND to RegisterHotKey(), eg:

      RegisterHotKey(hwnd, ...)
      
    • have your message loop forward the messages to your HWND, eg:

      while (GetMessage(&msg, NULL, 0, 0)) {
          if (msg.message == WM_HOTKEY) {
              SendMessage(hwnd, msg.message, msg.wParam, msg.lParam);
          }
          else {
              TranslateMessage(&msg);
              DispatchMessage(&msg);
          }
      }