Close popup menu programmatically on Windows

Viewed 272

I have a simple example for creating popup menu. I want to close this menu programmatically when pressing on button, is there any possibility to do that? Or maybe I should use different class?

I need to open this menu when button is pressed and close it the same way when pressing the button. Here is simple code example that I have.

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

Maybe the right way is to always create menu when button is pressed and destroy it right after that? But in that case we should create menu all the time when pressing the button. I was trying to create the menu once and show it when we need it. Also the menu is not shown right now. Trying to figure out why. Thanks in advance.

1 Answers

First, you can certainly avoid repeating the creation/destruction of your popup menu: just create it (once) on window creation (by handling the WM_CREATE message) and destroy it when handling WM_DESTROY. But note: you will need to make your hMenu a static variable, so that its value is maintained across multiple calls of your WndProc function.

As for hiding the menu programmatically – you can do this by sending the parent window the WM_CANCELMODE message, as suggested in this related question: How to CLOSE a context menu after a timeout?

You can keep a 'flag' variable (also must be static) to keep track of the current display status of the popup menu: if this is not shown, call TrackPopupMenu; if it is shown, send the WM_CANCELMODE message. You will also need to add the TPM_RETURNCMD flag in your call to TrackPopupMenu, so that you can reset the flag if/when the user selects a command from the menu.

Here's a version of your WndProc function that implements this approach:

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