上一篇我们学习了利用windows API创建工具栏和菜单栏,与上一篇紧密联系的就是菜单栏,菜单栏是一个大多数复杂一些的Windows应用程序不可或缺的部分。比如下图就是Windows自带的记事本的菜单栏:
菜单一般都是在标题栏下,工具栏以上,常常叫主菜单或顶级菜单(top-level menu),顶级菜单可能还会有弹出菜单(popup menu)或子菜单(submenu)。弹出菜单还有被“选中”(checked)状态,各菜单还有启用、禁用状态。
每一个菜单都有一个ID与之对应,当某个菜单被点击是,程序在WM_COMMAND消息中把菜单ID传给应该消息处理函数,就能知道哪个菜单被按下。
菜单栏的创建最常见是利用VS的菜单资源编辑器,然后加载该资源。比如下面的代码片段在创建主窗体时使用了LoadMenu函数加载菜单资源编辑的菜单:
hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(ID_MENU)); hWnd = CreateWindow(TEXT(“myclass”), TEXT(“mytitle”), WS_OVERLAPPENDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, hMenu, hInstance, NULL);
另外一种办法是在处理WM_CREATE消息是调用SetMenu函数来设置菜单:
SetMenu(hWnd, hMenu);
本文一直秉承一个原则,就是采用API的方式来创建而不是资源。因为用API来创建虽然麻烦一点,但是更加独立,比如如果不是用VS环境,那就可能没有资源编辑了,要是把我们的源程序在非VS的环境下编译就能显示出通用性和可移植性了。
- 菜单消息
当用户选择一个菜单时,会产生WM_INITMENU消息和WM_MENUSELECT,WM_INITMENU消息使得我们有机会在菜单的选中之前做一些事情,而WM_MENUSELECT消息是在菜单被选中或者光标移到该菜单时被发送,我们可以利用这个消息进行菜单选中时的处理。
WM_INITMENUPOPUP消息在一个弹出菜单显示前发送,可以用来修改一些菜单显示。
最重要、最常用的就是上面我们提到的WM_COMMAND消息,当菜单被点击时就会产生这个消息。上面的消息对应的参数意义请参考MSDN。
- 菜单创建
菜单相关的API有好几十个,我们这里只用一些常用的API函数,这几个函数基本可以完成菜单的基本功能,更多的菜单函数和功能的请参考MSDN。
函数CreateMenu可以创建一个菜单,CreatePopupMenu创建一个下拉式或弹出是菜单。函数AppendMenu可以追加一个菜单项,函数InsertMenu可以插入一个菜单项,TrackPopupMenu函数将在指定的位置显示一个弹出菜单。这几个菜单原型如下:
HMENU CreateMenu(VOID); HMENU CreatePopupMenu(VOID); BOOL AppendMenu(HMENU hMenu, UINT uFlags, UINT_PTR uIDNewItem, LPCTSTR lpNewItem); BOOL InsertMenu(HMENU hMenu, UINT uPosition, UINT uFlags, PTR uIDNewItem, LPCTSTR lpNewItem); BOOL TrackPopupMenu(HMENU hMenu, UINT uFlags, int x, int y, int nReserved, HWND hWnd, HWND prcRect);
其实菜单的常用部分大都是用这几个函数完成的,并不复杂。不说了,直接一边上代码一边解释更直接,我们通过以下demo演示运用这几个常见的函数来创建和使用菜单:
#include <windows.h> #define IDM_FILE_NEW 1001 #define IDM_FILE_OPEN 1002 #define IDM_FILE_SAVE 1003 #define IDM_EDIT_COPY 1004 #define IDM_EDIT_PASTE 1005 #define IDM_EDIT_HL 1006 #define IDM_VIEW_FULL 1007 #define IDM_VIEW_HALF 1008 #define IDM_VIEW_PART 1009 #define IDM_FILE_OPEN_SOLUTION 10021 #define IDM_FILE_OPEN_PROJECT 10022 static TCHAR szAppName[] = TEXT("Menubar"); static LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HWND hWnd; MSG msg; WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; if (!RegisterClass(&wndclass)) { MessageBox (NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR); return 0; } hWnd = CreateWindow(szAppName, // window class name szAppName, // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, // initial x position CW_USEDEFAULT, // initial y position 400, // initial x size 300, // initial y size NULL, // parent window handle NULL, // window menu handle hInstance, // program instance handle NULL); // creation parameters ShowWindow(hWnd, iCmdShow); UpdateWindow(hWnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } HMENU CreateMenuBar(void) { //总菜单 HMENU hMenu = CreateMenu(); //文件菜单 HMENU hFileMenu = CreateMenu(); AppendMenu(hFileMenu, MF_STRING, IDM_FILE_NEW, TEXT("&New")); AppendMenu(hFileMenu, MF_SEPARATOR, 0, NULL); //插入一条横条,请看运行效果 AppendMenu(hFileMenu, MF_STRING, IDM_FILE_SAVE, TEXT("&Save")); AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hFileMenu, TEXT("File(&F)")); //文件的二级子菜单 HMENU hSubMenu = CreateMenu(); AppendMenu(hSubMenu, MF_STRING, IDM_FILE_OPEN_SOLUTION, TEXT("So&lution")); AppendMenu(hSubMenu, MF_STRING, IDM_FILE_OPEN_PROJECT, TEXT("Pro&ject")); //将该二级菜单插入到第二条的位置 InsertMenu(hFileMenu, 1, MF_BYPOSITION|MF_POPUP, (UINT_PTR)hSubMenu, TEXT("Open")); //编辑菜单 hFileMenu = CreateMenu(); AppendMenu(hFileMenu, MF_STRING, IDM_EDIT_COPY, TEXT("&Copy")); AppendMenu(hFileMenu, MF_STRING, IDM_EDIT_PASTE, TEXT("&Paste")); AppendMenu(hFileMenu, MF_SEPARATOR, 0, NULL); AppendMenu(hFileMenu, MF_STRING|MF_CHECKED, IDM_EDIT_HL, TEXT("&Update"));//增加一个check选项 AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hFileMenu, TEXT("Edit(&E)")); //缩放菜单 hFileMenu = CreateMenu(); AppendMenu(hFileMenu, MF_STRING, IDM_VIEW_HALF, TEXT("&Half")); AppendMenu(hFileMenu, MF_SEPARATOR, 0, NULL); //设置一个灰色不可选的菜单,该菜单可以用EnableMenuItem函数修改可选状态 AppendMenu(hFileMenu, MF_STRING|MF_GRAYED, IDM_VIEW_PART, TEXT("P&art")); AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hFileMenu, TEXT("Zoom(&Z)")); return hMenu; } static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hDC; PAINTSTRUCT ps; switch (message) { case WM_CREATE: { HMENU hMenu = CreateMenuBar(); SetMenu(hWnd, hMenu); //以上只是创建了菜单,需要设置 } return 0; case WM_RBUTTONUP: { POINT point; point.x = LOWORD(lParam); point.y = HIWORD(lParam); ClientToScreen(hWnd, &point); //这里的坐标是相对于屏幕的,需要转换为客户坐标 HMENU hSubMenu = GetSubMenu(GetMenu(hWnd), 0); //获取菜单的第0个子菜单,用这个菜单来演示弹出菜单 TrackPopupMenu(hSubMenu, TPM_RIGHTBUTTON, point.x, point.y, 0, hWnd, NULL); } return 0; case WM_COMMAND: switch (LOWORD(wParam)) { case IDM_FILE_NEW: MessageBox(hWnd, TEXT("you click new file button"), TEXT("hint"), MB_OK); break; default: break; } return 0; case WM_PAINT: hDC = BeginPaint(hWnd, &ps); ; EndPaint(hWnd, &ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0 ; } return DefWindowProc (hWnd, message, wParam, lParam); }
本demo运行后点击“文件”菜单如下:
点击“编辑”菜单如下:
鼠标右键弹出快捷菜单:
文篇只演示了常用的菜单,其他比如位图菜单、非客户区弹出菜单等更多内容有兴趣在讨论,也可以参考MSDN的相关函数自己进行测试。本文的菜单栏编程结合上一篇的工具栏和状态栏内容以及第二篇的创建常用控件部分,基本可以完成窗口应用程序的界面编程了。当然再次强调,我们这些都是基于Windows API函数完成的,可能很多人会说,我用MFC,资源编辑器,对话框下的控件面板、甚至VB、C#都可以很快编写出这些界面。没错,但是隐藏在这些的下面还是会回到我们这些基本的API上。
更多经验交流可以加入Windows编程讨论QQ群:454398517。
关注微信公众平台:程序员互动联盟(coder_online),你可以第一时间获取原创技术文章,和(java/C/C++/Android/Windows/Linux)技术大牛做朋友,在线交流编程经验,获取编程基础知识,解决编程问题。程序员互动联盟,开发人员自己的家。
转载请注明出处http://www.coderonline.net/,谢谢合作!