久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

Windows消息編程

 冉亮 2010-08-22

Windows消息編程
韓耀旭

下載源代碼

本文主要包括以下內(nèi)容:

1,、簡單理解Windows的消息
2、通過一個簡單的Win32程序理解Windows消息
3,、通過幾個Win32程序?qū)嵗M一步深入理解Windows消息
4,、隊列消息和非隊列消息
5、WM_COMMAND和WM_NOTIFY
6,、MFC的消息映射
7,、消息反射機制

1,、簡單理解Windows的消息

消息,,就是指Windows發(fā)出的一個通知,告訴應(yīng)用程序某個事情發(fā)生了,。
舉個例子來說,,鼠標(biāo)單擊某應(yīng)用程序的一個按鈕。這時,,Windows(操作系統(tǒng))給應(yīng)用程序發(fā)送這個消息,,通知應(yīng)用程序該按鈕被點擊,應(yīng)用程序?qū)⑦M行相應(yīng)反應(yīng),。
消息一般用一個32位的數(shù)來標(biāo)識,,這個數(shù)唯一地標(biāo)識這個消息。這些消息的標(biāo)識符一般在頭文件winuser.h 中定義,,如:

#define WM_PAINT 0x000F
#define WM_QUIT 0x0012


其實消息本身是一個MSG結(jié)構(gòu),。MSG結(jié)構(gòu)定義如下:

typedef struct tagMSG {
HWND hwnd; //接受消息的窗口句柄
UINT message; //消息標(biāo)識符
WPARAM wParam; //32位附加信息
LPARAM lParam; //32位附加信息
DWORD time; //消息創(chuàng)建的時間
POINT pt; //消息創(chuàng)建時鼠標(biāo)在屏幕坐標(biāo)系中的位置
} MSG;

也就是說,,對于任何一個消息,都有一個MSG變量與之對應(yīng),,該變量包含了消息的相關(guān)信息,。而我們在一般情況下,只使用消息的消息標(biāo)識符,,該標(biāo)識符也唯一地代表了這個消息,。
舉個例子來說,當(dāng)我們收到一個字符消息的時候,,message成員變量的值就是WM_CHAR,,但用戶到底輸入的是什么字符,那么就由wParam和lParam來說明,。wParam,、lParam表示的信息隨消息的不同而不同。
Windows操作系統(tǒng)已經(jīng)給我們定義了大量的消息,,這些消息我們稱為系統(tǒng)消息,。除了系統(tǒng)消息,我們還可以自己定義消息,,即自定義消息,。
值小于0x0400的消息都是系統(tǒng)消息,自定義消息一般都大于0x0400,。
系統(tǒng)消息取值一般有如下規(guī)律,,如表1:

范圍 意義
0x0001——0x0087
主要是窗口消息
0x00A0——0x00A9
非客戶區(qū)消息
0x0100——0x0108
鍵盤消息
0x0111——0x0126
菜單消息
0x0132——0x0138
顏色控制消息
0x0200——0x020A
鼠標(biāo)消息
0x0211——0x0213
菜單循環(huán)消息
0x0220——0x0230
多文檔消息
0x03E0——0x03E8
DDE消息
0x0400
WM_USER
0x0400——0x7FFF
自定義消息

表1

在WINUSER.H中,我們有定義:

#define WM_USER 0x0400

對于自定義消息,,我們一般采用WM_USER 加一個整數(shù)值的方法定義自定義消息,,如:

#define WM_RECVDATA WM_USER + 1


如果您初次接觸Windows編程,或是初次接觸Windows消息,,對于上述解釋可能沒有看懂,,這也不要著急,后面的實例將會逐步帶您對Windows的消息編程有一個了解,。

2,、通過一個簡單的Win32程序理解Windows消息
例程1:一個簡單的Win32程序代碼(見附帶源碼 工程M1)
打開VC++ 6.0,新建一個Win32 Application,,工程名為M1,,在該工程添加C++ Source File,文件名為M1,,在該文件中添加如下代碼:

