在 Windows 上以编程方式关闭弹出菜单

Close popup menu programmatically on Windows

我有一个创建弹出菜单的简单示例。我想在按下按钮时以编程方式关闭此菜单,是否有可能这样做?或者我应该使用不同的 class?

我需要在按下按钮时打开此菜单,并在按下按钮时以相同的方式关闭它。这是我的简单代码示例。

#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;
bool gMenuHidden = 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);
        }
    }

    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:
        {
            if (wParam == BUTTON_ID)
            {
                //gMenuHidden = !gMenuHidden;
                if (gMenuHidden)
                {
                    hMenu = CreateAndInitializeMenu();

                    TrackPopupMenu(hMenu, TPM_RIGHTBUTTON, 150, 250, 0, mainHwnd, NULL);
                    DestroyMenu(hMenu);
                }
                else
                {
                    // TODO:
                }
            }
            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);
            break;
        }
    }

    return DefWindowProcW(hWnd, message, wParam, lParam);
}

也许正确的方法是始终在按下按钮时创建菜单并在按下按钮后立即销毁它?但在那种情况下,我们应该在按下按钮时一直创建菜单。我试图创建一次菜单并在我们需要时显示它。菜单现在也没有显示。试图找出原因。 提前致谢。

首先,您当然可以避免重复弹出菜单的 creation/destruction:只需在 window 创建时创建(一次)(通过处理 WM_CREATE 消息)并销毁它处理 WM_DESTROY 时。但请注意:您需要将 hMenu 设为 static 变量,以便在 WndProc 函数的多次调用中保持其值。

至于以编程方式隐藏菜单——您可以通过向父 window 发送 WM_CANCELMODE 消息来实现,如相关问题中所建议:How to CLOSE a context menu after a timeout?

你可以保留一个'flag'变量(也必须是static)来跟踪弹出菜单的当前显示状态:如果这是不是显示,调用TrackPopupMenu;如果 显示为 ,请发送 WM_CANCELMODE 消息。您还需要在对 TrackPopupMenu 的调用中添加 TPM_RETURNCMD 标志,以便您可以重置标志 if/when 用户从菜单中选择命令。

这是实现此方法的 WndProc 函数的一个版本:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    static HMENU hMenu;     // Make "static" so we can reuse across calls
    static bool hasMenu = false; // Flag to indicate current menu status
    POINT point;
    switch (msg) {
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDM_FILE_NEW:
                case IDM_FILE_OPEN:
                    MessageBeep(MB_ICONINFORMATION);
                    break;
                case IDM_FILE_QUIT:
                    SendMessage(hwnd, WM_CLOSE, 0, 0);
                    break;
            }
            break;
        case WM_CREATE:
            // Create the menu (once only) on window creation...
            hMenu = CreatePopupMenu();
            AppendMenuW(hMenu, MF_STRING, IDM_FILE_NEW, L"&New");
            AppendMenuW(hMenu, MF_STRING, IDM_FILE_OPEN, L"&Open");
            AppendMenuW(hMenu, MF_SEPARATOR, 0, NULL);
            AppendMenuW(hMenu, MF_STRING, IDM_FILE_QUIT, L"&Quit");
            hasMenu = false; // Reset here in case of multiple uses.
            break;
        case WM_RBUTTONUP:
            if (!hasMenu) { // Only show menu if it's not already active...
                hasMenu = true;
                point.x = LOWORD(lParam);
                point.y = HIWORD(lParam);
                ClientToScreen(hwnd, &point);
                // Add the "TPM_RETURNCMD" flag so we can reset "hasMenu" when a command is sselected (return non-zero):
                BOOL cmd = TrackPopupMenu(hMenu, TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hwnd, NULL);
                if (cmd) {
                    hasMenu = false;
                    SendMessage(hwnd, WM_COMMAND, cmd, 0); // Send the command (not done automatically with TPM_RETURNCMD)
                }
            }
            else {
                hasMenu = false;
                SendMessage(hwnd, WM_CANCELMODE, 0, 0);
            }
            break;
        case WM_DESTROY:
            DestroyMenu(hMenu); // Destroy the menu.
            PostQuitMessage(0);
            break;
    }
    return DefWindowProcW(hwnd, msg, wParam, lParam);
}