You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows平台下如何通过代码方式关闭Popup Menu?

解决Popup菜单的显示/切换问题

我来帮你梳理下当前代码的问题,以及实现点击按钮切换菜单显示/隐藏的正确方式:

首先修复当前菜单无法显示的问题

你代码里gMenuHidden初始值是false,第一次点击按钮时会进入空的else分支,菜单根本不会弹出来——这是最直接的问题,得先调整逻辑。

另外你原来的代码里,创建菜单后调用TrackPopupMenu,紧接着就DestroyMenu:虽然TrackPopupMenu是模态函数(会阻塞到菜单关闭),菜单能临时显示,但每次点击都重复创建销毁菜单,不仅没必要,还会增加不必要的开销。

实现菜单的显示/切换逻辑

要实现“点击按钮显示菜单,再次点击关闭菜单”,核心是主动终止模态的菜单弹窗,同时复用菜单资源。这里不需要换其他类,用原生Win32 API就能搞定:

关键思路

  • 菜单只创建一次(放在窗口初始化阶段),程序退出时再销毁
  • 用全局变量记录菜单是否正在显示
  • 菜单未显示时,调用TrackPopupMenu弹出;菜单已显示时,发送WM_CANCELMODE消息主动关闭它

修改后的完整代码示例

#define MAX_LOADSTRING 100
#define IDM_FILE_NEW 1
#define IDM_FILE_OPEN 2
#define IDM_FILE_QUIT 3
#define BUTTON_ID 1

// Global Variables:
HINSTANCE hInst; // current instance
WCHAR szTitle[MAX_LOADSTRING]; // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
HWND gButton = NULL;
HWND mainHwnd = NULL;
HMENU hMenu = NULL; // 全局菜单句柄,只创建一次
bool gMenuIsShowing = false; // 记录菜单是否正在显示

// Forward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // Initialize global strings
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_POPUPMENU, szWindowClass, MAX_LOADSTRING);

    MyRegisterClass(hInstance);

    // Perform application initialization:
    if (!InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_POPUPMENU));

    MSG msg;

    // Main message loop:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    // 程序退出前销毁菜单资源
    if (hMenu)
        DestroyMenu(hMenu);

    return (int)msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_POPUPMENU));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance; // Store instance handle in our global variable

    mainHwnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

    if (!mainHwnd)
    {
        return FALSE;
    }

    ShowWindow(mainHwnd, nCmdShow);
    UpdateWindow(mainHwnd);

    return TRUE;
}

HMENU CreateAndInitializeMenu()
{
    HMENU menu = CreatePopupMenu();
    AppendMenuW(menu, MF_STRING, IDM_FILE_NEW, L"&New");
    AppendMenuW(menu, MF_STRING, IDM_FILE_OPEN, L"&Open");
    AppendMenuW(menu, MF_SEPARATOR, 0, NULL);
    AppendMenuW(menu, MF_STRING, IDM_FILE_QUIT, L"&Quit");
    return menu;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        // 处理菜单命令
        switch (wmId)
        {
        case IDM_FILE_NEW:
            MessageBox(hWnd, L"New clicked", L"Info", MB_OK);
            gMenuIsShowing = false;
            break;
        case IDM_FILE_OPEN:
            MessageBox(hWnd, L"Open clicked", L"Info", MB_OK);
            gMenuIsShowing = false;
            break;
        case IDM_FILE_QUIT:
            DestroyWindow(hWnd);
            break;
        case BUTTON_ID:
            if (!gMenuIsShowing)
            {
                // 菜单未显示,弹出菜单(位置设为按钮右下角,更符合用户习惯)
                RECT btnRect;
                GetWindowRect(gButton, &btnRect);
                gMenuIsShowing = true;
                TrackPopupMenu(hMenu, TPM_RIGHTBUTTON, btnRect.right, btnRect.bottom, 0, hWnd, NULL);
                // 菜单关闭后更新状态
                gMenuIsShowing = false;
            }
            else
            {
                // 菜单正在显示,主动发送消息关闭它
                SendMessage(hWnd, WM_CANCELMODE, 0, 0);
                gMenuIsShowing = false;
            }
            break;
        default:
            return DefWindowProcW(hWnd, message, wParam, lParam);
        }
    }
    break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_CREATE:
    {
        gButton = CreateWindowW(L"button", L"Show/Hide menu", WS_VISIBLE | WS_CHILD, 100, 120, 120, 25, hWnd, (HMENU)BUTTON_ID, NULL, NULL);
        // 提前创建菜单,避免第一次点击时的延迟
        hMenu = CreateAndInitializeMenu();
    }
    break;
    default:
        return DefWindowProcW(hWnd, message, wParam, lParam);
    }
    return 0;
}

关键修改点说明

  1. 菜单资源复用:把菜单创建移到WM_CREATE事件中,程序退出时才销毁,避免重复创建销毁的开销。
  2. 修复显示逻辑:调整状态变量gMenuIsShowing的逻辑,第一次点击按钮直接弹出菜单。
  3. 主动关闭菜单:WM_CANCELMODE消息会终止当前窗口的模态操作(比如TrackPopupMenu的弹窗),从而实现点击按钮关闭菜单的效果。
  4. 优化菜单位置:将菜单弹出位置设为按钮的右下角,更符合常规交互习惯。
  5. 完善状态同步:菜单项被点击后,同步更新gMenuIsShowing的状态,确保按钮点击逻辑始终准确。

内容的提问来源于stack exchange,提问作者Oleg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 06:22:39