目录
菜单在 .rc 文件中的格式
加载/卸载菜单
菜单常用的操作
创建弹出菜单
菜单加速键
MFC下菜单消息路由
(本章节中例子都是用 VS2005 编译调试的)
菜单在 .rc 文件中的格式
.rc 中的菜单格式
虽然现在微软的编译器中都会自动生成好用的 rc 资源但是还是可以了解下它内部代码的意义.
这里是不太建议直接在 .rc 文件中修改菜单因为修改了.rc 文件后还需在其他文件中修改对应地方,否则在编译中会报错.所以还是建议在编译器的资源管理器中修改对话框.
格式:
menuID MENU [,载入特性选项]
{
菜单项列表
}
说明:
- menuID: 菜单资源标识
- MEMU: 关键字
- 载入特性:
- DISCARDABLE 当不再需要菜单时候菜单可丢弃
- FIXED 将菜单保存在内存中固定位置
- LOADONCALL 需要时加载菜单
- MOVEABLE 菜单在内存中可移动
- PRELOAD 立即加载菜单
- 菜单项列表:
- 弹出菜单/子菜单(POPUP)
- 格式:
- POPUP"子菜单名"[,选项]
BEGIN
…(菜单项成员)
END
- POPUP"子菜单名"[,选项]
- 说明:
- POPUP: 关键字
- 子菜单名: "子菜单的名字&热键"
- BEGIN: 子菜单中菜单项开始的标识
- 选项:
- MENUBARBREAK 菜单项纵向分隔标识
- CHECKED 显示选中标识
- INACTIVE 禁止一个菜单项
- GRAYED 禁止一个菜单项并使其显示灰色
- 菜单项成员: 子菜单或菜单项(定义如下所示)
- END: 子菜单中菜单项结束的标识
- 格式:
- 菜单项(MENUITEM)
- 格式: MENUITEM "菜单项名",菜单项标识符(ID)[,选项]
- 说明:
- MENUITEM: 关键字
- 菜单项名: "菜单项名字&热键"
- 选项:
- MENUBARBREAK 菜单项纵向分隔标识
- CHECKED 显示选中标识
- INACTIVE 禁止一个菜单项
- GRAYED 禁止一个菜单项并使其显示灰色
- 弹出菜单/子菜单(POPUP)
菜单组成部分
- 主菜单栏
- 下拉式菜单框
- 菜单项热键标识
- 菜单项加速键标识
- 菜单项分割线(占据菜单索引)
加载/卸载菜单
加载菜单
在 win32 界面程序中加载菜单有以下几种方式:
- 在窗口类设计时候进行加载
在定义 WNDCLASS 时对成员 lpszMenuName 赋予相对应的值 - 在创建窗口时候进行加载
- 动态加载菜单
代码示例:
.rc 资源内容
IDR_MENU1 MENU BEGIN POPUP "菜单1" BEGIN POPUP "子菜单1.1" BEGIN MENUITEM "菜单项1.1.1", ID_40001 MENUITEM "菜单项1.2.1", ID_40002 END MENUITEM "菜单项1.2", ID_40003 MENUITEM SEPARATOR MENUITEM "菜单项1.3", ID_40004 MENUITEM "菜单项1.4", ID_40005 END POPUP "菜单2" BEGIN MENUITEM "菜单项2.1", ID_40006 MENUITEM "菜单项2.2", ID_40007 END END
加载菜单:
- 第一种加载方式(类设计时):
WNDCLASS wndclass; .... wndclass.lpszMenuName=MAKEINTRESOURCE(IDR_MENU1); //这里省略了窗体类创建时需要填写的其他信息.
- 第二种加载方式(窗体创建时):
HMENU hmenu; WNDCLASS wndclass; .... wndclass.lpszMenuName=NULL; //这里省略了一些窗体类的必要信息填写,和注册窗口类等操作 //加载菜单到菜单句柄中 hmenu=LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU1)); //在创建窗体时候载入菜单 hwnd=CreateWindow("text","hellow world",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT,NULL,hmenu,hInstance,NULL);
- 第三种加载方式(窗体创建后):
HMENU hmenu; WNDCLASS wndclass; .... wndclass.lpszMenuName=NULL; //这里省略了一些窗体类的必要信息填写,和注册窗口类等操作 //创建窗体 hwnd=CreateWindow("text","hellow world",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL); //加载菜单到菜单句柄中 hmenu=LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU1)); //动态的加载菜单到窗体中去 SetMenu(hwnd,hmenu);
程序源码:
#include<windows.h> #include"resource.h" LRESULT CALLBACK textprom( HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ); int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // pointer to command line int nCmdShow // show state of window ) { WNDCLASS wndclass; HWND hwnd; HMENU hmenu; MSG msg; //设计窗口类 wndclass.cbClsExtra=0; wndclass.cbWndExtra=0; wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.hCursor=LoadCursor(NULL,IDC_ARROW); wndclass.hIcon=LoadIcon(NULL,IDI_ERROR); wndclass.hInstance=hInstance; wndclass.lpfnWndProc=textprom; wndclass.lpszClassName="text"; wndclass.lpszMenuName=NULL; //wndclass.lpszMenuName=MAKEINTRESOURCE(IDR_MENU1); wndclass.style=CS_HREDRAW | CS_VREDRAW; //注册窗口类 if(!RegisterClass(&wndclass)) { MessageBox(NULL,"create windows error!","error",MB_OK | MB_ICONSTOP); } //创建无菜单资源的窗口窗口 hwnd=CreateWindow("text","hellow world",WS_DLGFRAME | WS_MINIMIZEBOX | WS_SYSMENU,0,0, 500,300,NULL,NULL,hInstance,NULL); /* //载入菜单资源 hmenu=LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU1)); //创建有菜单资源的窗口 hwnd=CreateWindow("text","hellow world",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT,NULL,hmenu,hInstance,NULL);*/ //载入菜单资源,并在窗口加载菜单资源 hmenu=LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU1)); SetMenu(hwnd,hmenu); //显示更新窗口 ShowWindow(hwnd,nCmdShow); UpdateWindow(hwnd); //消息循环 while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK textprom( HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ) { switch(uMsg) { case WM_DESTROY: PostQuitMessage(0); break; } return DefWindowProc(hwnd,uMsg,wParam,lParam); }
运行结果:
卸载菜单
步骤:
代码示例:
HMENU hmenu; switch(uMsg) { //添加鼠标右键单击事件响应处理,即卸载菜单 case WM_LBUTTONDOWN: hmenu = GetMenu(hwnd); if(hmenu != NULL) { SetMenu(hwnd,NULL); DestroyMenu(hmenu); } break; .....
运行结果:
单击鼠标右键后
菜单常用的操作
菜单项常用操作
- 设置默认菜单项 SetMenuDefaultItem
- 禁止或激活菜单项 EnableMenuItem
- 设置默认菜单项 SetMenuDefaultItem
- 修改菜单项 ModifyMenu
- 设置或或取消菜单项选择标志 CheckMenuItem
- 获得菜单项的 ID GetMenuItemID
- 获得菜单项的标志位 GetMenuState
- 获得菜单项的字符串 GetMenuString
菜单常用操作
- 获取子菜单句柄 GetSubMenu
- 获得窗口主菜单句柄 GetMenu
- 动态创建空的弹出菜单 CreateMenu
- 销毁动态创建空的弹出菜单 DestroyMenu
- 删除菜单项 DeleteMenu
- 在菜单中插入菜单项 InsertMenu
- 在菜单尾部增加菜单项 AppendMenu
- 获得弹出菜单的菜单子项的数目 GetMenuItemCounte
菜单索引
在编写菜单操作前还需了解个很重要的概念,即了解菜单的结构,只有了解了这个结构,才能找到对应的菜单,子菜单或菜单项进行对应的操作.因为在操作菜单时需要获得对应的菜单句柄(如在上图子菜单1.1进行插入菜单项1.3.1操作时要获得子菜单1.1句柄),或操作菜单项时要获得对应的子菜单项的菜单句柄(如让上图菜单项1.1.1禁用时候需要获得子菜单1.1的句柄).然而在获取子菜单句柄时需要子菜单索引,所以对索引号的一些规则必须要有一定的了解.
简单规则如下:
-
- 菜单索引基于0开始;
- 分隔符也算一个菜单项,所以他也占据一个索引号
即如下图所示:
两个简单样例:
- 在上图子菜单1.1进行插入菜单项1.3.1操作
//在程序源码的显示更新窗口 ShowWindow(hwnd,nCmdShow); UpdateWindow(hwnd); //在程序源码的显示更新窗口后插入 //插入菜单项 AppendMenu(GetSubMenu(GetSubMenu(GetMenu(hwnd),0),0),MF_ENABLED,40012,"菜单项1.1.3");
- 让上图菜单项1.1.1禁用:
//在程序源码的显示更新窗口 ShowWindow(hwnd,nCmdShow); UpdateWindow(hwnd); //在程序源码的显示更新窗口后插入 //插入菜单项 AppendMenu(GetSubMenu(GetSubMenu(GetMenu(hwnd),0),0),MF_ENABLED,40012,"菜单项1.1.3"); //禁用菜单项 EnableMenuItem(GetSubMenu(GetSubMenu(GetMenu(hwnd),0),0),0,MF_BYPOSITION | MF_GRAYED);
创建弹出菜单
步骤:
-
载入菜单资源
(用 GetSubMenu,非 LoadMenu 或 getMenu,因为后两种获得的菜单句柄都是主菜单的句柄,而主菜单句柄不适合用 TrackPopupMenu 显示弹出菜单,若用的是主菜单句柄作为弹出菜单句柄时候效果如下图所示) -
调用 TrackPopupMenu 显示弹出菜单
流程图如下:
代码示例:
.rc 资源内容
/***********************************************/ //主菜单 IDR_MENU1 MENU BEGIN POPUP "菜单1" BEGIN POPUP "子菜单1.1" BEGIN MENUITEM "菜单项1.1.1", ID_40001 MENUITEM "菜单项1.2.1", ID_40002 END MENUITEM "菜单项1.2", ID_40003 MENUITEM SEPARATOR MENUITEM "菜单项1.3", ID_40004 MENUITEM "菜单项1.4", ID_40005 END POPUP "菜单2" BEGIN MENUITEM "菜单项2.1", ID_40006 MENUITEM "菜单项2.2", ID_40007 END END /***********************************************/ //弹出菜单 IDR_MENU2 MENU BEGIN POPUP "弹出菜单" BEGIN MENUITEM "弹出菜单项1.1", ID_A_40012 MENUITEM "弹出菜单项1.2", ID_A_40013 END END
加载弹出菜单
hmenuPop=GetSubMenu(LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU2)),0);
显示弹出菜单
POINT p; switch(uMsg) { //添加鼠标左键单击事件响应处理,即显示弹出菜单 case WM_RBUTTONDOWN: p.x=LOWORD(lParam); p.y=HIWORD(lParam); //将窗口坐标转换成屏幕坐标 ClientToScreen(hwnd,&p); TrackPopupMenu(hmenuPop,TPM_LEFTALIGN | TPM_RIGHTBUTTON,p.x,p.y,0,hwnd,NULL); break; .....
程序运行结果(在鼠标右键单击后):
菜单加速键
.rc 中的菜单格式
格式:
- 加速键ID ACCELERATORS
BEGIN
键名,命令ID [,类型] [,选项]
…
END
说明:
- 加速键ID: 一个字符串或者是1~65535之间的数字
- ACCELERATORS: 关键字
- BEGIN: 关键字,表示加速键列表的开始
- 键名: 表示加速键对应的按钮,可以有3种方式定义
- “^字母”:表示Ctrl键加上字母键.
- “字母”:表示字母,这时类型必须指明是VIRTKEY
- 数值:表示ASCII码为该数值的字母,这时类型必须指明为ASCII
- 命令ID: 按下加速键后,Windows向程序发送的命令ID.如果想把加速键和菜单项关联起来,这里就是要关联期间项的命令ID
- 类型: 用来指定键的定义方式,可以是 VIRTKEY 和 ASCII,分别用来表示“键名”字段定义的是虚拟键还是ASCII码
- 选项: 可以是 Alt, Control 或 Shift 中的单个或多个,如果指定多个,则中间用逗号隔开,表示加速键是按键加上这些控制键的组合键.这些选项只能在类型是VIRTKEY的情况下才能使用
- END 关键字,表示加速键列表的结束
编写菜单资源加速键
编写步骤:
- 加载菜单加速键资源: LoadAccelerators
- 修改消息循环: (即在消息循环中先把消息派送给转换菜单加速键,然后在派送给转换消息最后分配消息,如下图所示)
代码样例(为菜单项1.4增加快捷键 Crt+Alt+K):
.rc资源:
IDR_ACCELERATOR1 ACCELERATORS BEGIN "K", ID_40009, VIRTKEY, CONTROL, ALT, NOINVERT END
加载菜单加速资源:
HACCEL haccel;
haccel=LoadAccelerators(hInstance,MAKEINTRESOURCE(IDR_ACCELERATOR1));
更改消息循环:
while(GetMessage(&msg,NULL,0,0)) { if(!TranslateAccelerator(hwnd,haccel,&msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }
添加菜单项1.4 响应事件:
switch(uMsg) { //添加鼠标左键单击事件响应处理,即卸载对话框 case WM_RBUTTONDOWN: p.x=LOWORD(lParam); p.y=HIWORD(lParam); //将窗口坐标转换成屏幕坐标 ClientToScreen(hwnd,&p); TrackPopupMenu(hmenuPop,TPM_LEFTALIGN | TPM_RIGHTBUTTON,p.x,p.y,0,hwnd,NULL); break; //添加菜单响应事件 case WM_COMMAND: switch(LOWORD(wParam)) { //添加菜单项1.4响应事件 case ID_40009: MessageBox(hwnd,"success!","test",MB_OK); break; } break; .....
程序源码:
#include<windows.h> #include"resource.h" HMENU hmenuPop;//弹出菜单句柄 LRESULT CALLBACK textprom( HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ); int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // pointer to command line int nCmdShow // show state of window ) { WNDCLASS wndclass; HWND hwnd; HMENU hmenu; MSG msg; HACCEL haccel; //设计窗口类 wndclass.cbClsExtra=0; wndclass.cbWndExtra=0; wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.hCursor=LoadCursor(NULL,IDC_ARROW); wndclass.hIcon=LoadIcon(NULL,IDI_ERROR); wndclass.hInstance=hInstance; wndclass.lpfnWndProc=textprom; wndclass.lpszClassName="text"; wndclass.lpszMenuName=NULL; //wndclass.lpszMenuName=MAKEINTRESOURCE(IDR_MENU1); wndclass.style=CS_HREDRAW | CS_VREDRAW; //注册窗口类 if(!RegisterClass(&wndclass)) MessageBox(NULL,"create windows error!","error",MB_OK | MB_ICONSTOP); //创建无菜单资源的窗口窗口 hwnd=CreateWindow("text","hellow world",WS_DLGFRAME | WS_MINIMIZEBOX | WS_SYSMENU,0,0,500,300,NULL,NULL,hInstance,NULL); /* //载入菜单资源 hmenu=LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU1)); //创建有菜单资源的窗口 hwnd=CreateWindow("text","hellow world",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT,NULL,hmenu,hInstance,NULL);*/ //载入菜单资源,并在窗口加载菜单资源 hmenu=LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU1)); SetMenu(hwnd,hmenu); //载入弹出菜单资源 hmenuPop=GetSubMenu(LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU2)),0); //hmenuPop=LoadMenu(hInstance,MAKEINTRESOURCE(IDR_MENU2)); //显示更新窗口 ShowWindow(hwnd,nCmdShow); UpdateWindow(hwnd); //加载菜单加速资源 haccel=LoadAccelerators(hInstance,MAKEINTRESOURCE(IDR_ACCELERATOR1)); //消息循环 while(GetMessage(&msg,NULL,0,0)) { if(!TranslateAccelerator(hwnd,haccel,&msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; } LRESULT CALLBACK textprom( HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ) { POINT p; switch(uMsg) { //添加鼠标左键单击事件响应处理,即卸载对话框 case WM_RBUTTONDOWN: p.x=LOWORD(lParam); p.y=HIWORD(lParam); //将窗口坐标转换成屏幕坐标 ClientToScreen(hwnd,&p); TrackPopupMenu(hmenuPop,TPM_LEFTALIGN | TPM_RIGHTBUTTON,p.x,p.y,0,hwnd,NULL); break; //添加菜响应事件 case WM_COMMAND: switch(LOWORD(wParam)) { //添加菜单项1.4响应事件 case ID_40009: MessageBox(hwnd,"success!","test",MB_OK); break; } break; case WM_DESTROY: PostQuitMessage(0); break; } return DefWindowProc(hwnd,uMsg,wParam,lParam); }
运行结果(在单击菜单项1.4或者按下Ctrl+Alt+K的组合键时):
MFC下菜单消息路由
菜单消息路由
Cview -> CDocument -> CFrameWnd -> CWinApp
设置自己的菜单消息机制
MFC为菜单提供了一种命令更新机制,所以程序运行时,根据此机制去判断哪个菜单可以使用,哪个菜单不能使用,然后显示其相应的状态.默认情况下,所有菜单项的更新都是由MFC的命令更新机制完成的,如果我们想自己更改菜单的状态,那就必须把m_bAutoMenuEnable变量设置为false,之后我们自己对菜单项状态更新才能起作用
MFC的菜单消息机制
但是利用MFC编程时候,菜单项状态的维护依赖于CN_UPDATE_COMMAND_UI消息,MFC在后台要做的工作是:当要显示菜单时,操作系统发出WM_INITMENUPOPUP消息,然后由程序窗口基类如CFrameWnd接管,它会创建一个CCmdUI对象,并与程序的第一个菜单项相关联,调用该对象的一个成员函数DoUpdate(),这个函数发出CN_UPDATE_COMMAND_UI消息,这条消息带有一个指向CCmdUI对象的指针.这时,系统会自动判断是否存在一个ON_UPDATE_COMMAND_UI宏去捕获这个菜单项消息.如果找到这样一个宏,就会调用相应的消息相应函数进行处理,在这个函数中,可以利用传递进来的CCmdUI对象去调用相应的函数,使该激活或禁用该菜单项.当更新王第一菜单项后,同一个CCmdUI对象就设置为与第二个菜单项相关联,依此顺序进行,知道完成所有菜单项的处理.