//一個簡單的Win32應(yīng)用程序
//通過這個簡單的實例講解Windows消息是如何傳遞的
#include <windows.h>
//聲明窗口過程函數(shù)
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
//定義一個全局變量,,作為窗口類名
TCHAR szClassName[] = TEXT("SimpleWin32");
//應(yīng)用程序主函數(shù)
int WINAPI WinMain (HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR szCmdLine,
int iCmdShow)
{
//窗口類
WNDCLASS wndclass;
//當(dāng)窗口水平方向的寬度和垂直方向的高度變化時重繪整個窗口
wndclass.style = CS_HREDRAW|CS_VREDRAW;
//關(guān)聯(lián)窗口過程函數(shù)
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;//實例句柄
wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION);//圖標(biāo)
wndclass.hCursor = LoadCursor(NULL,IDC_ARROW);//光標(biāo)
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//畫刷
wndclass.lpszMenuName  = NULL;//菜單
wndclass.lpszClassName = szClassName;//類名稱
//注冊窗口類
if(!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("RegisterClass Fail!"),
szClassName, MB_ICONERROR);
return 0;
}
//建立窗口
HWND hwnd;
hwnd = CreateWindow(szClassName,//窗口類名稱
TEXT ("The Simple Win32 Application"),//窗口標(biāo)題
WS_OVERLAPPEDWINDOW,//窗口風(fēng)格,即通常我們使用的windows窗口樣式
CW_USEDEFAULT,//指定窗口的初始水平位置,即屏幕坐標(biāo)系的窗口的左上角的X坐標(biāo)
CW_USEDEFAULT,//指定窗口的初始垂直位置,即屏幕坐標(biāo)系的窗口的左上角的Y坐標(biāo)
CW_USEDEFAULT,//窗口的寬度
CW_USEDEFAULT,//窗口的高度
NULL,//父窗口句柄
NULL,//窗口菜單句柄
hInstance,//實例句柄
NULL);
ShowWindow(hwnd,iCmdShow);//顯示窗口
UpdateWindow(hwnd);//立即顯示窗口
//消息循環(huán)
MSG msg;
while(GetMessage(&msg,NULL,0,0))//從消息隊列中取消息
{
TranslateMessage (&msg);              //轉(zhuǎn)換消息
DispatchMessage (&msg);               //派發(fā)消息
}
return msg.wParam;
}
//消息處理函數(shù)
//參數(shù):窗口句柄,消息,消息參數(shù),,消息參數(shù)
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
//處理感興趣的消息
switch (message)
{
case WM_DESTROY:
//當(dāng)用戶關(guān)閉窗口,,窗口銷毀,程序需結(jié)束,,發(fā)退出消息,,以退出消息循環(huán)
PostQuitMessage(0);
return 0;
}
//其他消息交給由系統(tǒng)提供的缺省處理函數(shù)
return ::DefWindowProc (hwnd, message, wParam, lParam);
}
這是一個非常簡單的Win32小程序,編譯運行會顯示一個窗口,,關(guān)閉窗口程序會結(jié)束運行,。 代碼中已經(jīng)做了簡單注解,這里我們不作過多說明,。我在這里再著重講解一下消息循環(huán)部分,。
//消息循環(huán)
MSG msg;
while(GetMessage(&msg,NULL,0,0))//從消息隊列中取消息
{
TranslateMessage (&msg);              //轉(zhuǎn)換消息
DispatchMessage (&msg);               //派發(fā)消息
}

這段代碼是消息循環(huán)部分,它的作用是循環(huán)檢測消息隊列(不懂消息隊列,?沒關(guān)系,,后面會詳細說明)中的消息并進行處理。這段代碼涉及GetMessage,,TranslateMessage,,DispatchMessage這三個函數(shù),相關(guān)函數(shù)還有PeekMessage,WaitMessage,。在此,,我們先對這五個函數(shù)簡單講解。

1,、GetMessage

函數(shù)原型:

BOOL GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);

參數(shù):

lpMsg:一個指向MSG結(jié)構(gòu)的指針,,該結(jié)構(gòu)用于存放從消息隊列里取出的消息。
hWnd:窗口句柄,。如果該參數(shù)是非零值,,則GetMessage只檢索該窗口(也包括其子窗口)消息,如果為零,,則GetMessage檢索整個進程內(nèi)的消息,。
wMsgFilterMin:指定被檢索的最小消息值,也就是消息范圍的下界限參數(shù),。
wMsgFilterMax:上界限參數(shù),。如果wMsgFilterMin和wMsgFilterMax都為零,則不進行消息過濾,,GetMessage檢索所有有效的消息。

返回值

GetMessage檢索到WM_QUIT消息,,返回值是零,;其它情況,返回非零值。

函數(shù)功能:

這個API函數(shù)用來從消息隊列中“摘取”一個消息,,放到lpMsg所指的變量里,。(注:如果所取窗口的消息隊列中沒有消息,則程序會暫停在GetMessage(…) 函數(shù)里,,不會返回,。)
再通俗一點講解GetMessage函數(shù):
當(dāng)程序執(zhí)行GetMessage()的時候,會檢查消息隊列,,如果有消息在消息隊列里,,它取出該消息,將該消息填充到lpMsg所指的MSG結(jié)構(gòu),,并返回TRUE值,。如果此時消息隊列里沒有消息(消息隊列為空),它會將線程阻塞,,也就是將控制權(quán)交給系統(tǒng),,直到消息隊列中有內(nèi)容時,才喚醒線程繼續(xù)執(zhí)行,。
對于GetMessage()函數(shù),,還有一點需要說明,就是當(dāng)從消息隊列中取出的消息是WM_QUIT時,,函數(shù)返回值是0,。我們一般利用這一點退出消息循環(huán),結(jié)束程序,。

