# Windows API 菜单 > 原文: [http://zetcode.com/gui/winapi/menus/](http://zetcode.com/gui/winapi/menus/) 在 Windows API 教程的这一部分中,我们创建菜单。菜单是位于菜单栏中的一组命令。菜单栏包含菜单列表。 菜单可以包含菜单项或其他菜单调用子菜单。 执行命令的菜单项称为命令项或命令。 在 Windows 上,菜单栏有时称为顶层菜单。 菜单和子菜单称为弹出菜单。 菜单项通常分为一些逻辑组。 这些组由分隔符分隔。 分隔符是一条小的水平线。 ## 一个简单的菜单 在下面的示例中,我们创建一个菜单栏和三个菜单命令。 我们还创建一个分隔符。 `simplemenu.c` ```c #include LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void AddMenus(HWND); #define IDM_FILE_NEW 1 #define IDM_FILE_OPEN 2 #define IDM_FILE_QUIT 3 int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.lpszClassName = L"Simple menu"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Simple menu", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 350, 250, 0, 0, hInstance, 0); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_CREATE: AddMenus(hwnd); break; 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_DESTROY: PostQuitMessage(0); break; } return DefWindowProcW(hwnd, msg, wParam, lParam); } void AddMenus(HWND hwnd) { HMENU hMenubar; HMENU hMenu; hMenubar = CreateMenu(); hMenu = CreateMenu(); 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"); AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR) hMenu, L"&File"); SetMenu(hwnd, hMenubar); } ``` 两个菜单项发出短促的声音。 第三个终止应用。 ```c 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; ``` 如果选择菜单项,则窗口过程会收到`WM_COMMAND`消息。 菜单项 id 在`wParam`值的低位字中。 ```c hMenubar = CreateMenu(); hMenu = CreateMenu(); ``` 使用`CreateMenu()`函数创建菜单栏和菜单。 ```c 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"); AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR) hMenu, L"&File"); ``` 菜单项和子菜单是使用`AppendMenuW()`函数创建的。 我们要附加的内容取决于标志。 `MF_STRING`附加标签,`MF_SEPARATOR`附加分隔符,`MF_POPUP`附加菜单。 ```c SetMenu(hwnd, hMenubar); ``` 最后,我们设置菜单栏,调用`SetMenu()`函数。 ![A menu example](img/39f8bef8c509ee59e9afc3d7feb0e767.jpg) 图:简单菜单 ## 弹出菜单 弹出菜单也称为上下文菜单。 它是在某些情况下显示的命令列表。 例如,在 Firefox Web 浏览器中,当我们右键单击网页时,将获得一个上下文菜单。 在这里,我们可以重新加载页面,返回页面或查看页面源。 如果右键单击工具栏,则将获得另一个用于管理工具栏的上下文菜单。 `popupmenu.c` ```c #include LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); #define IDM_FILE_NEW 1 #define IDM_FILE_OPEN 2 #define IDM_FILE_QUIT 3 int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.lpszClassName = L"Popup menu"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Popup menu", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 350, 250, 0, 0, hInstance, 0); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HMENU hMenu; 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_RBUTTONUP: point.x = LOWORD(lParam); point.y = HIWORD(lParam); hMenu = CreatePopupMenu(); ClientToScreen(hwnd, &point); 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"); TrackPopupMenu(hMenu, TPM_RIGHTBUTTON, point.x, point.y, 0, hwnd, NULL); DestroyMenu(hMenu); break; case WM_DESTROY: PostQuitMessage(0); break; } return DefWindowProcW(hwnd, msg, wParam, lParam); } ``` 我们有一个上下文菜单示例,其中包含三个菜单项。 ```c case WM_RBUTTONUP: point.x = LOWORD(lParam); point.y = HIWORD(lParam); ... ``` 当光标在窗口的客户区域中时,用户释放鼠标右键时,将发布`WM_RBUTTONUP`消息。 `lParam`的低位字指定光标的 x 坐标。 高阶字指定光标的 y 坐标。 坐标相对于客户区域的左上角。 ```c hMenu = CreatePopupMenu(); ``` `CreatePopupMenu()`函数创建一个弹出菜单。 它将句柄返回到新创建的菜单。 菜单最初是空的。 ```c ClientToScreen(hwnd, &point); ``` `ClientToScreen()`函数将指定点的客户坐标转换为屏幕坐标。 我们需要这些坐标才能显示上下文菜单。 ```c 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"); ``` 创建三个菜单项和一个分隔符。 ```c TrackPopupMenu(hMenu, TPM_RIGHTBUTTON, point.x, point.y, 0, hwnd, NULL); ``` `TrackPopupMenu()`函数在指定位置显示上下文菜单,并跟踪菜单上项目的选择。 ```c DestroyMenu(hMenu); ``` 最后,使用`DestroyMenu()`函数销毁菜单对象。 未分配给窗口的菜单必须被明确销毁。 ![A popup menu](img/83e5ddd2cfb4635e3f3de9ec62bccce4.jpg) 图:弹出菜单 ## 复选菜单项 复选菜单项是在标签前带有复选标记的菜单项。 菜单项可以使用`CheckMenuItem()`函数选中或取消选中。 `checkmenuitem.c` ```c #include #include LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void AddMenus(HWND); #define IDM_VIEW_STB 1 HWND ghSb; HMENU ghMenu; int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.lpszClassName = L"Check menu item"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Check menu item", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 350, 250, 0, 0, hInstance, 0); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { UINT state; switch(msg) { case WM_CREATE: AddMenus(hwnd); InitCommonControls(); ghSb = CreateWindowExW(0, STATUSCLASSNAMEW, NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU) 1, GetModuleHandle(NULL), NULL); break; case WM_COMMAND: switch(LOWORD(wParam)) { case IDM_VIEW_STB: state = GetMenuState(ghMenu, IDM_VIEW_STB, MF_BYCOMMAND); if (state == MF_CHECKED) { ShowWindow(ghSb, SW_HIDE); CheckMenuItem(ghMenu, IDM_VIEW_STB, MF_UNCHECKED); } else { ShowWindow(ghSb, SW_SHOWNA); CheckMenuItem(ghMenu, IDM_VIEW_STB, MF_CHECKED); } break; } break; case WM_SIZE: SendMessage(ghSb, WM_SIZE, wParam, lParam); break; case WM_DESTROY: PostQuitMessage(0); break; } return DefWindowProcW(hwnd, msg, wParam, lParam); } void AddMenus(HWND hwnd) { HMENU hMenubar; hMenubar = CreateMenu(); ghMenu = CreateMenu(); AppendMenuW(ghMenu, MF_STRING, IDM_VIEW_STB, L"&Statusbar"); CheckMenuItem(ghMenu, IDM_VIEW_STB, MF_CHECKED); AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR) ghMenu, L"&View"); SetMenu(hwnd, hMenubar); } ``` 在示例中,我们有一个包含一个菜单项的视图菜单。 该菜单项将显示或隐藏状态栏。 当状态栏可见时,将选中菜单项。 ```c #define IDM_VIEW_STB 1 ``` 这是将显示或隐藏状态栏的菜单项的 ID。 ```c InitCommonControls(); ``` 状态栏是常用控件。 必须使用`InitCommonControls()`函数对其进行初始化。 ```c ghSb = CreateWindowExW(0, STATUSCLASSNAMEW, NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU) 1, GetModuleHandle(NULL), NULL); ``` 此代码行创建一个状态栏控件。 ```c state = GetMenuState(ghMenu, IDM_VIEW_STB, MF_BYCOMMAND); ``` 我们通过`GetMenuState()`函数获得状态栏菜单项的状态。 ```c if (state == MF_CHECKED) { ShowWindow(ghSb, SW_HIDE); CheckMenuItem(ghMenu, IDM_VIEW_STB, MF_UNCHECKED); } else { ShowWindow(ghSb, SW_SHOWNA); CheckMenuItem(ghMenu, IDM_VIEW_STB, MF_CHECKED); } ``` 根据其状态,我们使用`ShowWindow()`函数显示或隐藏状态栏控件。 菜单项通过`CheckMenuItem()`函数被选中或取消选中。 ```c case WM_SIZE: SendMessage(ghSb, WM_SIZE, wParam, lParam); break; ``` 调整窗口大小后,我们将调整状态栏的大小以适合窗口。 ![A check menu item](img/7c97e075fbf2061d9152bc207f64b507.jpg) 图:复选菜单项 ## 单选菜单项 单选菜单项使您可以从互斥的选项列表中进行选择。 单选菜单项通过`CheckMenuRadioItem()`函数进行管理。 `radiomenuitem.c` ```c #include LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void AddMenus(HWND); #define IDM_MODE_MAP 1 #define IDM_MODE_SAT 2 #define IDM_MODE_TRA 3 #define IDM_MODE_STR 4 HMENU hMenu; int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.lpszClassName = L"Radio menu item"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Radio menu item", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 350, 250, 0, 0, hInstance, 0); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_CREATE: AddMenus(hwnd); break; case WM_COMMAND: switch(LOWORD(wParam)) { case IDM_MODE_MAP: CheckMenuRadioItem(hMenu, IDM_MODE_MAP, IDM_MODE_STR, IDM_MODE_MAP, MF_BYCOMMAND); MessageBeep(MB_ICONERROR); break; case IDM_MODE_SAT: CheckMenuRadioItem(hMenu, IDM_MODE_MAP, IDM_MODE_STR, IDM_MODE_SAT, MF_BYCOMMAND); MessageBeep(0xFFFFFFFF); break; case IDM_MODE_TRA: CheckMenuRadioItem(hMenu, IDM_MODE_MAP, IDM_MODE_STR, IDM_MODE_TRA, MF_BYCOMMAND); MessageBeep(MB_ICONWARNING); break; case IDM_MODE_STR: CheckMenuRadioItem(hMenu, IDM_MODE_MAP, IDM_MODE_STR, IDM_MODE_STR, MF_BYCOMMAND); MessageBeep(MB_ICONINFORMATION); break; } break; case WM_DESTROY: PostQuitMessage(0); break; } return DefWindowProcW(hwnd, msg, wParam, lParam); } void AddMenus(HWND hwnd) { HMENU hMenubar; hMenubar = CreateMenu(); hMenu = CreateMenu(); AppendMenuW(hMenu, MF_STRING, IDM_MODE_MAP, L"&Map"); AppendMenuW(hMenu, MF_STRING, IDM_MODE_SAT, L"&Satellite"); AppendMenuW(hMenu, MF_STRING, IDM_MODE_TRA, L"&Traffic"); AppendMenuW(hMenu, MF_STRING, IDM_MODE_STR, L"Street &view"); CheckMenuRadioItem(hMenu, IDM_MODE_MAP, IDM_MODE_STR, IDM_MODE_MAP, MF_BYCOMMAND); AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR) hMenu, L"&Map mode"); SetMenu(hwnd, hMenubar); } ``` 在示例中,我们有四个单选菜单项; 一次只能选择其中之一。 每个单选菜单项都会发出不同的声音。 ```c #define IDM_MODE_MAP 1 #define IDM_MODE_SAT 2 #define IDM_MODE_TRA 3 #define IDM_MODE_STR 4 ``` 这些是单选菜单项的 ID。 ```c case IDM_MODE_MAP: CheckMenuRadioItem(hMenu, IDM_MODE_MAP, IDM_MODE_STR, IDM_MODE_MAP, MF_BYCOMMAND); MessageBeep(MB_ICONERROR); break; ``` `CheckMenuRadioItem()`检查指定的菜单项并将其设为单选项目。 此外,它清除关联的菜单项组中的所有其他菜单项。 该函数的第一个参数是包含菜单项组的菜单句柄。 最后一个参数指示前三个参数的含义; 当指定`MF_BYCOMMAND`时,这些参数是菜单项的 ID。 第二个参数是组中第一个菜单项的 ID,第三个参数是组中最后一个菜单项的 ID。 第四个参数是要检查的菜单标识符。 ```c ... AppendMenuW(hMenu, MF_STRING, IDM_MODE_STR, L"Street &view"); CheckMenuRadioItem(hMenu, IDM_MODE_MAP, IDM_MODE_STR, IDM_MODE_MAP, MF_BYCOMMAND); AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR) hMenu, L"&Map mode"); ... ``` 首先,选择第一个单选项目。 ![A radio menu item](img/6904cf2c9e454abf9201d386599d4b5d.jpg) 图:单选菜单项 ## 子菜单 子菜单是位于另一个菜单内的菜单。 `submenu.c` ```c #include LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void AddMenus(HWND); #define IDM_FILE_NEW 1 #define IDM_FILE_IMPORT 2 #define IDM_IMPORT_MAIL 11 int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.lpszClassName = L"Submenu"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Submenu", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 350, 250, 0, 0, hInstance, 0); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_CREATE: AddMenus(hwnd); break; case WM_COMMAND: switch(LOWORD(wParam)) { case IDM_FILE_NEW: MessageBoxW(NULL, L"New file selected", L"Information", MB_OK); break; case IDM_IMPORT_MAIL: MessageBoxW(NULL, L"Import mail selected", L"Information", MB_OK); } break; case WM_DESTROY: PostQuitMessage(0); break; } return DefWindowProcW(hwnd, msg, wParam, lParam); } void AddMenus(HWND hwnd) { HMENU hMenubar = CreateMenu(); HMENU hMenu = CreateMenu(); HMENU hSubMenu = CreatePopupMenu(); AppendMenuW(hMenu, MF_STRING, IDM_FILE_NEW, L"&New"); AppendMenuW(hMenu, MF_STRING | MF_POPUP, (UINT_PTR) hSubMenu, L"&Import"); AppendMenuW(hSubMenu, MF_STRING, IDM_IMPORT_MAIL, L"Import &mail"); AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR) hMenu, L"&File"); SetMenu(hwnd, hMenubar); } ``` 在示例中,我们有两个菜单项; 一个位于“文件”菜单中,另一个位于“文件的导入”子菜单中。 选择每个菜单项都会显示一个消息框。 ```c case IDM_IMPORT_MAIL: MessageBoxW(NULL, L"Import mail selected", L"Information", MB_OK); ``` 当我们选择“导入邮件”子菜单项时,将显示一个带有`"Import mail selected"`文本的消息框。 ```c HMENU hSubMenu = CreatePopupMenu(); ``` 使用`CreatePopupMenu()`函数创建一个子菜单。 ```c AppendMenuW(hMenu, MF_STRING | MF_POPUP, (UINT_PTR) hSubMenu, L"&Import"); ``` 通过`AppendMenuW()`函数,我们在文件菜单中添加了一个子菜单。 `MF_POPUP`标志用于弹出菜单和子菜单。 ```c AppendMenuW(hSubMenu, MF_STRING, IDM_IMPORT_MAIL, L"Import &mail"); ``` 通常使用`AppendMenuW()`函数将菜单项添加到子菜单。 ![Submenu](img/9a3dff66260320d751b2b1a9b5b87cf6.jpg) 图:子菜单 在 Windows API 教程的这一部分中,我们介绍了菜单。