帥哥們,美女們,下午好,我又來誤人子弟,請做好準備。
今天,我們的目的是,想要實現下圖中的這種菜單效果。

就是一種類似單選按鈕的菜單,多個菜單項中,同時只有一個會被選中。
首先,我們在資源編輯器中,設計一個菜單資源。這個資源編輯器在管理資源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);
}
}
~~~
- 前言
- (1):關于C++的幾個要點
- (2):完整的開發流程
- (3):窗口的重繪
- (4):創建菜單
- (5):具有單選標記的菜單
- (6):創建右鍵菜單
- (7):多邊形窗口
- (8):繪圖(A)
- (9):繪圖(B)
- (10):繪圖(C)
- (11):使用控件——先來耍一下按鈕
- (12):使用控件——單選按鈕
- (13):握手對話框
- (14):用對話框作為主窗口
- (15):ListView控件
- (16):ListView的多個視圖
- (17):啟動和結束進程
- (18):使用對話框的兩個技巧
- (19):瀏覽和打開文件
- (20):瀏覽文件夾
- (21):復制&amp;粘貼&amp;剪貼板操作
- (22):抓取屏幕
- (23):漸變顏色填充
- (24):計時器
- (25):監視剪貼板