午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

Windows消息編程原理

 FADEC 2010-08-12
 


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

 

1、簡(jiǎn)單理解Windows的消息

 

2、通過(guò)一個(gè)簡(jiǎn)單的Win32程序理解Windows消息


3、通過(guò)幾個(gè)Win32程序?qū)嵗M(jìn)一步深入理解Windows消息


4、隊(duì)列消息和非隊(duì)列消息


5WM_COMMANDWM_NOTIFY


6MFC的消息映射


7、消息反射機(jī)制


1、簡(jiǎn)單理解Windows的消息


消息,就是指Windows發(fā)出的一個(gè)通知,告訴應(yīng)用程序某個(gè)事情發(fā)生了。


舉個(gè)例子來(lái)說(shuō),鼠標(biāo)單擊某應(yīng)用程序的一個(gè)按鈕。這時(shí),Windows操作系統(tǒng))給應(yīng)用程序發(fā)送這個(gè)消息,通知應(yīng)用程序該按鈕被點(diǎn)擊,應(yīng)用程序?qū)⑦M(jìn)行相應(yīng)反應(yīng)。


消息一般用一個(gè)32位的數(shù)來(lái)標(biāo)識(shí),這個(gè)數(shù)唯一地標(biāo)識(shí)這個(gè)消息。這些消息的標(biāo)識(shí)符一般在頭文件winuser.h 中定義,如:


#define WM_PAINT 0x000F


#define WM_QUIT 0x0012


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


typedef struct tagMSG {


HWND hwnd; //接受消息的窗口句柄


UINT message; //消息標(biāo)識(shí)符


WPARAM wParam; //32位附加信息


LPARAM lParam; //32位附加信息


DWORD time; //消息創(chuàng)建的時(shí)間


POINT pt; //消息創(chuàng)建時(shí)鼠標(biāo)在屏幕坐標(biāo)系中的位置


} MSG;


也就是說(shuō),對(duì)于任何一個(gè)消息,都有一個(gè)MSG變量與之對(duì)應(yīng),該變量包含了消息的相關(guān)信息。而我們?cè)谝话闱闆r下,只使用消息的消息標(biāo)識(shí)符,該標(biāo)識(shí)符也唯一地代表了這個(gè)消息。


舉個(gè)例子來(lái)說(shuō),當(dāng)我們收到一個(gè)字符消息的時(shí)候,message成員變量的值就是WM_CHAR,但用戶到底輸入的是什么字符,那么就由wParamlParam來(lái)說(shuō)明。wParamlParam表示的信息隨消息的不同而不同。


Windows操作系統(tǒng)已經(jīng)給我們定義了大量的消息,這些消息我們稱(chēng)為系統(tǒng)消息。除了系統(tǒng)消息,我們還可以自己定義消息,即自定義消息。


值小于0x0400的消息都是系統(tǒng)消息,自定義消息一般都大于0x0400。


系統(tǒng)消息取值一般有如下規(guī)律,如表1

 

范圍

意義

0x0001——0x0087

主要是窗口消息

0x00A0——0x00A9

非客戶區(qū)消息

0x0100——0x0108

鍵盤(pán)消息

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


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


#define WM_RECVDATA WM_USER + 1


如果您初次接觸Windows編程,或是初次接觸Windows消息,對(duì)于上述解釋可能沒(méi)有看懂,這也不要著急,后面的實(shí)例將會(huì)逐步帶您對(duì)Windows的消息編程有一個(gè)了解。


2、通過(guò)一個(gè)簡(jiǎn)單的Win32程序理解Windows消息


例程1:一個(gè)簡(jiǎn)單的Win32程序代碼(見(jiàn)附帶源碼 工程M1


打開(kāi)VC++ 6.0,新建一個(gè)Win32 Application,工程名為M1,在該工程添加C++ Source File,文件名為M1,在該文件中添加如下代碼:


//一個(gè)簡(jiǎn)單的Win32應(yīng)用程序


//通過(guò)這個(gè)簡(jiǎn)單的實(shí)例講解Windows消息是如何傳遞的


#include<windows.h>

 
//聲明窗口過(guò)程函數(shù)

 
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);


//定義一個(gè)全局變量,作為窗口類(lèi)名

 
TCHAR szClassName[] = TEXT("SimpleWin32");

 
//應(yīng)用程序主函數(shù)

 
int WINAPI WinMain (HINSTANCE hInstance,


HINSTANCE hPrevInstance,


LPSTR szCmdLine,


int iCmdShow)