如語句:

while(GetMessage(&msg,NULL,0,0))
……

2 ,、PeekMessage

函數(shù)原型:

BOOL PeekMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax,UINT wRemoveMsg);

參數(shù):

lpMsg、hWnd,、wMsgFilterMin,、wMsgFilterMax這四個參數(shù)的意義和GetMessage對應(yīng)參數(shù)的意義相同,在此不再贅述,。
wRemoveMsg:這個參數(shù)決定讀消息時是否刪除消息,,可選值有PM_NOREMOVE和PM_REMOVE。如果您選PM_NOREMOVE,,執(zhí)行該函數(shù)后消息仍然留在消息隊列(我稱為讀消息),;如果您選PM_REMOVE,執(zhí)行該函數(shù)后將在消息隊列中移除該消息(同GetMessage()),。

返回值:

消息隊列中有消息,,返回值為TRUE;消息隊列中沒有消息,,返回值為FALSE,。

函數(shù)功能:

PeekMessage()也是從消息隊列中取消息,,但它是GetMessage()不同,主要在以下兩點:

(一),、GetMessage()只能從消息隊列中取走消息,,也就是說,GetMessage()執(zhí)行后,,該消息將從消息隊列中移除,。
PeekMessage()可以從消息隊列中取走消息。也可以讀消息,,讓消息繼續(xù)留在消息隊列里,。

(二)、當(dāng)消息隊列中沒有消息時,,GetMessage()將會阻塞線程,,等待消息;而PeekMessage()與GetMessage()不同,,它執(zhí)行后會立刻返回,,消息隊列中有消息時,返回值為TRUE,;消息隊列中沒有消息時,,返回值為FALSE。

3 ,、WaitMessage

函數(shù)原型:

BOOL WaitMessage(VOID);

函數(shù)功能:

這個函數(shù)的作用是當(dāng)消息隊列中沒有消息時,,將控制權(quán)交給其它線程。該函數(shù)將會使線程掛起,,直到消息隊列中又有新消息,。
這個函數(shù)專門和PeekMessage配合使用,當(dāng)消息隊列中沒有消息時,,掛起線程,,等待消息隊列中新消息的到來,這樣可以減輕CPU的運算負(fù)擔(dān),。

4 ,、TranslateMessage

函數(shù)原型:

BOOL TranslateMessage(CONST MSG*lpMsg);

參數(shù):

  IpMsg:指向MSG結(jié)構(gòu)的指針,,該結(jié)構(gòu)是函數(shù)GetMessage或PeekMessage從消息隊列里取得的消息,。
  函數(shù)功能:該函數(shù)將虛擬鍵消息轉(zhuǎn)換為字符消息。字符消息被寄送到調(diào)用線程的消息隊列里,,當(dāng)下一次線程調(diào)用函數(shù)GetMessage或PeekMessage時被讀出,。
什么是虛擬鍵碼呢?Windows為了方便輸入管理,,減少程序?qū)υO(shè)備的依賴性,,將鍵盤上所有的按鍵都用一個兩位十六進制數(shù)對應(yīng),,這些數(shù)稱為虛擬鍵碼,。虛擬鍵碼一般以VK_開頭,,如:Esc鍵對應(yīng)的虛擬鍵碼是VK_ESCAPE;空格鍵對應(yīng)的虛擬鍵碼是VK_SPACE,;VK_LWIN與左邊的Windows徽標(biāo)鍵相對應(yīng),。
當(dāng)一個按鍵被按下時,會觸發(fā)WM_KEYDOWN消息,, WM_KEYDOWN消息的wParam參數(shù)值就是虛擬鍵值,。通過這個值就可以判斷哪個鍵被按下了。
為什么我們要把虛擬鍵碼轉(zhuǎn)換為字符碼呢,?
比如我們按下了‘A’鍵,,此時我們得到的字符可能是‘A’,也可能是小寫的‘a’,,這由當(dāng)時的大寫狀態(tài)(Caps Lock)以及是否同時按下了Shift鍵有關(guān),。TranslateMessage()函數(shù)的作用就是不用我們考慮這些問題,而是根據(jù)這些情況,,自動返回一個ASCII碼值,,以方便用戶使用。
并不是所有的虛擬鍵碼值都會Translate成字符碼,。字母,、數(shù)字鍵都有字符碼相對應(yīng),而像方向箭頭鍵,、F1—F12功能鍵這些按鍵就沒有字符碼相對應(yīng),。當(dāng)虛擬鍵碼需要轉(zhuǎn)化成字符碼時,TranslateMessage()函數(shù)就在消息隊列里放一條WM_CHAR消息,,WM_CHAR消息的wParam參數(shù)值就是轉(zhuǎn)換后的ASCII碼值,。

5、DispatchMessage

函數(shù)原型:

LONG DispatchMessage(CONST MSG *lpmsg);

函數(shù)功能:
它的作用很簡單,,就是分派消息到窗口的消息處理函數(shù)去執(zhí)行,。
了解了這5個函數(shù),消息循環(huán)這段代碼就不難理解:

GetMessage()從消息隊列中取消息,,對取出的消息進行轉(zhuǎn)換(TranslateMessage),,對于能夠?qū)⑻摂M鍵碼轉(zhuǎn)化成字符碼的消息,會在消息隊列里放一條WM_CHAR消息,,最后將消息發(fā)送到相應(yīng)的消息處理函數(shù)進行處理,。循環(huán)執(zhí)行這個處理過程,直到收到WM_QUIT消息,,才退出循環(huán),,結(jié)束程序,。

3、通過幾個Win32程序?qū)嵗M一步深入理解Windows消息

例程2:對比使用GetMessage和PeekMessage處理消息循環(huán)(見附帶源碼 工程M2)
同工程M1,,新建工程M2,,將工程M1的源代碼全部拷貝到M2,并將消息循環(huán)部分的代碼改為:

//消息循環(huán)
MSG msg;
while(true)
{
if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) //從消息隊列中取消息
{
if(msg.message == WM_QUIT)
break;
TranslateMessage (&msg);           //轉(zhuǎn)換消息
DispatchMessage (&msg);            //派發(fā)消息
}
else
WaitMessage();
} //End of while(true)

編譯,、運行工程M2,,觀察運行效果,可以看出,,使用PeekMessage處理消息循環(huán)同樣能夠達到與GetMessage相同的效果,。
PeekMessage處理消息循環(huán)比GetMessage還要靈活,尤其體現(xiàn)在游戲編程中,。游戲編程者不希望玩家在沒有鍵盤或鼠標(biāo)輸入時游戲是靜止不動的,,他們希望怪獸從后面沖出來,圍攻玩家,,追捕玩家,。為了做到這樣的效果,需要這樣一種消息循環(huán):當(dāng)遇到需要處理的消息時去處理消息,,其余的時間都讓程序代碼自動產(chǎn)生激烈的場面,。
下面的例程3將模擬這種消息循環(huán)。
例程3:模擬演示游戲編程如何進行消息處理(見附帶源碼工程M3),。
詳細的代碼參看工程M3,,編譯并執(zhí)行,您會發(fā)現(xiàn)程序不停地自己畫圓,,這模擬游戲自動產(chǎn)生激烈的場面,。當(dāng)您按下上、下,、左,、右箭頭鍵,您就會發(fā)現(xiàn)您在相應(yīng)的方向畫線,,這模擬游戲程序及時處理玩家的消息,。

4、隊列消息和非隊列消息
Windows把消息分為兩種:一種是需要立即處理的消息,,另一種是不需要立即處理的消息,。
對于需要立即處理的消息,Windows直接把它送給窗口的消息處理函數(shù)進行處理,,這類消息我們叫做非隊列消息,;
而對于不需要立即處理的消息,Windows會把它發(fā)送給應(yīng)用程序的消息隊列進行排隊,,由應(yīng)用程序逐個進行處理,,我們把這類消息叫做隊列消息,。
為了更清楚地說明這個問題,我們參看圖1:

圖1

圖1的解釋:

1,、Windows操作系統(tǒng)有一個消息隊列,,它存放操作系統(tǒng)收到的消息。如:當(dāng)按鍵被按下,,鍵盤會發(fā)送一個消息到操作系統(tǒng)的消息隊列,。
2,、操作系統(tǒng)把系統(tǒng)消息隊列中的消息分派到各個應(yīng)用程序的消息隊列,。如果它是第1個應(yīng)用程序的消息,操作系統(tǒng)把它發(fā)給第1個應(yīng)用程序,,把它放在第1個應(yīng)用程序的消息隊列,;如果它是第2個應(yīng)用程序的消息,發(fā)送給第2個程序的消息隊列,。
3,、應(yīng)用程序的消息循環(huán)從自己的消息隊列中取消息,取出的消息調(diào)用窗口過程函數(shù)進行處理,。
4,、PostMessage是寄送消息,函數(shù)執(zhí)行后立即返回,。寄送的消息是隊列消息,,放在程序的消息隊列中排隊處理。一般來說,,新寄送的消息排在消息隊列的末尾,,這樣可以保證窗口以先進先出的順序處理消息。
SendMessage是發(fā)送消息,,它發(fā)出的消息是非隊列消息,,直接調(diào)用窗口過程函數(shù)處理。SendMessage函數(shù)一直等消息處理完成后才返回,。

