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);
}
Subscribe to my newsletter
Read articles from Ciprian Fusa directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Ciprian Fusa
Ciprian Fusa
I am a .NET Developer and Consultant with over 15 years of experience, I specialize in crafting scalable, high-performance applications that drive business growth and innovation.