{

 
//窗口類(lèi)


WNDCLASS wndclass;

 
//當(dāng)窗口水平方向的寬度和垂直方向的高度變化時(shí)重繪整個(gè)窗口


wndclass.style = CS_HREDRAW|CS_VREDRAW;

 
//關(guān)聯(lián)窗口過(guò)程函數(shù)


wndclass.lpfnWndProc = WndProc;


wndclass.cbClsExtra = 0;


wndclass.cbWndExtra = 0;


wndclass.hInstance = hInstance;//實(shí)例句柄


wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION);//圖標(biāo)


wndclass.hCursor = LoadCursor(NULL,IDC_ARROW);//光標(biāo)


wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//畫(huà)刷


wndclass.lpszMenuName = NULL;//菜單


wndclass.lpszClassName = szClassName;//類(lèi)名稱(chēng)

 




//注冊(cè)窗口類(lèi)


if(!RegisterClass (&wndclass))


{


MessageBox (NULL, TEXT ("RegisterClass Fail!"),


szClassName, MB_ICONERROR);


return 0;


}

 


//建立窗口


HWND hwnd;


hwnd = CreateWindow(szClassName,//窗口類(lèi)名稱(chēng)


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,//實(shí)例句柄


NULL);

 
ShowWindow(hwnd,iCmdShow);//顯示窗口


UpdateWindow(hwnd);//立即顯示窗口

 
//消息循環(huán)


MSG msg;


while(GetMessage(&msg,NULL,0,0))//從消息隊(duì)列中取消息


{


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)閉窗口,窗口銷(xiāo)毀,程序需結(jié)束,發(fā)退出消息,以退出消息循環(huán)


PostQuitMessage(0);


return 0;


}


//其他消息交給由系統(tǒng)提供的缺省處理函數(shù)


return ::DefWindowProc (hwnd, message, wParam, lParam);


}


這是一個(gè)非常簡(jiǎn)單的Win32小程序,編譯運(yùn)行會(huì)顯示一個(gè)窗口,關(guān)閉窗口程序會(huì)結(jié)束運(yùn)行。 代碼中已經(jīng)做了簡(jiǎn)單注解,這里我們不作過(guò)多說(shuō)明。我在這里再著重講解一下消息循環(huán)部分。


//消息循環(huán)

 
MSG msg;


while(GetMessage(&msg,NULL,0,0))//從消息隊(duì)列中取消息


{


TranslateMessage (&msg);       //轉(zhuǎn)換消息


DispatchMessage (&msg);        //派發(fā)消息


}


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


1GetMessage


函數(shù)原型:


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


參數(shù):


lpMsg:一個(gè)指向MSG結(jié)構(gòu)的指針,該結(jié)構(gòu)用于存放從消息隊(duì)列里取出的消息。


hWnd:窗口句柄。如果該參數(shù)是非零值,則GetMessage只檢索該窗口(也包括其子窗口)消息,如果為零,則GetMessage檢索整個(gè)進(jìn)程內(nèi)的消息。


wMsgFilterMin:指定被檢索的最小消息值,也就是消息范圍的下界限參數(shù)。


wMsgFilterMax:上界限參數(shù)。如果wMsgFilterMinwMsgFilterMax都為零,則不進(jìn)行消息過(guò)濾,GetMessage檢索所有有效的消息。


返回值


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


函數(shù)功能:


這個(gè)API函數(shù)用來(lái)從消息隊(duì)列中“摘取”一個(gè)消息,放到lpMsg所指的變量里。(注:如果所取窗口的消息隊(duì)列中沒(méi)有消息,則程序會(huì)暫停在GetMessage() 函數(shù)里,不會(huì)返回。)


再通俗一點(diǎn)講解GetMessage函數(shù):


當(dāng)程序執(zhí)行GetMessage()的時(shí)候,會(huì)檢查消息隊(duì)列,如果有消息在消息隊(duì)列里,它取出該消息,將該消息填充到lpMsg所指的MSG結(jié)構(gòu),并返回 TRUE值。如果此時(shí)消息隊(duì)列里沒(méi)有消息(消息隊(duì)列為空),它會(huì)將線程阻塞,也就是將控制權(quán)交給系統(tǒng),直到消息隊(duì)列中有內(nèi)容時(shí),才喚醒線程繼續(xù)執(zhí)行。


對(duì)于GetMessage()函數(shù),還有一點(diǎn)需要說(shuō)明,就是當(dāng)從消息隊(duì)列中取出的消息是WM_QUIT時(shí),函數(shù)返回值是0。我們一般利用這一點(diǎn)退出消息循環(huán),結(jié)束程序。


如語(yǔ)句:


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

 




……


2 、PeekMessage


函數(shù)原型:


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


參數(shù):


lpMsghWndwMsgFilterMinwMsgFilterMax這四個(gè)參數(shù)的意義和GetMessage對(duì)應(yīng)參數(shù)的意義相同,在此不再贅述。


wRemoveMsg:這個(gè)參數(shù)決定讀消息時(shí)是否刪除消息,可選值有PM_NOREMOVEPM_REMOVE。如果您選PM_NOREMOVE,執(zhí)行該函數(shù)后消息仍然留在消息隊(duì)列(我稱(chēng)為讀消息);如果您選PM_REMOVE,執(zhí)行該函數(shù)后將在消息隊(duì)列中移除該消息(同GetMessage())。


返回值:


消息隊(duì)列中有消息,返回值為TRUE;消息隊(duì)列中沒(méi)有消息,返回值為FALSE。


函數(shù)功能:


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


(一)、GetMessage()只能從消息隊(duì)列中取走消息,也就是說(shuō),GetMessage()執(zhí)行后,該消息將從消息隊(duì)列中移除。


PeekMessage()可以從消息隊(duì)列中取走消息。也可以讀消息,讓消息繼續(xù)留在消息隊(duì)列里。


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


3 WaitMessage


函數(shù)原型:


BOOL WaitMessage(VOID);


函數(shù)功能:


這個(gè)函數(shù)的作用是當(dāng)消息隊(duì)列中沒(méi)有消息時(shí),將控制權(quán)交給其它線程。該函數(shù)將會(huì)使線程掛起,直到消息隊(duì)列中又有新消息。


這個(gè)函數(shù)專(zhuān)門(mén)和PeekMessage配合使用,當(dāng)消息隊(duì)列中沒(méi)有消息時(shí),掛起線程,等待消息隊(duì)列中新消息的到來(lái),這樣可以減輕CPU的運(yùn)算負(fù)擔(dān)。


4 、TranslateMessage


函數(shù)原型:


BOOL TranslateMessageCONST MSG*lpMsg);


參數(shù):


IpMsg:指向MSG結(jié)構(gòu)的指針,該結(jié)構(gòu)是函數(shù)GetMessagePeekMessage從消息隊(duì)列里取得的消息。


函數(shù)功能:該函數(shù)將虛擬鍵消息轉(zhuǎn)換為字符消息。字符消息被寄送到調(diào)用線程的消息隊(duì)列里,當(dāng)下一次線程調(diào)用函數(shù)GetMessagePeekMessage時(shí)被讀出。


什么是虛擬鍵碼呢?Windows為了方便輸入管理,減少程序?qū)υO(shè)備的依賴(lài)性,將鍵盤(pán)上所有的按鍵都用一個(gè)兩位十六進(jìn)制數(shù)對(duì)應(yīng),這些數(shù)稱(chēng)為虛擬鍵碼。虛擬鍵碼一般以VK_開(kāi)頭,如:Esc鍵對(duì)應(yīng)的虛擬鍵碼是VK_ESCAPE;空格鍵對(duì)應(yīng)的虛擬鍵碼是VK_SPACEVK_LWIN與左邊的 Windows徽標(biāo)鍵相對(duì)應(yīng)。


當(dāng)一個(gè)按鍵被按下時(shí),會(huì)觸發(fā)WM_KEYDOWN消息, WM_KEYDOWN消息的wParam參數(shù)值就是虛擬鍵值。通過(guò)這個(gè)值就可以判斷哪個(gè)鍵被按下了。


為什么我們要把虛擬鍵碼轉(zhuǎn)換為字符碼呢?


比如我們按下了‘A’鍵,此時(shí)我們得到的字符可能是‘A’,也可能是小寫(xiě)的‘a’,這由當(dāng)時(shí)的大寫(xiě)狀態(tài)(Caps Lock)以及是否同時(shí)按下了Shift鍵有關(guān)。TranslateMessage()函數(shù)的作用就是不用我們考慮這些問(wèn)題,而是根據(jù)這些情況,自動(dòng)返回一個(gè)ASCII碼值,以方便用戶使用。


并不是所有的虛擬鍵碼值都會(huì)Translate成字符碼。字母、數(shù)字鍵都有字符碼相對(duì)應(yīng),而像方向箭頭鍵、F1—F12功能鍵這些按鍵就沒(méi)有字符碼相對(duì)應(yīng)。當(dāng)虛擬鍵碼需要轉(zhuǎn)化成字符碼時(shí),TranslateMessage()函數(shù)就在消息隊(duì)列里放一條WM_CHAR消息,WM_CHAR消息的 wParam參數(shù)值就是轉(zhuǎn)換后的ASCII碼值。


5、DispatchMessage


函數(shù)原型:


LONG DispatchMessage(CONST MSG *lpmsg);


函數(shù)功能:


它的作用很簡(jiǎn)單,就是分派消息到窗口的消息處理函數(shù)去執(zhí)行。


了解了這5個(gè)函數(shù),消息循環(huán)這段代碼就不難理解:


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


3、通過(guò)幾個(gè)Win32程序?qū)嵗M(jìn)一步深入理解Windows消息


例程2:對(duì)比使用GetMessagePeekMessage處理消息循環(huán)(見(jiàn)附帶源碼 工程M2


同工程M1,新建工程M2,將工程M1的源代碼全部拷貝到M2,并將消息循環(huán)部分的代碼改為:


//消息循環(huán)

 




MSG msg;

 




while(true)


{


if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) //從消息隊(duì)列中取消息


{


if(msg.message == WM_QUIT)


break;


TranslateMessage (&msg);      //轉(zhuǎn)換消息


DispatchMessage (&msg);      //派發(fā)消息


}


else


WaitMessage();

 




} //End of while(true)


編譯、運(yùn)行工程M2,觀察運(yùn)行效果,可以看出,使用PeekMessage處理消息循環(huán)同樣能夠達(dá)到與GetMessage相同的效果。


PeekMessage處理消息循環(huán)比GetMessage還要靈活,尤其體現(xiàn)在游戲編程中。游戲編程者不希望玩家在沒(méi)有鍵盤(pán)或鼠標(biāo)輸入時(shí)游戲是靜止不動(dòng)的,他們希望怪獸從后面沖出來(lái),圍攻玩家,追捕玩家。為了做到這樣的效果,需要這樣一種消息循環(huán):當(dāng)遇到需要處理的消息時(shí)去處理消息,其余的時(shí)間都讓程序代碼自動(dòng)產(chǎn)生激烈的場(chǎng)面。


下面的例程3將模擬這種消息循環(huán)。


例程3:模擬演示游戲編程如何進(jìn)行消息處理(見(jiàn)附帶源碼工程M3)。


詳細(xì)的代碼參看工程M3,編譯并執(zhí)行,您會(huì)發(fā)現(xiàn)程序不停地自己畫(huà)圓,這模擬游戲自動(dòng)產(chǎn)生激烈的場(chǎng)面。當(dāng)您按下上、下、左、右箭頭鍵,您就會(huì)發(fā)現(xiàn)您在相應(yīng)的方向畫(huà)線,這模擬游戲程序及時(shí)處理玩家的消息。


4、隊(duì)列消息和非隊(duì)列消息


Windows把消息分為兩種:一種是需要立即處理的消息,另一種是不需要立即處理的消息。


對(duì)于需要立即處理的消息,Windows直接把它送給窗口的消息處理函數(shù)進(jìn)行處理,這類(lèi)消息我們叫做非隊(duì)列消息;


而對(duì)于不需要立即處理的消息,Windows會(huì)把它發(fā)送給應(yīng)用程序的消息隊(duì)列進(jìn)行排隊(duì),由應(yīng)用程序逐個(gè)進(jìn)行處理,我們把這類(lèi)消息叫做隊(duì)列消息。


為了更清楚地說(shuō)明這個(gè)問(wèn)題,我們參看圖1

Windows消息編程


查看原圖(大圖)


1


1的解釋?zhuān)?/span>


1、Windows操作系統(tǒng)有一個(gè)消息隊(duì)列,它存放操作系統(tǒng)收到的消息。如:當(dāng)按鍵被按下,鍵盤(pán)會(huì)發(fā)送一個(gè)消息到操作系統(tǒng)的消息隊(duì)列。


2、操作系統(tǒng)把系統(tǒng)消息隊(duì)列中的消息分派到各個(gè)應(yīng)用程序的消息隊(duì)列。如果它是第1個(gè)應(yīng)用程序的消息,操作系統(tǒng)把它發(fā)給第1個(gè)應(yīng)用程序,把它放在第1個(gè)應(yīng)用程序的消息隊(duì)列;如果它是第2個(gè)應(yīng)用程序的消息,發(fā)送給第2個(gè)程序的消息隊(duì)列。


3、應(yīng)用程序的消息循環(huán)從自己的消息隊(duì)列中取消息,取出的消息調(diào)用窗口過(guò)程函數(shù)進(jìn)行處理。


4PostMessage是寄送消息,函數(shù)執(zhí)行后立即返回。寄送的消息是隊(duì)列消息,放在程序的消息隊(duì)列中排隊(duì)處理。一般來(lái)說(shuō),新寄送的消息排在消息隊(duì)列的末尾,這樣可以保證窗口以先進(jìn)先出的順序處理消息。


SendMessage是發(fā)送消息,它發(fā)出的消息是非隊(duì)列消息,直接調(diào)用窗口過(guò)程函數(shù)處理。SendMessage函數(shù)一直等消息處理完成后才返回。


我們有必要再專(zhuān)門(mén)學(xué)習(xí)一下SendMessagePostMessage函數(shù)。


SendMessage的函數(shù)原型:


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


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


PostMessage的函數(shù)原型:


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


該函數(shù)把一條消息放置到創(chuàng)建hWnd窗口的線程的消息隊(duì)列中,該函數(shù)不等消息被處理就馬上將控制返回。


從上面這兩個(gè)函數(shù),我們可以看出消息的發(fā)送方式和寄送方式的區(qū)別:被發(fā)送的消息會(huì)被立即處理,處理完畢后函數(shù)才返回;被寄送的消息不會(huì)被立即處理,他被放到一個(gè)先進(jìn)先出的隊(duì)列中,按次序等候處理,而且函數(shù)放置消息后立即返回。


以寄送方式發(fā)送的消息通常是與用戶輸入事件相對(duì)應(yīng)的,因?yàn)檫@些事件不是十分緊迫,可以進(jìn)行緩沖處理,例如鼠標(biāo)、鍵盤(pán)消息都是寄送消息。應(yīng)用程序調(diào)用系統(tǒng)函數(shù),系統(tǒng)一般會(huì)發(fā)送非隊(duì)列消息。例如,當(dāng)程序調(diào)用SetWindowPos,系統(tǒng)會(huì)發(fā)送WM_WINDOWPOSCHANGED消息。


例程M4,測(cè)試消息隊(duì)列的容量(見(jiàn)附帶源碼工程M4


代碼中已經(jīng)作了注解,編譯、運(yùn)行程序,您就會(huì)發(fā)現(xiàn)消息隊(duì)列的最大容量是10000


例程M5,用記事本查看消息隊(duì)列和窗口過(guò)程函數(shù)處理的消息


這個(gè)例程的出發(fā)點(diǎn)是利用記事本分別捕獲消息隊(duì)列中的消息和窗口過(guò)程函數(shù)處理過(guò)的消息。


該例程還演示了PostMessageSendMessage的不同。


由于該例程相對(duì)復(fù)雜一些,例程中的注解也相對(duì)多一些。編譯、運(yùn)行程序,彈出如下窗口:

Windows消息編程


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


打開(kāi)MessageQueue.txt文件,如下圖:

Windows消息編程


文件中記錄了消息隊(duì)列中的各個(gè)消息以及消息的ID號(hào),其中有一條消息是WM_POSTMESSAGE,這說(shuō)明PostMessage寄送的WM_POSTMESSAGE消息確實(shí)放到了消息隊(duì)列中。


再打開(kāi)MessageWndProc.txt文件,如下圖:

Windows消息編程


文件中記錄了窗口過(guò)程處理的各個(gè)消息和消息的ID號(hào),其中有兩條消息WM_POSTMESSAGEWM_SENDMESSAGE,這說(shuō)明了兩個(gè)問(wèn)題:WM_POSTMESSAGE消息從消息隊(duì)列取出,再次派發(fā)到窗口過(guò)程函數(shù)處理;SendMessage發(fā)送的WM_SENDMESSAGE消息,沒(méi)有經(jīng)過(guò)消息隊(duì)列,直接送到窗口過(guò)程函數(shù)處理。


5WM_COMMANDWM_NOTIFY


控件通知消息,是指這樣一種消息,一個(gè)窗口內(nèi)的控件發(fā)生了一些事情,需要通知父窗口。當(dāng)用戶與控件窗口交互時(shí),控件通知消息就會(huì)從控件窗口發(fā)送到它的主窗口,這種消息一般不是為了處理用戶命令,而是為了讓主窗口能夠改變控件。


WM_COMMANDWM_NOTIFY都是控件通知消息。


在最初的Windows 3.x中,還沒(méi)有WM_NOTIFY,只存在WM_COMMAND消息,wParam參數(shù)中包含一個(gè)通知碼和控件IDlParam中包含控件句柄。這樣一來(lái),wParamlParam都被填充了,沒(méi)有額外的空間來(lái)傳遞一些其它信息,如鼠標(biāo)按下的位置和時(shí)間。


為了解決這個(gè)問(wèn)題,Windows 3.x就提出了一個(gè)解決策略,那就是給一些消息添加一些附加消息,比如控件自畫(huà)用到的DRAWITEMSTRUCT等,這樣,不同的消息附加的內(nèi)容不同,結(jié)果是非?;靵y。


Win32中,微軟又提出了一個(gè)更好的解決方案,引進(jìn)了NMHDR結(jié)構(gòu)。這個(gè)結(jié)構(gòu)的引進(jìn)把消息統(tǒng)一起來(lái),利用它可以傳遞各種復(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)通知碼


}


使用這個(gè)結(jié)構(gòu),WM_NOTIFY還可以附帶更多的信息,您可以定義一個(gè)更大的結(jié)構(gòu),這個(gè)結(jié)構(gòu)的第一個(gè)元素就是NMHDR結(jié)構(gòu),在該元素的后面您還可以放置其它附加信息。由于在這個(gè)大結(jié)構(gòu)中,第一個(gè)成員是NMHDR,這樣一來(lái),我們就可以利用指向NMHDR的指針來(lái)指向這個(gè)結(jié)構(gòu),不論后面有沒(méi)有其它內(nèi)容。


可見(jiàn),WM_NOTIFYWM_COMMAND相比,是一種更靈活的消息格式,lParam中放的是一個(gè)稱(chēng)為NMHDR結(jié)構(gòu)的指針。在wParam中放的則是控件的ID。最初Windows 3.x就有的控件,如EditCombo,List,Button等,發(fā)送的控件通知消息的格式是WM_COMMAND;而后期的Win32通用控件,如List View,Image List,IP AddressTree View,Toolbar等,發(fā)送的都是WM_NOTIFY控件通知消息。


另外,當(dāng)用戶選擇菜單的一個(gè)命令項(xiàng),也會(huì)發(fā)送WM_COMMAND消息。


當(dāng)用戶選擇菜單的一個(gè)命令項(xiàng)或控件給父窗口發(fā)送通知消息,都可以使用WM_COMMAND消息。為了區(qū)分這兩種情況,規(guī)定它們有以下區(qū)別,如表2


消息來(lái)源


wParam (high word)


wParam (low word)


lParam

 




菜單


0


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


0

 




控件


控件定義的通知碼


控件ID


控件窗口的句柄


2


例程M6,演示菜單發(fā)出WM_COMMAND消息和子控件發(fā)送WM_COMMAND消息的區(qū)別(見(jiàn)附帶源碼工程M6


打開(kāi)VC++ 6.0,新建Win32 Application工程M6,然后在該工程中新建C++ Source File,文件名為M6,M6的文件內(nèi)容具體見(jiàn)例程M6。


在例程M6所在的路徑打開(kāi)M6文件夾,新建一個(gè)文本文檔,如下圖:

Windows消息編程


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

Windows消息編程

 




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

Windows消息編程


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

 



Windows消息編程

 




分別單擊“FirstButton”按鈕和“Menu1”菜單,會(huì)彈出相應(yīng)的提示消息框。


M6中對(duì)于WM_COMMAND消息的處理,源代碼如下:


case WM_COMMAND:


{


if(lParam == 0)


{


switch(LOWORD(wParam))


{


case IDM_MENU1:


MessageBox(NULL,"MENU1菜單被點(diǎn)擊","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,"按鈕被點(diǎn)擊","M6",MB_OK);


}


break;


}


}


}


break;


對(duì)于WM_COMMAND消息,因?yàn)椴藛魏妥涌丶寄苡|發(fā)。我們首先判斷lParam,如果lParam0,是菜單觸發(fā)的WM_COMMAND消息;如果lParam不為0,是子控件觸發(fā)的WM_COMMAND控件通知消息。對(duì)于菜單觸發(fā)的WM_COMMAND消息,我們?cè)偻ㄟ^(guò) (LOWORD(wParam))(菜單的標(biāo)識(shí)ID)判斷是哪個(gè)菜單觸發(fā)的消息;對(duì)于控件觸發(fā)的WM_COMMAND消息,我們通過(guò) (LOWORD(wParam))(控件ID)知道是哪個(gè)控件觸發(fā)的消息,而且通過(guò)(HIWORD(wParam))(控件定義的通知碼)知道控件到底觸發(fā)了什么消息。


本例程我們純手工添加并編輯資源文件M6.rc,之所以這樣做是為了讓您了解資源文件的實(shí)質(zhì)。實(shí)際編程中,您完全可以利用資源編輯器更加方便地添加、編輯資源文件,后面的例程將會(huì)演示說(shuō)明。