我們有必要再專門學(xué)習(xí)一下SendMessage和PostMessage函數(shù),。

SendMessage的函數(shù)原型:

LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam);

這個函數(shù)向窗口發(fā)送一條消息,一直等到消息被處理之后才返回,。也就是說,,接收消息的窗口的窗口函數(shù)立即被調(diào)用。函數(shù)的返回值由接收消息的窗口的窗口函數(shù)返回,。

PostMessage的函數(shù)原型:

BOOL PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam);

該函數(shù)把一條消息放置到創(chuàng)建hWnd窗口的線程的消息隊列中,,該函數(shù)不等消息被處理就馬上將控制返回。
從上面這兩個函數(shù),,我們可以看出消息的發(fā)送方式和寄送方式的區(qū)別:被發(fā)送的消息會被立即處理,,處理完畢后函數(shù)才返回,;被寄送的消息不會被立即處理,他被放到一個先進先出的隊列中,,按次序等候處理,,而且函數(shù)放置消息后立即返回。
以寄送方式發(fā)送的消息通常是與用戶輸入事件相對應(yīng)的,,因為這些事件不是十分緊迫,,可以進行緩沖處理,例如鼠標(biāo),、鍵盤消息都是寄送消息,。應(yīng)用程序調(diào)用系統(tǒng)函數(shù),系統(tǒng)一般會發(fā)送非隊列消息,。例如,,當(dāng)程序調(diào)用SetWindowPos,系統(tǒng)會發(fā)送WM_WINDOWPOSCHANGED消息,。
例程M4,,測試消息隊列的容量(見附帶源碼工程M4)
代碼中已經(jīng)作了注解,編譯,、運行程序,,您就會發(fā)現(xiàn)消息隊列的最大容量是10000。
例程M5,,用記事本查看消息隊列和窗口過程函數(shù)處理的消息
這個例程的出發(fā)點是利用記事本分別捕獲消息隊列中的消息和窗口過程函數(shù)處理過的消息,。
該例程還演示了PostMessage和SendMessage的不同。
由于該例程相對復(fù)雜一些,,例程中的注解也相對多一些,。編譯、運行程序,,彈出如下窗口:


關(guān)閉該窗口,,退出運行,檢查M5例程所在的路徑,,您就會發(fā)現(xiàn)多了兩個文件MessageQueue.txt和MessageWndProc.txt,,MessageQueue.txt文件中記錄的是應(yīng)用程序M5從運行到關(guān)閉消息隊列中處理過的消息;MessageWndProc.txt中記錄的M5窗口過程函數(shù)處理過的消息,。
打開MessageQueue.txt文件,,如下圖:


文件中記錄了消息隊列中的各個消息以及消息的ID號,其中有一條消息是WM_POSTMESSAGE,,這說明PostMessage寄送的WM_POSTMESSAGE消息確實放到了消息隊列中,。
再打開MessageWndProc.txt文件,如下圖:


文件中記錄了窗口過程處理的各個消息和消息的ID號,其中有兩條消息WM_POSTMESSAGE和WM_SENDMESSAGE,,這說明了兩個問題:WM_POSTMESSAGE消息從消息隊列取出,,再次派發(fā)到窗口過程函數(shù)處理;SendMessage發(fā)送的WM_SENDMESSAGE消息,,沒有經(jīng)過消息隊列,,直接送到窗口過程函數(shù)處理。

5,、WM_COMMAND和WM_NOTIFY

控件通知消息,,是指這樣一種消息,一個窗口內(nèi)的控件發(fā)生了一些事情,,需要通知父窗口,。當(dāng)用戶與控件窗口交互時,控件通知消息就會從控件窗口發(fā)送到它的主窗口,,這種消息一般不是為了處理用戶命令,,而是為了讓主窗口能夠改變控件。
WM_COMMAND和WM_NOTIFY都是控件通知消息,。
在最初的Windows 3.x中,還沒有WM_NOTIFY,,只存在WM_COMMAND消息,,wParam參數(shù)中包含一個通知碼和控件ID,lParam中包含控件句柄,。這樣一來,,wParam和lParam都被填充了,沒有額外的空間來傳遞一些其它信息,,如鼠標(biāo)按下的位置和時間,。
為了解決這個問題,Windows 3.x就提出了一個解決策略,,那就是給一些消息添加一些附加消息,,比如控件自畫用到的DRAWITEMSTRUCT等,這樣,,不同的消息附加的內(nèi)容不同,,結(jié)果是非常混亂,。
在Win32中,,微軟又提出了一個更好的解決方案,引進了NMHDR結(jié)構(gòu),。這個結(jié)構(gòu)的引進把消息統(tǒng)一起來,,利用它可以傳遞各種復(fù)雜的消息。
NMHDR結(jié)構(gòu)內(nèi)容如下:

NMHDR
{
HWND hWndFrom;//相當(dāng)于原WM_COMMAND消息的lParam
UINT idFrom; //相當(dāng)于原WM_COMMAND消息的wParam(LOWORD)
UINT code; //相當(dāng)于原WM_COMMAND消息的wParam(HIWORD)通知碼
}

使用這個結(jié)構(gòu),WM_NOTIFY還可以附帶更多的信息,,您可以定義一個更大的結(jié)構(gòu),,這個結(jié)構(gòu)的第一個元素就是NMHDR結(jié)構(gòu),在該元素的后面您還可以放置其它附加信息,。由于在這個大結(jié)構(gòu)中,,第一個成員是NMHDR,這樣一來,,我們就可以利用指向NMHDR的指針來指向這個結(jié)構(gòu),,不論后面有沒有其它內(nèi)容。
可見,,WM_NOTIFY和WM_COMMAND相比,,是一種更靈活的消息格式,lParam中放的是一個稱為NMHDR結(jié)構(gòu)的指針,。在wParam中放的則是控件的ID,。最初Windows 3.x就有的控件,如Edit,,Combo,,List,Button等,,發(fā)送的控件通知消息的格式是WM_COMMAND,;而后期的Win32通用控件,如List View,,Image List,,IP Address,Tree View,,Toolbar等,,發(fā)送的都是WM_NOTIFY控件通知消息。
另外,,當(dāng)用戶選擇菜單的一個命令項,,也會發(fā)送WM_COMMAND消息。
當(dāng)用戶選擇菜單的一個命令項或控件給父窗口發(fā)送通知消息,,都可以使用WM_COMMAND消息,。為了區(qū)分這兩種情況,規(guī)定它們有以下區(qū)別,,如表2:

消息來源
wParam (high word)
wParam (low word)
lParam

菜單
0
菜單標(biāo)識符 (IDM_*)
0

控件
控件定義的通知碼
控件ID
控件窗口的句柄

表2

例程M6,,演示菜單發(fā)出WM_COMMAND消息和子控件發(fā)送WM_COMMAND消息的區(qū)別(見附帶源碼工程M6)
打開VC++ 6.0,新建Win32 Application工程M6,,然后在該工程中新建C++ Source File,,文件名為M6,M6的文件內(nèi)容具體見例程M6。
在例程M6所在的路徑打開M6文件夾,,新建一個文本文檔,,如下圖:



將“新建文本文檔.txt”改名為“M6.rc”,如下圖:


右鍵單擊M6.rc,,在彈出的快捷菜單中使用“寫字板”打開,,如下圖:

添加的內(nèi)容具體見M6.rc,保存后退出,。編譯,、運行工程M6,彈出如下窗口:



分別單擊“FirstButton”按鈕和“Menu1”菜單,,會彈出相應(yīng)的提示消息框,。
M6中對于WM_COMMAND消息的處理,源代碼如下:
case WM_COMMAND:
{
if(lParam == 0)
{
switch(LOWORD(wParam))
{
case IDM_MENU1:
MessageBox(NULL,"MENU1菜單被點擊","M6",MB_OK);
break;
case IDM_EXIT:
DestroyWindow(hwnd);
break;
}
}
else //處理子控件觸發(fā)的WM_COMMAND控件通知消息
{
//(LOWORD(wParam))是控件ID
switch(LOWORD(wParam))
{
case ButtonID1:
if(HIWORD(wParam) == BN_CLICKED)
{
MessageBox(NULL,"按鈕被點擊","M6",MB_OK);
}
break;
}
}
}
break;

對于WM_COMMAND消息,,因為菜單和子控件都能觸發(fā),。我們首先判斷l(xiāng)Param,如果lParam為0,,是菜單觸發(fā)的WM_COMMAND消息,;如果lParam不為0,是子控件觸發(fā)的WM_COMMAND控件通知消息,。對于菜單觸發(fā)的WM_COMMAND消息,,我們再通過(LOWORD(wParam))(菜單的標(biāo)識ID)判斷是哪個菜單觸發(fā)的消息;對于控件觸發(fā)的WM_COMMAND消息,,我們通過(LOWORD(wParam))(控件ID)知道是哪個控件觸發(fā)的消息,而且通過(HIWORD(wParam))(控件定義的通知碼)知道控件到底觸發(fā)了什么消息,。
本例程我們純手工添加并編輯資源文件M6.rc,,之所以這樣做是為了讓您了解資源文件的實質(zhì)。實際編程中,,您完全可以利用資源編輯器更加方便地添加,、編輯資源文件,后面的例程將會演示說明,。
例程M7,,演示W(wǎng)M_NOTIFY控件通知消息(見附帶源碼 工程M7)
WM_NOTIFY消息是通用控件發(fā)送給其父窗口的消息,其中參數(shù)wParam 是發(fā)送消息的通用控件的ID,,參數(shù)lParam 是一個指針,,這個指針指向一個 NMHDR 結(jié)構(gòu),該結(jié)構(gòu)包含了通知碼和其它附加信息,。

