简体   繁体   中英

I'm testing WM_MENUCHAR but the code doesn't work as expected

The example below uses an app window with just the System menu, and I'm trying to understand how the message WM_MENUCHAR is supposed to be used. The MSDN doc for the message says:

Sent when a menu is active and the user presses a key that does not correspond to any mnemonic or accelerator key. This message is sent to the window that owns the menu.

In the example below whenever I press the keys Alt + A , Alt + B , or whatever, the WM_MENUCHAR is sent to the window proc.

I'm assuming that the Alt key activates the system menu, since all the WM_MENUCHAR messages sent to WndProc() have HIWORD(wParam) = MF_SYSMENU and the reason why the message is sent to the window is due to the fact that the program doesn't have an accelerator table, nor does the system menu contains any mnemonic.

This is point that is not clear to me: in response to the WM_MENUCHAR the code returns MAKELPARAM(5, MNC_SELECT) but this return value doesn't invoke the Close menu item in the System menu (remember that 5 is the base-0 order number for this menu item in the System menu). What am I missing?

#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pszCmdLine, int nCmdShow)
{
    WNDCLASSEX  wndclassx;

    wndclassx.cbSize = sizeof(WNDCLASSEX);
    wndclassx.style = CS_HREDRAW | CS_VREDRAW;
    wndclassx.lpfnWndProc = WndProc;
    wndclassx.cbClsExtra = 0;
    wndclassx.cbWndExtra = 0;
    wndclassx.hInstance = hInstance;
    wndclassx.hIcon = 0;
    wndclassx.hCursor = LoadCursor(0, IDC_ARROW);
    wndclassx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wndclassx.lpszClassName = L"WndProc";
    wndclassx.lpszMenuName = nullptr;
    wndclassx.hIconSm = 0;

    if (!RegisterClassEx(&wndclassx)) return 0;

    HWND hWnd = CreateWindow(L"WndProc", L"WM_MENUCHAR", WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, 0);

    ShowWindow(hWnd, SW_MAXIMIZE);
    UpdateWindow(hWnd);

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

    return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_MENUCHAR:
        return MAKELPARAM(5, MNC_SELECT);

        case WM_DESTROY:
        PostQuitMessage(0);
        break;

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

MNC_SELECT merely "selects" the menu item specified by the low word, ie if you could see the menu that menu item would be highlighted. Seems pretty pointless, not sure what the use case is. If you wanted to actually invoke the menu item, you would use MNC_EXECUTE.

Additionally, the default "system menu" returned in lParam of the WM_MENUCHAR message apparently has only one item -- one that activates the actual system menu. In the actual system menu, the separator between "maximize" and "close" counts as a menu item, so the ordinal number of the "close" menu item is actually 6. So this code will do the following: if you press Alt+A and keep holding Alt, it will pop up the window system menu. If you then press A again, it will close the window.

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int count = 0;
    switch (message)
    {
    case WM_MENUCHAR:
        count = GetMenuItemCount((HMENU)lParam);
        if (count == 1)
            return MAKELRESULT(0, MNC_EXECUTE);
        else
            return MAKELRESULT(6, MNC_EXECUTE);

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

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

And the following I believe shows the full intended use of the message, including MNC_SELECT and MNC_EXECUTE. If you hold down Alt and keep pressing A it will pop up the system menu and highlight every item on it from top to bottom until it gets to the last item (typically "close") which it then executes. Note the weird noop between highlighting "maximize" and highlighting "close" when it "selects" the separator.

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int count = 0;
    static int selected = 1;
    switch (message)
    {
    case WM_MENUCHAR:
        count = GetMenuItemCount((HMENU)lParam);
        if (count == 1)
            return MAKELRESULT(0, MNC_EXECUTE);
        else
        {
            if (selected < count)
                return MAKELRESULT(selected++, MNC_SELECT);
            else
                return MAKELRESULT(count - 1, MNC_EXECUTE);
        }

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM