在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<Windows.h>#define IDM_FILE_OPEN 100#define IDM_FILE_NEW 101#define IDM_FILE_PIC 102#define IDM_FILE_EXIT 103#define IDM_MENU_ADD 104#define IDM_MENU_REM 105#define IDM_MENU_DEL 106#define IDM_MENU_MOD 107#define IDM_ABOUT 108#define IDM_VERSION 109#define IDM_MENU_NEW 110#define IDM_POP_ONE 200#define IDM_POP_TWO 201#define IDM_SYS_ONE 300#define IDM_SYS_TWO 301LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);HMENU createOwnMenu(HWND hwnd);HMENU createPopMenu(HWND hwnd);HACCEL createAccelerator(HWND hwnd);TCHAR* szAppName = TEXT("MenuClass");TCHAR* szWndName = TEXT("ChangeMenu");HACCEL hAccel = NULL;int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPreInstance,LPSTR lpCmdLine,int iCmdShow){ WNDCLASS wndCls; HWND hWnd; MSG msg; 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 AppendMenu AppendMenu(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;}HACCEL createAccelerator(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;}