下面我們看結(jié)構(gòu)NMHDR:

typedef struct tagNMHDR {
//發(fā)送消息的控件的句柄,,相當(dāng)于原WM_COMMAND消息的lParam
HWND hwndFrom;
//發(fā)送消息的控件的ID,相當(dāng)于原WM_COMMAND消息的wParam(LOWORD)
UINT idFrom;
//通知碼,也就是發(fā)送的具體消息,,相當(dāng)于原WM_COMMAND消息的wParam(HIWORD)通知碼
UINT code;
} NMHDR;

打開VC++ 6.0,,新建Win32 Application工程M7,然后在該工程中新建C++ Source File,,文件名為M7,,M7的文件內(nèi)容具體見例程M7。
下面,,我們利用資源編輯器添加資源,。單擊“文件”->“新建”,在“新建”對話框中選中“Resource Script”,,文件名為“M7”,,如下圖:



單擊“確定”,添加M7資源文件,。
右擊“M7.RC”文件夾,,選中“Insert…”菜單項,如下圖:



彈出“插入資源”對話框,,



選中“Dialog”,,點擊“新建”按鈕,新建一個對話框資源,。
右擊新建的“IDD_DIALOG1”,,在屬性對話框中將ID改為“IDC_DIALOG”,關(guān)閉屬性框,。



雙擊“IDC_DIALOG”,,打開該對話框,調(diào)整至合適大小,,在對話框上添加一個列表控件(List Control),,將該列表控件的ID設(shè)置為IDC_LIST,如下圖:



并且把列表控件改為“Report”類型,,如下圖:



編輯并運行程序,,程序運行會彈出如下對話框:

分別用鼠標(biāo)雙擊第一行或第二行,會彈出相應(yīng)消息框,。
程序代碼都有詳細注釋,,您可以閱讀代碼,細細體會WM_NOTIFY控件通知消息,。

6,、MFC的消息映射

使用MFC編程時,消息發(fā)送和處理的本質(zhì)和Win32相同,,但是,,它對消息處理進行了封裝,,簡化了程序員編程時消息處理的復(fù)雜性,它通過消息映射機制來處理消息,,程序員不必去設(shè)計和實現(xiàn)自己的窗口過程,。
說白了,MFC中的消息映射機制實質(zhì)是一張巨大的消息及其處理函數(shù)對應(yīng)表,。消息映射基本上分為兩大部分:
在頭文件(.h)中有一個宏DECLARE_MESSAGE_MAP(),它放在類的末尾,,是一個public屬性的;與之對應(yīng)的是在實現(xiàn)部分(.cpp)增加了一個消息映射表,,內(nèi)容如下:

BEGIN_MASSAGE_MAP(當(dāng)前類,,當(dāng)前類的基類)
//{{AFX_MSG_MAP(CMainFrame)

消息的入口項

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

但是僅是這兩項還不足以完成一條消息,要是一個消息工作,,必須還有以下3個部分去協(xié)作:
1,、在類的定義中加入相應(yīng)的函數(shù)聲明;
2,、在類的消息映射表中加入相應(yīng)的消息映射入口項,;
3、在類的實現(xiàn)中加入相應(yīng)的函數(shù)體,;


消息的添加

(1),、利用Class Wizard實現(xiàn)自動添加
在菜單中選擇View -> Class Wizard激活Class Wizard,選擇Message Map標(biāo)簽,,從Class name組合框中選取我們想要添加消息的類,。在Object IDs列表框中,選取類的名稱,。此時,,Messages列表框顯示該類的可重載成員函數(shù)和窗口消息??芍剌d成員函數(shù)顯示在列表的上部,,以實際虛構(gòu)成員函數(shù)的大小寫字母來表示。其他為窗口消息,,以大寫字母出現(xiàn)。選中我們要添加的消息,,單擊Add Funtion按鈕,,Class Wizard自動將該消息添加進來。
有時候,,我們想要添加的消息在Message列表中找不到,,我們可以利用Class Wizard上Class Info標(biāo)簽以擴展消息列表。在該頁中,,找到Message Filter組合框,,通過它可以改變首頁中Messages列表框中的選項,。