例程M7,演示WM_NOTIFY控件通知消息(見(jiàn)附帶源碼 工程M7


WM_NOTIFY消息是通用控件發(fā)送給其父窗口的消息,其中參數(shù)wParam 是發(fā)送消息的通用控件的ID,參數(shù)lParam 是一個(gè)指針,這個(gè)指針指向一個(gè) 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;


打開(kāi)VC++ 6.0,新建Win32 Application工程M7,然后在該工程中新建C++ Source File,文件名為M7,M7的文件內(nèi)容具體見(jiàn)例程M7。


下面,我們利用資源編輯器添加資源。單擊“文件”->“新建”,在“新建”對(duì)話框中選中“Resource Script”,文件名為“M7”,如下圖:

Windows消息編程


查看原圖(大圖)

 




單擊“確定”,添加M7資源文件。


右擊“M7.RC”文件夾,選中“Insert…”菜單項(xiàng),如下圖:

 



Windows消息編程

 




彈出“插入資源”對(duì)話框,

Windows消息編程

 




選中“Dialog”,點(diǎn)擊“新建”按鈕,新建一個(gè)對(duì)話框資源。


右擊新建的“IDD_DIALOG1”,在屬性對(duì)話框中將ID改為“IDC_DIALOG”,關(guān)閉屬性框。

 



Windows消息編程


查看原圖(大圖)

 




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

 



Windows消息編程


查看原圖(大圖)

 




并且把列表控件改為“Report”類(lèi)型,如下圖:

 



Windows消息編程

 




編輯并運(yùn)行程序,程序運(yùn)行會(huì)彈出如下對(duì)話框:

 



Windows消息編程


查看原圖(大圖)


分別用鼠標(biāo)雙擊第一行或第二行,會(huì)彈出相應(yīng)消息框。


程序代碼都有詳細(xì)注釋?zhuān)梢蚤喿x代碼,細(xì)細(xì)體會(huì)WM_NOTIFY控件通知消息。


6、MFC的消息映射


使用MFC編程時(shí),消息發(fā)送和處理的本質(zhì)和Win32相同,但是,它對(duì)消息處理進(jìn)行了封裝,簡(jiǎn)化了程序員編程時(shí)消息處理的復(fù)雜性,它通過(guò)消息映射機(jī)制來(lái)處理消息,程序員不必去設(shè)計(jì)和實(shí)現(xiàn)自己的窗口過(guò)程。


說(shuō)白了,MFC中的消息映射機(jī)制實(shí)質(zhì)是一張巨大的消息及其處理函數(shù)對(duì)應(yīng)表。消息映射基本上分為兩大部分:


在頭文件(.h)中有一個(gè)宏DECLARE_MESSAGE_MAP(),它放在類(lèi)的末尾,是一個(gè)public屬性的;與之對(duì)應(yīng)的是在實(shí)現(xiàn)部分(.cpp)增加了一個(gè)消息映射表,內(nèi)容如下:


BEGIN_MASSAGE_MAP(當(dāng)前類(lèi),當(dāng)前類(lèi)的基類(lèi))


//{{AFX_MSG_MAP(CMainFrame)


消息的入口項(xiàng)


//}}AFX_MSG_MAP


END_MESSAGE_MAP()


但是僅是這兩項(xiàng)還不足以完成一條消息,要是一個(gè)消息工作,必須還有以下3個(gè)部分去協(xié)作:


1、在類(lèi)的定義中加入相應(yīng)的函數(shù)聲明;


2、在類(lèi)的消息映射表中加入相應(yīng)的消息映射入口項(xiàng);


3、在類(lèi)的實(shí)現(xiàn)中加入相應(yīng)的函數(shù)體;


消息的添加


1)、利用Class Wizard實(shí)現(xiàn)自動(dòng)添加


在菜單中選擇View -> Class Wizard激活Class Wizard,選擇Message Map標(biāo)簽,從Class name組合框中選取我們想要添加消息的類(lèi)。在Object IDs列表框中,選取類(lèi)的名稱(chēng)。此時(shí),Messages列表框顯示該類(lèi)的可重載成員函數(shù)和窗口消息??芍剌d成員函數(shù)顯示在列表的上部,以實(shí)際虛構(gòu)成員函數(shù)的大小寫(xiě)字母來(lái)表示。其他為窗口消息,以大寫(xiě)字母出現(xiàn)。選中我們要添加的消息,單擊Add Funtion按鈕,Class Wizard自動(dòng)將該消息添加進(jìn)來(lái)。


有時(shí)候,我們想要添加的消息在Message列表中找不到,我們可以利用Class WizardClass Info標(biāo)簽以擴(kuò)展消息列表。在該頁(yè)中,找到Message Filter組合框,通過(guò)它可以改變首頁(yè)中Messages列表框中的選項(xiàng)。


2)、手動(dòng)添加消息


如果Messages列表框中確實(shí)沒(méi)有我們想要的消息,就需要我們手工添加:


1)在類(lèi)的.h文件中添加處理函數(shù)的聲明,緊接著在//}}AFX_MSG行之后加入聲明,注意,一定要以afx_msg開(kāi)頭。


