帥哥們,美女們,下午好,我又來誤人子弟,請做好准備。
今天,我們的目的是,想要實現 下圖中的這種菜單效果。
就是一種類似單選按鈕的菜單,多個菜單項中,同時只有一個會被選中。
首先,我們 在資源編輯器中,設計一個菜單資源。這個資源編輯器在管理資源ID的時候,有些問題,有時候不同步 更新,有時候會保存不到,反正就會混亂。如果遇到問題,你可以先把菜單設計好,接著打開 resource.h,手動把這些ID和它的值改一下。為了使這三個菜單項能形成一個組,必須讓它們的ID值是 連續的,比如我這裡讓它們分別為501,502,503。
101指的是整個菜單資源,後三個都是子菜單項。如果想更保險的話,可以在【 解決方案資源管理器】中右擊資源文件(.rc結尾),選擇【查看代碼】,然後檢查一下是否正確就可 以了。
現在菜單弄好了,下面我們來了解一下把菜單添加到窗口的兩個類型。
第一種是類級別的, 也就是我們在設計窗口類時,直接指定給WNDCLASS結構的lpszMenuName成員,這樣做意味著,在調用 CreateWindow函數創建窗口時,無論你是否為窗口指定菜單,最終顯示的窗口上都會有菜單,因為它是 基於整個窗口類的。
// 在這裡把菜單附加上,成為類級別 wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAIN);//整個菜單資源的ID,不是菜單項
HWND hm = CreateWindow( L"MainWd", L"我的應用程序", WS_OVERLAPPEDWINDOW, 25, 18, 380, 280, NULL, NULL, hthisInstance, NULL);
這樣在我們創建窗口時,哪怕你把hMenu參數設為NULL,最後顯示的窗口都會有菜 單,因為菜單是屬於窗口類本身的。
另一種方式,就是不設置為類級別的菜單,而是在調用 CreateWindow時指定給hMenu參數。
HWND hm = CreateWindow( L"MainWd", L"我的應用程序", WS_OVERLAPPEDWINDOW, 25, 18, 380, 280, NULL, LoadMenu(hthisInstance,MAKEINTRESOURCE(IDR_MAIN)), hthisInstance, NULL);
同時我們把設計窗口類時設置菜單的代碼注釋掉。
// 在這裡把菜單附 加上,成為類級別 //wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAIN);//整個菜單資源的ID,不是菜單項
然後, 我們運行這個程序,它還是有菜單的。
接著,我們把CreateWindow的hMenu參數設置為NULL,
HWND hm = CreateWindow( L"MainWd", L"我的應用程序", WS_OVERLAPPEDWINDOW, 25, 18, 380, 280, NULL, /*LoadMenu(hthisInstance,MAKEINTRESOURCE(IDR_MAIN))*/ NULL, hthisInstance, NULL);
看看這時候運行程序,還能不能看到菜單。
現在就看不到菜單了,這兩種加載菜單的方式,就區別在這裡。
要為菜單實現單選標 記,調用CheckMenuRadioItem函數,第一個參數是要在其子項中設置的單選的菜單的句柄,第二個參數 和第三個參數指定合並為一個組的ID范圍,在這個范圍內的菜單項被看人為同一組,這一組中,每一次 只能有一項被checked,第四個參數就指定在這組項中哪一個被選中,最後一個參數決定是用ID來標識 還用從0開始的索引。
但是,我們在改變菜單項單選狀態前,必須獲得【水果】彈出菜單的句柄 。
我們先來看一下,一般菜單欄的層次結構。
它就像一個樹形結構,一層一層往下展開,上圖中,紅色矩形畫的部分是菜單的根,即整個 菜單欄,藍色矩形標注的是菜單欄的下一級,彈出菜單,如【文件】、【編輯】、【視圖】這些,它們 一般只負責彈出子項列表,自身不響應用戶選擇命令,這也是我們在資源編輯器中設計菜單時,不需要 給它們ID號的原因。
在【文件】下面又有了項,如圖中黃色矩形標注的地方,如【新建】、【 打開】。
知道這個後,我們的思路就有了。
1、調用GetMenu( 窗口句柄 )獲取窗口中菜 單欄的句柄;
2、調用GetSubMenu( 菜單欄句柄,0 )獲得【水果】彈出菜單的句柄,0表 示菜單欄中的第一個元素,如果第二個就是1,我們的彈出菜只有【水果】一項;
3、調用 CheckMenuRadioItem函來來Check菜單。
因為我們是在響應WM_COMMAND消息時作出響應的,所以 這些代碼應寫在WindowProc中。
LRESULT CALLBACK WindowMainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // 獲取窗口上的整個菜單欄的句柄 HMENU hmm = GetMenu(hwnd); // 獲取第一個彈出菜單,即[水果]菜單 HMENU hfmn = GetSubMenu(hmm, 0); switch(msg) { case WM_COMMAND: { .......
菜單句柄是HMENU類型,所以GetMenu和GetSubMenu函數都返回HMENU類型的 值。其實,這裡我給大家推薦一個技巧,就是使用auto關鍵字,我們無需管它函數什麼,統一用auto關 鍵字,它會根據代碼上下文推斷數據類型,就像C#裡面的var聲明變量一樣。所以,我們上面的代碼可 以改為:
LRESULT CALLBACK WindowMainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // 獲取窗口上的整個菜單欄的句柄 auto hmm = GetMenu(hwnd); // 獲取第一個彈出菜單,即[水果]菜單 auto hfmn = GetSubMenu(hmm, 0); switch(msg) { case WM_COMMAND: { ........
然後,我們響應命令消息。
switch(msg) { case WM_COMMAND: { //判斷用戶選了哪個菜單 switch(LOWORD(wParam)) { case IDM_APPLE: CheckMenuRadioItem(hfmn, IDM_APPLE, IDM_BANANA, IDM_APPLE, MF_BYCOMMAND); MessageBox(hwnd,L"你選擇了蘋果。",L"提示",MB_OK); break; case IDM_PEAR: CheckMenuRadioItem(hfmn, IDM_APPLE, IDM_BANANA, IDM_PEAR, MF_BYCOMMAND); MessageBox(hwnd,L"你選擇了梨子。", L"提示", MB_OK); break; case IDM_BANANA: CheckMenuRadioItem(hfmn, IDM_APPLE, IDM_BANANA, IDM_BANANA, MF_BYCOMMAND); MessageBox(hwnd, L"你選擇了香蕉。", L"提示", MB_OK); break; } } return 0;
這樣就得到單選菜單的效果了。下面是完整的代碼清單。
#include <Windows.h> #include "resource.h" // 聲明消息處理函數 LRESULT CALLBACK WindowMainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); //入口點 int WINAPI WinMain( HINSTANCE hthisInstance,//當前實例句柄 HINSTANCE hPrevInstance,//錢一個實例句柄,一般不使用 LPSTR cmdline,//命令行參數 int nShow)//窗口的顯示方式 { // 設計窗口類 WNDCLASS wc = { }; wc.lpszClassName = L"MainWd"; wc.hInstance = hthisInstance; wc.lpfnWndProc = WindowMainProc; wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // 在這裡把菜單附加上,成為類級別 //wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAIN);//整個菜單資源的ID,不是菜單項 // 讓窗口自動重繪 wc.style = CS_HREDRAW | CS_VREDRAW; // 注冊窗口類 RegisterClass(&wc); // 創建窗口 HWND hm = CreateWindow( L"MainWd", L"我的應用程序", WS_OVERLAPPEDWINDOW, 25, 18, 380, 280, NULL, LoadMenu(hthisInstance,MAKEINTRESOURCE(IDR_MAIN)), hthisInstance, NULL); if(hm == NULL) return 0; // 顯示窗口 ShowWindow(hm, SW_SHOW); // 消息循環 MSG msg; while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } LRESULT CALLBACK WindowMainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // 獲取窗口上的整個菜單欄的句柄 auto hmm = GetMenu(hwnd); // 獲取第一個彈出菜單,即[水果]菜單 auto hfmn = GetSubMenu(hmm, 0); switch(msg) { case WM_COMMAND: { //判斷用戶選了哪個菜單 switch(LOWORD(wParam)) { case IDM_APPLE: CheckMenuRadioItem(hfmn, IDM_APPLE, IDM_BANANA, IDM_APPLE, MF_BYCOMMAND); MessageBox(hwnd,L"你選擇了蘋果。",L"提示",MB_OK); break; case IDM_PEAR: CheckMenuRadioItem(hfmn, IDM_APPLE, IDM_BANANA, IDM_PEAR, MF_BYCOMMAND); MessageBox(hwnd,L"你選擇了梨子。", L"提示", MB_OK); break; case IDM_BANANA: CheckMenuRadioItem(hfmn, IDM_APPLE, IDM_BANANA, IDM_BANANA, MF_BYCOMMAND); MessageBox(hwnd, L"你選擇了香蕉。", L"提示", MB_OK); break; } } return 0; case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hwnd, msg, wParam, lParam); } }