(2)、手動添加消息
如果Messages列表框中確實沒有我們想要的消息,,就需要我們手工添加:
1)在類的.h文件中添加處理函數(shù)的聲明,,緊接著在//}}AFX_MSG行之后加入聲明,注意,,一定要以afx_msg開頭,。
通常,添加處理函數(shù)聲明的最好的地方是源代碼中Class Wizard維護的表的下面,,在它標(biāo)記其領(lǐng)域的{{ }}括弧外面,。這些括弧中的任何東西都有可能會被Class Wizard銷毀。
2)接著,,在用戶類的.cpp文件中找到//}}AFX_MSG_MAP行,,緊接在它之后加入消息入口項。同樣,,也放在{{ }}外面,。
3)最后,在該文件中添加消息處理函數(shù)的實體,。

對于能夠使用Class Wizard添加的消息,,盡量使用Class Wizard添加,以減少我們的工作量,;對于不能使用Class Wizard添加的消息和自定義消息,,需要手動添加??傮w說來,,MFC的消息編程對用戶來說,相對比較簡單,,在此不再使用實例演示,。

7、消息反射機制
什么叫消息反射,?
父窗口將控件發(fā)給它的通知消息,,反射回控件進行處理(即讓控件處理這個消息),這種通知消息讓控件自己處理的機制叫做消息反射機制,。
通過前面的學(xué)習(xí)我們知道,,一般情況下,控件向父窗口發(fā)送通知消息,,由父窗口處理這些通知消息,。這樣,父窗口(通常是一個對話框)會對這些消息進行處理,,換句話說,,控件的這些消息處理必須在父窗口類體內(nèi),,每當(dāng)我們添加子控件的時候,就要在父窗口類中復(fù)制這些代碼,。很明顯,,這對代碼的維護和移植帶來了不便,而且,,明顯背離C++的對象編程原則,。
從4.0版開始,MFC提供了一種消息反射機制(Message Reflection),,可以把控件通知消息反射回控件,。具體地講,對于反射消息,,如果控件有該消息的處理函數(shù),,那么就由控件自己處理該消息,如果控件不處理該消息,,則框架會把該消息繼續(xù)送給父窗口,,這樣父窗口繼續(xù)處理該消息??梢?,新的消息反射機制并不破壞原來的通知消息處理機制。

 消息反射機制為控件提供了處理通知消息的機會,,這是很有用的,。如果按傳統(tǒng)的方法,由父窗口來處理這個消息,,則加重了控件對象對父窗口的依賴程度,,這顯然違背了面向?qū)ο蟮脑瓌t。若由控件自己處理消息,,則使得控件對象具有更大的獨立性,,大大方便了代碼的維護和移植。
實例M8:簡單地演示MFC的消息反射機制,。(見附帶源碼 工程M8)
打開VC++ 6.0,,新建一個基于對話框的工程M8。
在該工程中,,新建一個CMyEdit類,,基類是CEdit。接著,,在該類中添加三個變量,如下:

private:
CBrush m_brBkgnd;
COLORREF m_clrBkgnd;
COLORREF m_clrText;

在CMyEdit::CMyEdit()中,,給這三個變量賦初值:

{
m_clrBkgnd = RGB( 255, 255, 0 );
m_clrText = RGB( 0, 0, 0 );
m_brBkgnd.CreateSolidBrush(RGB( 150, 150, 150) );
}

打開ClassWizard,,類名為CMyEdit,,Messages處選中“=WM_CTLCOLOR”,您是否發(fā)現(xiàn),,WM_CTLCOLOR消息前面有一個等號,,它表示該消息是反射消息,也就是說,,前面有等號的消息是可以反射的消息,。



消息反射函數(shù)代碼如下:

HBRUSH CMyEdit::CtlColor(CDC* pDC, UINT nCtlColor)
{
// TODO: Change any attributes of the DC here
pDC->SetTextColor( m_clrText );//設(shè)置文本顏色
pDC->SetBkColor( m_clrBkgnd );//設(shè)置背景顏色
//請注意,在我們改寫該函數(shù)的內(nèi)容前,,函數(shù)返回NULL,,即return NULL;
//函數(shù)返回NULL將會執(zhí)行父窗口的CtlColor函數(shù),而不執(zhí)行控件的CtlColor函數(shù)
//所以,,我們讓函數(shù)返回背景刷,,而不返回NULL,目的就是為了實現(xiàn)消息反射
return m_brBkgnd; //返回背景刷
}
在IDD_M8_DIALOG對話框中添加一個Edit控件,,使用ClassWizard給該Edit控件添加一個CMyEdit類型的變量m_edit1,,把Edit控件和CMyEdit關(guān)聯(lián)起來。


編譯,,運行程序,,觀察運行效果。
就寫這些吧,,水平有限,,希望能對您有所幫助。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多