通常,添加處理函數(shù)聲明的最好的地方是源代碼中Class Wizard維護(hù)的表的下面,在它標(biāo)記其領(lǐng)域的{{ }}括弧外面。這些括弧中的任何東西都有可能會(huì)被Class Wizard銷(xiāo)毀。


2)接著,在用戶類(lèi)的.cpp文件中找到//}}AFX_MSG_MAP行,緊接在它之后加入消息入口項(xiàng)。同樣,也放在{{ }}外面。


3)最后,在該文件中添加消息處理函數(shù)的實(shí)體。


對(duì)于能夠使用Class Wizard添加的消息,盡量使用Class Wizard添加,以減少我們的工作量;對(duì)于不能使用Class Wizard添加的消息和自定義消息,需要手動(dòng)添加。總體說(shuō)來(lái),MFC的消息編程對(duì)用戶來(lái)說(shuō),相對(duì)比較簡(jiǎn)單,在此不再使用實(shí)例演示。


7、消息反射機(jī)制


什么叫消息反射?


父窗口將控件發(fā)給它的通知消息,反射回控件進(jìn)行處理(即讓控件處理這個(gè)消息),這種通知消息讓控件自己處理的機(jī)制叫做消息反射機(jī)制。


通過(guò)前面的學(xué)習(xí)我們知道,一般情況下,控件向父窗口發(fā)送通知消息,由父窗口處理這些通知消息。這樣,父窗口(通常是一個(gè)對(duì)話框)會(huì)對(duì)這些消息進(jìn)行處理,換句話說(shuō),控件的這些消息處理必須在父窗口類(lèi)體內(nèi),每當(dāng)我們添加子控件的時(shí)候,就要在父窗口類(lèi)中復(fù)制這些代碼。很明顯,這對(duì)代碼的維護(hù)和移植帶來(lái)了不便,而且,明顯背離C++的對(duì)象編程原則。


4.0版開(kāi)始,MFC提供了一種消息反射機(jī)制(Message Reflection),可以把控件通知消息反射回控件。具體地講,對(duì)于反射消息,如果控件有該消息的處理函數(shù),那么就由控件自己處理該消息,如果控件不處理該消息,則框架會(huì)把該消息繼續(xù)送給父窗口,這樣父窗口繼續(xù)處理該消息??梢?jiàn),新的消息反射機(jī)制并不破壞原來(lái)的通知消息處理機(jī)制。


消息反射機(jī)制為控件提供了處理通知消息的機(jī)會(huì),這是很有用的。如果按傳統(tǒng)的方法,由父窗口來(lái)處理這個(gè)消息,則加重了控件對(duì)象對(duì)父窗口的依賴(lài)程度,這顯然違背了面向?qū)ο蟮脑瓌t。若由控件自己處理消息,則使得控件對(duì)象具有更大的獨(dú)立性,大大方便了代碼的維護(hù)和移植。


實(shí)例M8:簡(jiǎn)單地演示MFC的消息反射機(jī)制。(見(jiàn)附帶源碼 工程M8


打開(kāi)VC++ 6.0,新建一個(gè)基于對(duì)話框的工程M8。


在該工程中,新建一個(gè)CMyEdit類(lèi),基類(lèi)是CEdit。接著,在該類(lèi)中添加三個(gè)變量,如下:


private:


CBrush m_brBkgnd;


COLORREF m_clrBkgnd;


COLORREF m_clrText;


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


{


m_clrBkgnd = RGB( 255, 255, 0 );


m_clrText = RGB( 0, 0, 0 );


m_brBkgnd.CreateSolidBrush(RGB( 150, 150, 150) );


}


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

Windows消息編程


查看原圖(大圖)

 




消息反射函數(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è)置背景顏色

 




//請(qǐng)注意,在我們改寫(xiě)該函數(shù)的內(nèi)容前,函數(shù)返回NULL,即return NULL;

 




//函數(shù)返回NULL將會(huì)執(zhí)行父窗口的CtlColor函數(shù),而不執(zhí)行控件的CtlColor函數(shù)

 




//所以,我們讓函數(shù)返回背景刷,而不返回NULL,目的就是為了實(shí)現(xiàn)消息反射


return m_brBkgnd; //返回背景刷


}


IDD_M8_DIALOG對(duì)話框中添加一個(gè)Edit控件,使用ClassWizard給該Edit控件添加一個(gè)CMyEdit類(lèi)型的變量m_edit1,把Edit控件和CMyEdit關(guān)聯(lián)起來(lái)。

Windows消息編程


編譯,運(yùn)行程序,觀察運(yùn)行效果。
就寫(xiě)這些吧,水平有限,希望能對(duì)您有所幫助。

 

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

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多