Win32 API programming with C - Using menus

A menu is a list of items that specify options or groups of options (a submenu) for an application. Clicking a menu item opens a submenu or causes the application to carry out a command.

A menu is arranged in a hierarchy. At the top level of the hierarchy is the menu bar; which contains a list of menus, which in turn can contain submenus. A menu bar is sometimes called a top-level menu, and the menus and submenus are also known as pop-up menus.

A menu item can either carry out a command or open a submenu. An item that carries out a command is called a command item or a command.

Let's define two IDs for our menu commands.

#define ID_FILE_ABOUT 1
#define ID_FILE_EXIT  2

And we'll write this function to add a menu to our window.

void AddMenu(HWND hwnd) {
    // create menu bar
    HMENU hMenubar = CreateMenu();
    // main menu
    HMENU hMenu = CreateMenu();

    // menu items
    AppendMenu(hMenu, MF_STRING, ID_FILE_ABOUT, "About");
    AppendMenu(hMenu, MF_STRING, ID_FILE_EXIT, "Exit");
    AppendMenu(hMenubar, MF_POPUP, (UINT_PTR)hMenu, "File");

    // attach menu bar to the window
    SetMenu(hwnd, hMenubar);
}

We call this function just before we show the window and we pass the window handle.

AddMenu(hwnd);
ShowWindow(hwnd, nCmdShow);

In order to process menu commands we have to handle the WM_COMMAND message inside our Window Procedure. The wParam parameter contains our menu command ID that we defined earlier.

switch (msg)
{
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
            case ID_FILE_ABOUT:
                MessageBox(hwnd, "About menu item clicked", "Notice", MB_OK | MB_ICONINFORMATION);
                break;
            case ID_FILE_EXIT:
                DestroyWindow(hwnd);
                break;
            }
        break;
    // ...
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
return 0;

Full implementation below:

#include <windows.h>

#define ID_FILE_ABOUT 1
#define ID_FILE_EXIT  2

// global variables
const char g_szClassName[] = "myWindowClass";

// function declarations
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
void AddMenu(HWND);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG msg;

    //Step 1: Register the Window Class
    wc.cbSize         = sizeof(WNDCLASSEX);                // The size, in bytes, of this structure
    wc.style         = 0;                                 // The class style(s)
    wc.lpfnWndProc     = WndProc;                           // A pointer to the window procedure.
    wc.cbClsExtra     = 0;                                 // The number of extra bytes to allocate following the window-class structure.
    wc.cbWndExtra     = 0;                                 // The number of extra bytes to allocate following the window instance.
    wc.hInstance     = hInstance;                         // A handle to the instance that contains the window procedure for the class.
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);   // A handle to the class icon. This member must be a handle to an icon resource. If this member is NULL, the system provides a default icon.
    wc.hCursor         = LoadCursor(NULL, IDC_ARROW);       // A handle to the class cursor. This member must be a handle to a cursor resource. If this member is NULL, an application must explicitly set the cursor shape whenever the mouse moves into the application's window.
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);          // A handle to the class background brush.
    wc.lpszMenuName  = NULL;                              // Pointer to a null-terminated character string that specifies the resource name of the class menu.
    wc.lpszClassName = g_szClassName;                     // A string that identifies the window class.
    wc.hIconSm         = LoadIcon(NULL, IDI_APPLICATION);   // A handle to a small icon that is associated with the window class.

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // Step 2: Creating the Window
    hwnd = CreateWindowEx(
        0,                      // Optional window styles.
        g_szClassName,          // Window class
        "My window",            // Window text
        WS_OVERLAPPEDWINDOW,    // Window style
        CW_USEDEFAULT,          // Position X
        CW_USEDEFAULT,          // Position Y
        800,                    // Width
        600,                    // Height
        NULL,                   // Parent window
        NULL,                   // Menu
        hInstance,              // Instance handle
        NULL                    // Additional application data
    );

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    AddMenu(hwnd);
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // Step 3: The Message Loop
    while(GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

// Step 4: the Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case ID_FILE_ABOUT:
                    MessageBox(hwnd, "About menu item clicked", "Notice", MB_OK | MB_ICONINFORMATION);
                    break;
                case ID_FILE_EXIT:
                    PostQuitMessage(0);
                    break;
                }    
            break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
        }
    return 0;
}

void AddMenu(HWND hwnd) {
    // create menu bar
    HMENU hMenubar = CreateMenu();
    // main menu
    HMENU hMenu = CreateMenu();

    // menu items
    AppendMenu(hMenu, MF_STRING, ID_FILE_ABOUT, "About");
    AppendMenu(hMenu, MF_STRING, ID_FILE_EXIT, "Exit");
    AppendMenu(hMenubar, MF_POPUP, (UINT_PTR)hMenu, "File");

    // attach menu bar to the window
    SetMenu(hwnd, hMenubar);
}