在Windows Application 中创建菜单过程如下所示:
1. 创建菜单资源
2. 应用程序装载菜单资源
3. 应用程序响应菜单事件消息
首先分清几个概念:
<1>“主菜单” 和 “顶层菜单” 是一个意思。
<2>主菜单中的项目叫做 “弹出菜单” 或者 “子菜单”。
<3>弹出菜单的项目能够是另外一个弹出菜单。
<4>菜单的状态:启用,禁用。无效化,无效化跟前两者的差别是灰色显示文字。
1. 创建菜单资源
在创建菜单资源的时候, 可以使用两种方式: 一是使用IDE编辑器来创建菜单; 二是使用人工的方式编辑.rc文件. 第一种方式在很多MFC教材中都有使用. 我在这里主要介绍人工方式来编辑菜单资源, 有助于大家了解使用IDE编程的资源.
如果你在*.rc资源脚本中创建一个菜单资源, 都必须创建一个*.H文件与之相对应, 用于解决资源的标示符的符号引用问题.。
但有一种意外,菜单名必须是符号,不能使名称字符串。 下面是在*.rc文件中常见的MEMU描述的基本语法:
MENU_NAME MENU DISCARDABLE{ POPUP “name” { MENUITEM “name”, MENU_ID}}
MENU_NAME可以是一个名称字符串或是一个符号, 关键字DISCARDABLE有点过时了但还是必须的. 菜单中的一级菜单定义都是以关键字POPUP开头的. 而菜单项是以MENUITEM定义的, 至于MENU_ID是在*.h文件中定义的. 如下例子:
// .RC资源文件中MainMenu MENU DISCARDABLE{POPUP "File" { MENUITEM"Open", MENU_FILE_ID_OPEN MENUITEM"Close", MENU_FILE_ID_CLOSE MENUITEM"Save", MENU_FILE_ID_SAVE MENUITEM"Exit", MENU_FILE_ID_EXIT } // end popup POPUP "Help" { MENUITEM"About", MENU_HELP_ABOUT } // end popup } // end top level menu // .H文件中// defines for the top level menu FILE#define MENU_FILE_ID_OPEN 1000#define MENU_FILE_ID_CLOSE 1001#define MENU_FILE_ID_SAVE 1002#define MENU_FILE_ID_EXIT 1003 // defines for the top level menu HELP#define MENU_HELP_ABOUT 2000在这里没有定义”MainMenu”, 这样可以通过字符串而不是ID来访问该菜单. 如果我向如下方式定义了该项的ID#define MainMenu 100
必须使用MAKEINTRESOURCE(MainMenu)或MAKEINTRESOURCE(100)来访问该菜单资源.
如果想设置热键或者快捷键的话, 可以使用连字号(&)来达到这个功能. 例如:
MENUITEM “E&xit”, MENU_FILE_ID_EXIT
使x成为热键, 而
POPUP “&File”
使F成为通过Alt+F使用的快捷键
2. 装载菜单
在windows类的定义中, 定义菜单的代码是:
WNDCLASSEX wnd;wnd.lpszMenuName = NULL; //默认情况下
只需要将它赋值为该菜单的名称:
wnd.lpszMenuName = “MainMenu”;
或
wnd.lpszMenuName = MAKEINTRESOURCE(MainMenu);
这样做的话, 创建的每个窗口都会有一个相同的菜单. 要解决该问题的话,可以在创建菜单过程中通过传递菜单句柄来将一个菜单指定给一个窗口. 通过LoadMenu()装载菜单资源, 如果成功的话会返回一个HMENU句柄.
LoadMenu()原型:HMENU LoadMenu(HINSTANCE hInstance, // handle of applicationinstance LPCTSTRlpMenuName); // menu name string or menu-resource identifier
通过标准的CreateWindow()函数, 将菜单”ManiMenu”装载到该菜单句柄参数中:
// create the windowhwnd = CreateWindowEx(NULL, WINDOW_CLASS_NAME, “Sound”,WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 400, 400, NULL,LoadMenu(hinstance, “MainMenu”), hinstance, NULL);
将菜单关联到窗口的最后一个方法是调用SetMenu()函数:
BOOL SetMenu( HWND hwnd, // handle of window to attach to HMENUhMenu); // handle of menu
SetMenu()直接将该菜单关联到一个窗口上, 这个新菜单将优先于任何以前关联的菜单.例如:
// …wnd.lpszMenuName = NULL; // …hwnd = CreateWindowEx(NULL, WINDOW_CLASS_NAME, “Sound”,WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 400, 400, NULL,NULL, // handle to menu ,note it’s null.hinstance, NULL); //…// load the menu resourceHMENU hMenu = LoadMenu(hinstance, “MainMenu”);// attach the menu to the windowSetMenu(hwnd, hMemu);
响应菜单事件的消息
在选中一个菜单项后放开鼠标时的消息传递,这指的是一个选择过程. 选择过程将一个WM_COMMAND消息传递到与该菜单关联的窗口的WinProc函数中. 指定的菜单项ID和其他各种数据存储在该消息的wparam和lparam中, 如下所示:
msg --- WM_COMMAND
LOWORD(lparam) ---- 发出消息的窗口句柄
wparam --- 选中的菜单项的ID
编译可能出现的错误:
DemoMenu.obj : error LNK2001: unresolved external symbol__imp__PlaySoundA@12
将winmm.lib添加到工程中, 过程: 工程->设置->连接->在工程选项中添加winmm.lib.
关于Menu的函数
关于菜单的操作从大体方向上看无外乎增删改查四种操作。
4.1 HMENUCreateMenu(VOID);
4.2 BOOL AppendMenu( HMENU hMenu, // handle to menu
UINT uFlags, //menu-item options
UINT_PTR uIDNewItem, // identifier, menu, or submenu
LPCTSTR lpNewItem //menu-item content);
4.3 BOOLInsertMenu( HMENU hMenu, // handle to menu
UINT uPosition, // item that new item precedes
UINT uFlags, // options
UINT_PTR uIDNewItem, // identifier, menu, or submenu
LPCTSTR lpNewItem // menu item content);
4.4 BOOLInsertMenuItem( HMENU hMenu, // handle to menu
UINT uItem, // identifier or position
BOOL fByPosition, // meaning of uItem
LPCMENUITEMINFO lpmii // menu item information);
右键菜单
右键菜单跟一般的菜单没什么差别。仅仅是弹出的时候。须要用到这个函数
BOOLTrackPopupMenu( HMENU hMenu, // handle to shortcut menu UINT uFlags, // options int x, // horizontal position int y, // vertical position int nReserved, // reserved, must be zero HWND hWnd, // handle to owner window CONST RECT *prcRect // ignored);
当中的uFlags參数指定了菜单中菜单中的对齐方式,左键右键选定菜单项
TPM_CENTERALIGN。TMP_LEFTALIGN。TMP_RIGHTALIGN
TPM_BOTTOMALIGN, TPM_TOPALIGN, TPM_VCENTERALIGN
TPM_LEFTBUTTPON,TPM_RIGHTBUTTON
TPM_NONOTIFY, TPM_RETURNCMD
系统菜单
获取系统菜单句柄
HMENU GetSystemMenu(
HWNDhWnd, // handle to window
BOOL bRevert // reset option);
当中bRevert = FALSE时候。表示。复制系统的菜单,而且返回复制菜单的句柄,这个菜单能够进行改动。当bRevert = TRUE 时,设置系统菜单为默认的原始状态。而且函数返回值是NULL.
加速键
加速键也是一种资源,它能够使用快捷键迅速的打开命令项。加速键能够在在资源中自己加入,也能够直接使用程序编写。
while(GetMessage(&msg,NULL,0,0)){if (!TranslateAccelerator(hWnd,hAccel,&msg)){TranslateMessage(&msg);DispatchMessage(&msg);}}
在这里 TranslateAccelerator函数把 一些按键消息翻译成WM_COMMAND,或者WM_SYSCOMMAND消息。
代码演示样例
一下演示样例是一个全然依靠code不依赖创建资源的菜单demo,当中包括菜单条。
右键菜单,系统菜单,加速键能够对菜单进行动态的改动,删除。加入操作。
#define OEMRESOURCE# include#define IDM_FILE_OPEN100#define IDM_FILE_NEW101#define IDM_FILE_PIC102#define IDM_FILE_EXIT103#define IDM_MENU_ADD104#defineIDM_MENU_REM105#define IDM_MENU_DEL106#define IDM_MENU_MOD107#defineIDM_ABOUT108#define IDM_VERSION109#define IDM_MENU_NEW110#defineIDM_POP_ONE200#define IDM_POP_TWO201#define IDM_SYS_ONE300#define IDM_SYS_TWO301LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);HMENUcreateOwnMenu(HWND hwnd);HMENUcreatePopMenu(HWND hwnd);HACCELcreateAccelerator(HWND hwnd);TCHAR* szAppName = TEXT("MenuClass");TCHAR* szWndName = TEXT("ChangeMenu");HACCELhAccel = NULL;int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPreInstance,LPSTR lpCmdLine,int iCmdShow){WNDCLASSwndCls;HWNDhWnd;MSGmsg;wndCls.cbClsExtra = 0;wndCls.cbWndExtra = 0;wndCls.lpfnWndProc = WndProc;wndCls.style = CS_HREDRAW | CS_VREDRAW;wndCls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);wndCls.hCursor = LoadCursor(NULL,IDC_ARROW);wndCls.hIcon = LoadIcon(NULL,IDI_APPLICATION);wndCls.hInstance = hInstance;wndCls.lpszClassName = szAppName;wndCls.lpszMenuName = NULL;if(!RegisterClass(&wndCls)) {MessageBox(NULL,TEXT("Register window failed"),TEXT("Error"),MB_OK);}hWnd = CreateWindow(szAppName,szWndName,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL);UpdateWindow(hWnd);ShowWindow(hWnd,SW_NORMAL);while(GetMessage(&msg,NULL,0,0)){if(!TranslateAccelerator(hWnd,hAccel,&msg))TranslateMessage(&msg);DispatchMessage(&msg);}return msg.wParam;}LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam){static int nAdd = 1;HMENU hMenu,hSonMenu,hTmpMenu;HMENU hTrack,hSon;HMENU hSysMenu;POINT pt;switch(msg){case WM_CREATE:hMenu = createOwnMenu(hwnd);SetMenu(hwnd,hMenu);hSysMenu = GetSystemMenu(hwnd,FALSE);AppendMenu(hSysMenu,MF_SEPARATOR,0,NULL);AppendMenu(hSysMenu,MF_STRING,IDM_SYS_ONE,TEXT("SysOne"));AppendMenu(hSysMenu,MF_STRING,IDM_SYS_TWO,TEXT("SysTwo"));hAccel = createAccelerator(hwnd);break;case WM_SYSCOMMAND:switch(LOWORD(wParam)){case IDM_SYS_ONE:MessageBox(NULL,TEXT("This is added system menu item1"),NULL,MB_OK);break;case IDM_SYS_TWO:MessageBox(NULL,TEXT("This is added system menu item2"),NULL,MB_OK);break;}break;case WM_COMMAND:hMenu = GetMenu(hwnd);switch(LOWORD(wParam)){case IDM_FILE_OPEN:MessageBox(NULL,TEXT("File Open selected"),TEXT("Test check"),MB_OK);break;case IDM_MENU_ADD:hTmpMenu = GetSubMenu(hMenu,2);AppendMenu(hTmpMenu,MF_STRING,IDM_MENU_NEW+nAdd,TEXT("New Item"));nAdd++;DrawMenuBar(hwnd);break;case IDM_MENU_REM:hTmpMenu = GetSubMenu(hMenu,2);if( nAdd >1 ){nAdd--;RemoveMenu(hTmpMenu,IDM_MENU_NEW+nAdd,MF_BYCOMMAND);}// 当菜单项时弹出菜单的时候,只切断弹出菜单跟所属菜单的联系,但不销毁对象/*GetSubMenu(hTmpMenu,2);RemoveMenu(hTmpMenu,2,MF_BYPOSITION);*/DrawMenuBar(hwnd);break;case IDM_MENU_MOD:hTmpMenu = GetSubMenu(hMenu,2);ModifyMenu(hTmpMenu,0,MF_BYPOSITION,IDM_ABOUT,TEXT("Modified Item"));DrawMenuBar(hwnd);break;case IDM_MENU_DEL:hTmpMenu = GetSubMenu(hMenu,2);DeleteMenu(hTmpMenu,2,MF_BYPOSITION);DrawMenuBar(hwnd);break;}break;case WM_RBUTTONDOWN:hTrack= createPopMenu(hwnd);hSon= GetSubMenu(hTrack,0);pt.x = LOWORD(lParam);pt.y = HIWORD(lParam);ClientToScreen(hwnd,&pt);TrackPopupMenu(hSon,TPM_LEFTBUTTON| TPM_RIGHTALIGN,pt.x,pt.y,0,hwnd,NULL);break;case WM_DESTROY:PostQuitMessage(0);break;}return DefWindowProc(hwnd,msg,wParam,lParam);}HMENU createOwnMenu(HWND hwnd){HMENU hMenu= CreateMenu();HMENU hPopMenu= CreateMenu();//MF_BYPOSIOTION,MF_BYCOMMAND is not useful here in AppendMenuAppendMenu(hPopMenu,MF_STRING|MF_CHECKED|MF_GRAYED,IDM_FILE_OPEN,TEXT("&Open"));InsertMenu(hPopMenu,0,MF_BYPOSITION|MF_STRING|MF_DISABLED,IDM_FILE_NEW,TEXT("&New"));HINSTANCE hInstance = (HINSTANCE)GetWindowLong(hwnd,GWL_HINSTANCE);HBITMAP hBmp = (HBITMAP)LoadImage(hInstance,TEXT("bitmap1.bmp"),IMAGE_BITMAP,0,0,LR_LOADFROMFILE);AppendMenu(hPopMenu,MF_BITMAP,IDM_FILE_PIC,(LPCWSTR)hBmp);AppendMenu(hPopMenu,MF_SEPARATOR,NULL,NULL);AppendMenu(hPopMenu,MF_STRING,IDM_FILE_EXIT,TEXT("&Exit"));SetMenuItemBitmaps(hPopMenu,IDM_FILE_PIC,MF_BYCOMMAND,hBmp,hBmp);AppendMenu(hMenu,MF_POPUP,(UINT_PTR)hPopMenu,TEXT("File"));hPopMenu = CreateMenu();AppendMenu(hPopMenu,MF_STRING,IDM_MENU_ADD,TEXT("&Add"));AppendMenu(hPopMenu,MF_STRING,IDM_MENU_REM,TEXT("&Remove"));AppendMenu(hPopMenu,MF_STRING,IDM_MENU_MOD,TEXT("&Modify"));AppendMenu(hPopMenu,MF_STRING,IDM_MENU_DEL,TEXT("&Delete"));AppendMenu(hMenu,MF_POPUP,(UINT_PTR)hPopMenu,TEXT("Menu"));hPopMenu = CreateMenu();AppendMenu(hPopMenu,MF_STRING,IDM_ABOUT,TEXT("About"));AppendMenu(hPopMenu,MF_STRING,IDM_VERSION,TEXT("Version"));HMENU hSonMenu = CreateMenu();AppendMenu(hSonMenu,MF_STRING,IDM_MENU_NEW,TEXT("Son"));AppendMenu(hPopMenu,MF_POPUP,(UINT_PTR)hSonMenu,TEXT("Son Menu"));AppendMenu(hMenu,MF_POPUP,(UINT_PTR)hPopMenu,TEXT("Change"));return hMenu;}HMENU createPopMenu(HWND hwnd){HMENU hMenu = CreateMenu();HMENU hPop= CreateMenu();AppendMenu(hPop,MF_STRING,IDM_POP_ONE,TEXT("One"));AppendMenu(hPop,MF_STRING,IDM_POP_TWO,TEXT("Twod"));AppendMenu(hMenu,MF_POPUP,(UINT_PTR)hPop,TEXT("Pop"));return hMenu;}HACCELcreateAccelerator(HWND hwnd){ACCEL acce[2];acce[0].fVirt = FCONTROL | FNOINVERT | FVIRTKEY;acce[0].key = 'A';acce[0].cmd = IDM_MENU_ADD;acce[1].fVirt = FCONTROL | FNOINVERT | FVIRTKEY;acce[1].key = 'R';acce[1].cmd = IDM_MENU_REM;HACCEL hAccel = CreateAcceleratorTable(acce,2);return hAccel;}