目錄
菜單在 .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);
程序源碼:
View Code
#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 資源內容
View Code
/***********************************************/ //主菜單 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; .....
程序源碼:
View Code
#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對象就設置為與第二個菜單項相關聯,依此順序進行,知道完成所有菜單項的處理.
