深刻理解Windows消息機制編程
今天咱們來學一學Windows消息機制,咱們知道在傳統的C語音程序中,當咱們須要打開一個文件時,咱們能夠調用fopen()函數,這個函數最後又會調用操做系統提供的函數以此來打開文件。而在Windows編程中,不只用戶能夠調用系統的API函數,反之,系統也能夠調用應用程序,而這些調用就是經過Windows的消息機制來實現的。Windows程序設計是一種徹底不一樣於傳統的DOS方式的程序設計方法,它是一種事件驅動的程序設計模式,主要是基於消息的。小程序
1、那麼消息到底是What 嘞?windows
消息系統對於一個win32程序來講十分重要,它是一個程序運行的動力源泉。一個消息,是系統定義的一個32位的值,他惟一的定義了一個事件,向 Windows發出一個通知,告訴應用程序某個事情發生了。例如,單擊鼠標、改變窗口尺寸、按下鍵盤上的一個鍵都會使Windows發送一個消息給應用程序的消息隊列(下面會講到)中,而後應用程序再從消息隊列中取出消息並進行相應的響應。在這個處理的過程當中,操做系統也會給應用程序「發送消息」,而所謂的發送消息--------實際上就是操做系統調用程序中的一個專門負責處理消息的函數,這個函數稱爲窗口過程。
消息自己是做爲一個記錄傳遞給應用程序的,這個記錄中包含了消息的類型以及其餘信息。例如,對於單擊鼠標所產生的消息來講,這個記錄中包含了單擊鼠標時的座標。這個記錄類型叫作MSG,MSG含有來自windows應用程序消息隊列的消息信息,在Windows中MSG結構體定義以下:設計模式
typedef struct tagMsg
{
HWND hwnd; //接受該消息的窗口句柄
UINT message; //消息常量標識符,也就是咱們一般所說的消息號
WPARAM wParam; //32位消息的特定附加信息,確切含義依賴於消息值
LPARAM lParam; //32位消息的特定附加信息,確切含義依賴於消息值
DWORD time; //消息建立時的時間
POINT pt; //消息建立時的鼠標/光標在屏幕座標系中的位置
}MSG;網絡
2、What is消息隊列?函數
在Windows編程中,每個Windows應用程序開始執行後,系統都會爲該程序建立一個消息隊列,這個消息隊列用來存放該應用程序所建立的窗口的信息。例如,當咱們按下鼠標右鍵的時候,這時會產生一個WM_RBUTTONDOWN消息,系統會自動將這個消息放進當前窗口所屬的應用程序的消息隊列中,等待應用程序的結束。Windows將產生的消息以此放進消息隊列中,應用程序則經過一個消息循環不斷的從該消息隊列中讀取消息,並作出響應(後面會詳細講述消息處理過程。。。。)ui
3、消息中的家庭成員?
經過前面所羅列的MSG結構體,咱們是否是會對消息結構裏邊含有的東東有了一個比較清楚的認識呢?若是尚未,不要緊!!呵呵,那麼我再次對那些咋一看就會淚奔的變量作出詳細的解釋:
hwnd - - - 一個32位的窗口句柄(個人PC是32 位的^_^),它表示的是消息所屬的窗口。咱們一般開發的程序都是窗口應用程序,通常一個消息都是和某個窗口相關聯的。好比咱們在某個活動窗口按下鼠標右鍵,此時產生的消息就是發送給該活動窗口的。窗口能夠是任何類型的屏幕對象,由於Win32可以維護大多數可視對象的句柄(窗口、對話框、按鈕、編輯框等)。spa
(補充一下:「句柄」---在Windows程序中,有各類各樣的資源,系統在建立這些資源的時候,都會爲他們分配內存,並返回標識這些資源的標識號,這個標識號就是句柄)
message- - - -一個消息的標識符,用於區別其餘消息的常量值,這些常量能夠是Windows單元中預約義的常量,也能夠是自定義的常量。在Windows中消息是由一個數值表示的,不一樣的消息對應不一樣的數值。但因爲當這些消息種類多到足以挑戰咱們的IQ,因此聰明的程序開發者便想到將這些數值定義爲WM_XXX宏的形式。例如,鼠標左鍵按下的消息--WM_LBUTTONDOWN,鍵盤按下消息--WM_KEYDOWN,字符消息--WM_CHAR,等等。。。。消息標識符以常量命名的方式指出消息的含義。當窗口過程接收到消息以後,他就會使用消息標識符來決定如何處理消息。例如、WM_PAINT告訴窗口過程窗體客戶區被改變了須要重繪。符號常量指定系統消息屬於的類別,其前綴指明瞭處理解釋消息的窗體的類型。
wParam和lParam- - - 用於指定消息的附加信息。例如,當咱們收到一個鍵盤按下消息的時候,message成員變量的值就是WM_KEYDOWN,可是用戶到底按下的是哪個按鍵,咱們就得拜託這二位,由他們來告知咱們具體的信息。操作系統
time和pt- - -這倆兄弟分別被用來表示消息投遞到消息隊列中的時間和鼠標當前的位置,通常狀況下不怎麼使用(但不表明沒用)線程
4、see see 消息標識符
系統保留消息標識符的值在0x0000在0x03ff(WM_USER-1)範圍。這些值被系統定義消息使用。應用程序不能使用這些值給本身的消息。應用程序消息從WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF;WM_USER到 0X7FFF範圍的消息由應用程序本身使用;0XC000到0XFFFF範圍的消息用來和其餘應用程序通訊,在此只是羅列一些具備標誌性的消息值:
WM_NULL---0x0000 空消息。
0x0001----0x0087 主要是窗口消息。
0x00A0----0x00A9 非客戶區消息
0x0100----0x0108 鍵盤消息
0x0111----0x0126 菜單消息
0x0132----0x0138 顏色控制消息
0x0200----0x020A 鼠標消息
0x0211----0x0213 菜單循環消息
0x0220----0x0230 多文檔消息
0x03E0----0x03E8 DDE消息
0x0400 WM_USER
0x8000 WM_APP
0x0400----0x7FFF 應用程序自定義私有消息
5、原來消息也有分類啊
windows中的消息雖然不少,可是種類並不繁雜,
//*************************************
大致上有3種: 窗口消息、 ******
命令消息、 ******
控件通知消息。*****
****************************************//
(1) 窗口消息- - - -大概是系統中最爲常見的消息,它是指由操做系統和控制其餘窗口的窗口所使用的消息。例如CreateWindow、DestroyWindow和MoveWindow等都會激發窗口消息,還有咱們在上面談到的單擊鼠標所產生的消息也是一種窗口消息。
(2) 命令消息- - - - 這是一種特殊的窗口消息,他用來處理從一個窗口發送到另外一個窗口的用戶請求,例如按下一個按鈕,他就會向主窗口發送一個命令消息。
(3) 控件通知消息- - - 其實它是這樣滴,當一個窗口內的子控件發生了一些事情,而這些是須要通知父窗口的,此刻它就上場啦。通知消息只適用於標準的窗口控件如按鈕、列表框、組合框、編輯框,以及Windows公共控件如樹狀視圖、列表視圖等。
例如,單擊或雙擊一個控件、在控件中選擇部分文本、操做控件的滾動條都會產生通知消息-------她相似於命令消息,那麼控件通知消息就會從控件窗口發送到它的主窗口。可是這種消息的存在並非爲了處理用戶命令,而是爲了讓主窗口可以改變控件,例如加載、顯示數據。
再例如,按下一個按鈕,他向父窗口發送的消息也能夠看做是一個控件通知消息;單擊鼠標所產生的消息能夠由主窗口直接處理,而後交給控件窗口處理。其中窗口消息及控件通知消息主要由窗口類即直接或間接由CWND類派生類處理。相對窗口消息及控件通知消息而言,命令消息的處理對象範圍就廣得多,它不只能夠由窗口類處理,還能夠由文擋類,文檔模板類及應用類所處理。
因爲控件通知消息很重要的,編程者用的也比較多,可是具體的含義每每令初學者暈頭轉向,因此我決定把常見的幾個列出來供你們參考:
按扭控件
BN_CLICKED 用戶單擊了按鈕
BN_DISABLE 按鈕被禁止
BN_DOUBLECLICKED 用戶雙擊了按鈕
BN_HILITE 用/戶加亮了按鈕
BN_PAINT 按鈕應當重畫
BN_UNHILITE 加亮應當去掉
組合框控件
CBN_CLOSEUP 組合框的列表框被關閉
CBN_DBLCLK 用戶雙擊了一個字符串
CBN_DROPDOWN 組合框的列表框被拉出
CBN_EDITCHANGE 用戶修改了編輯框中的文本
CBN_EDITUPDATE 編輯框內的文本即將更新
CBN_ERRSPACE 組合框內存不足
CBN_KILLFOCUS 組合框失去輸入焦點
CBN_SELCHANGE 在組合框中選擇了一項
CBN_SELENDCANCEL 用戶的選擇應當被取消
CBN_SELENDOK 用戶的選擇是合法的
CBN_SETFOCUS 組合框得到輸入焦點
編輯框控件
EN_CHANGE 編輯框中的文本己更新
EN_ERRSPACE 編輯框內存不足
EN_HSCROLL 用戶點擊了水平滾動條
EN_KILLFOCUS 編輯框正在失去輸入焦點
EN_MAXTEXT 插入的內容被截斷
EN_SETFOCUS 編輯框得到輸入焦點
EN_UPDATE 編輯框中的文本將要更新
EN_VSCROLL 用戶點擊了垂直滾動條消息含義
列表框控件
LBN_DBLCLK 用戶雙擊了一項
LBN_ERRSPACE 列表框內存不夠
LBN_KILLFOCUS 列表框正在失去輸入焦點
LBN_SELCANCEL 選擇被取消
LBN_SELCHANGE 選擇了另外一項
LBN_SETFOCUS 列表框得到輸入焦點
/***************************
*****眼睛疼,休息下,作作眼保操吧【上下左右,左右上下,】
*****話說前面咱們已經講過了消息隊列,那麼OK,下面來說講隊列消息和非隊列消息。注意啦!注意啦!!隊列消息和消息隊列不要搞混了(此刻我想到了函數指針和指針*****函數,多麼淡淡疼的問題)。FH很少說,開始吧。。。。
***************************/
6、隊列消息和非隊列消息
從消息的發送途徑來看,Windows程序中的消息能夠分紅2種:隊列消息和非隊列消息,也有叫「進隊消息」和「不進隊消息」。
消息隊列能夠分紅系統消息隊列和線程消息隊列。系統消息隊列由Windows維護,線程消息隊列則由每一個GUI線程本身進行維護,爲避免給non-GUI現成建立消息隊列,全部線程產生時並無消息隊列,僅當線程第一次調用GDI函數時系統纔給線程建立一個消息隊列。(本段內容貌似應該放在消息隊列時講,但我的以爲放在這裏很方便理解下面的內容)
(1)、隊列消息送到系統消息隊列,而後到線程消息隊列;
對於隊列消息,最多見的是鼠標和鍵盤觸發的消息,例如WM_MOUSERMOVE,WM_CHAR等消息,還有一些其它的消息,例如:WM_PAINT、 WM_TIMER和WM_QUIT。當鼠標、鍵盤事件被觸發後,相應的鼠標或鍵盤驅動程序就會把這些事件轉換成相應的消息,而後輸送到系統消息隊列,由 Windows系統去進行處理。Windows系統則在適當的時機,從系統消息隊列中取出一個消息,根據前面咱們所說的MSG消息結構肯定消息是要被送往那個窗口,而後把取出的消息送往建立窗口的線程的相應隊列,下面的事情就該由線程消息隊列操心了,Windows開始忙本身的事情去了。線程看到本身的消息隊列中有消息,就從隊列中取出來,經過操做系統發送到合適的窗口過程去處理。
通常來說,系統老是將消息Post在消息隊列的末尾。這樣保證窗口以先進先出的順序接受消息。然而,WM_PAINT是一個例外,同一個窗口的多個 WM_PAINT被合併成一個 WM_PAINT 消息, 合併全部的無效區域到一個無效區域。合併WM_PAIN的目的是爲了減小刷新窗口的次數。
(2)、非隊列消息直接送給目的窗口過程。
非隊列消息將會繞過系統隊列和消息隊列,直接將消息發送到窗口過程,。系統發送非隊列消息通知窗口,系統發送消息通知窗口。例如,當用戶激活一個窗口系統發送WM_ACTIVATE,WM_SETFOCUS, and WM_SETCURSOR。這些消息通知窗口它被激活了。非隊列消息也能夠由當應用程序調用系統函數產生。例如,當程序調用SetWindowPos系統發送WM_WINDOWPOSCHANGED消息。一些函數也發送非隊列消息,例以下面咱們要談到的函數。
7、一個簡單的Win32程序
Now,讓咱們經過這個程序來更進一步的理解Windows消息!!!
//一個簡單的Win32應用程序 //經過這個簡單的實例講解Windows消息是如何傳遞的 #include <windows.h> //聲明窗口過程函數 LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); //定義一個全局變量,做爲窗口類名 TCHAR szClassName[] = TEXT("SimpleWin32"); //應用程序主函數 int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow) { /***********注意如下幾步是windows窗口建立的流程*********************/
//****1.設計一個窗口類****
//(說明:在這裏須要本身查一下 _WNDCLASS結構體,不過裏邊的成員就是如下被初始化的那些變量)
WNDCLASS wndclass;
//typedef struct _WNDCLASS{
wndclass.style = CS_HREDRAW|CS_VREDRAW; //當窗口水平方向的寬度和垂直方向的高度變化時重繪整個窗口 wndclass.lpfnWndProc = WndProc;//關聯窗口過程函數 wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance;//實例句柄 wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION);//圖標 wndclass.hCursor = LoadCursor(NULL,IDC_ARROW);//光標 wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//畫刷 wndclass.lpszMenuName = NULL;//菜單 wndclass.lpszClassName = szClassName;//類名稱
//}; //****2.註冊窗口類 if(!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("RegisterClass Fail!"), szClassName, MB_ICONERROR); return 0; } //****3.建立一個窗口 HWND hwnd; hwnd = CreateWindow(szClassName,//窗口類名稱 TEXT ("The Simple Win32 Application"),//窗口標題 WS_OVERLAPPEDWINDOW,//窗口風格,即一般咱們使用的windows窗口樣式 CW_USEDEFAULT,//指定窗口的初始水平位置,即屏幕座標系的窗口的左上角的X座標 CW_USEDEFAULT,//指定窗口的初始垂直位置,即屏幕座標系的窗口的左上角的Y座標 CW_USEDEFAULT,//窗口的寬度 CW_USEDEFAULT,//窗口的高度 NULL,//父窗口句柄 NULL,//窗口菜單句柄 hInstance,//實例句柄 NULL); //****4.顯示窗口
ShowWindow(hwnd,iCmdShow);
//**** 5.更新窗口 UpdateWindow(hwnd); /***********************以上爲整個窗口建立的流程**************************/
//消息循環 MSG msg; while(GetMessage(&msg,NULL,0,0))//從消息隊列中取消息 { TranslateMessage (&msg); //轉換消息 DispatchMessage (&msg); //派發消息 } return msg.wParam; } //消息處理函數 //參數:窗口句柄,消息,消息參數,消息參數 LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { //處理感興趣的消息 switch (message) { case WM_DESTROY: //當用戶關閉窗口,窗口銷燬,程序需結束,發退出消息,以退出消息循環 PostQuitMessage(0); return 0; } //其餘消息交給由系統提供的缺省處理函數 return ::DefWindowProc (hwnd, message, wParam, lParam); }
這是一個很是簡單的Win32小程序,編譯運行會顯示一個窗口,關閉窗口程序會結束運行。這段代碼涉及GetMessage,TranslateMessage,DispatchMessage這三個函數,相關函數還有PeekMessage,WaitMessage等。在此,咱們先對這些與消息相關的函數進行簡單講解。
消息的發送
把一個消息發送到窗口有3種方式:發送、寄送和廣播。
發送消息的函數有SendMessage、SendMessageCallback、SendNotifyMessage、 SendMessageTimeout;
寄送消息的函數主要有PostMessage、PostThreadMessage、 PostQuitMessage;
廣播消息的函數我知道的只有BroadcastSystemMessage、 BroadcastSystemMessageEx。
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&/
SendMessage的原型以下:LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam),
這個函數主要是向一個或多個窗口發送一條消息,一直等到消息被處理以後纔會返回。不過須要注意的是,若是接收消息的窗口是同一個應用程序的一部分,那麼這個窗口的窗口函數就被做爲一個子程序立刻被調用;若是接收消息的窗口是被另外的線程所建立的,那麼窗口系統就切換到相應的線程而且調用相應的窗口函數,這條消息不會被放進目標應用程序隊列中。函數的返回值是由接收消息的窗口的窗口函數返回,返回的值取決於被髮送的消息。
PostMessage的原型以下:BOOL PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam),
該函數把一條消息放置到建立hWnd窗口的線程的消息隊列中,該函數不等消息被處理就立刻將控制返回。須要注意的是,若是hWnd參數爲 HWND_BROADCAST,那麼,消息將被寄送給系統中的全部的重疊窗口和彈出窗口,可是子窗口不會收到該消息;若是hWnd參數爲NULL,則該函數相似於將dwThreadID參數設置成當前線程的標誌來調用PostThreadMEssage函數。
從上面的這2個具備表明性的函數,咱們能夠看出消息的發送方式和寄送方式的區別所在:被髮送的消息會被當即處理,處理完畢後函數纔會返回;被寄送的消息不會被當即處理,他被放到一個先進先出的隊列中,一直等到應用程序空線的時候纔會被處理,不過函數放置消息後當即返回。
實際上,發送消息到一個窗口處理過程和直接調用窗口處理過程之間並無太大的區別,他們直接的惟一區別就在於你能夠要求操做系統截獲全部被髮送的消息,可是不可以截獲對窗口處理過程的直接調用。
以寄送方式發送的消息一般是與用戶輸入事件相對應的,由於這些事件不是十分緊迫,能夠進行緩慢的緩衝處理,例如鼠標、鍵盤消息會被寄送,而按鈕等消息則會被髮送。
廣播消息用得比較少,BroadcastSystemMessage函數原型以下:
long BroadcastSystemMessage(DWORDdwFlags,LPDWORD lpdwRecipients,UINT uiMessage,WPARAM wParam,LPARAM lParam);該函數能夠向指定的接收者發送一條消息,這些接收者能夠是應用程序、可安裝的驅動程序、網絡驅動程序、系統級別的設備驅動消息和他們的任意組合。須要注意的是,若是dwFlags參數是BSF_QUERY而且至少一個接收者返回了BROADCAST_QUERY_DENY,則返回值爲0,若是沒有指定BSF_QUERY,則函數將消息發送給全部接收者,而且忽略其返回值。
消息的接收
消息的接收主要有3個函數:GetMessage、PeekMessage、WaitMessage。
GetMessage原型以下:BOOL GetMessage(LPMSG lpMsg,HWND hWnd,UINTwMsgFilterMin,UINT wMsgFilterMax);
該函數用來獲取與hWnd參數所指定的窗口相關的且wMsgFilterMin和wMsgFilterMax參數所給出的消息值範圍內的消息。須要注意的是,若是hWnd爲NULL,則GetMessage獲取屬於調用該函數應用程序的任一窗口的消息,若是 wMsgFilterMin和wMsgFilterMax都是0,則GetMessage就返回全部可獲得的消息。函數獲取以後將刪除消息隊列中的除 WM_PAINT消息以外的其餘消息,至於WM_PAINT則只有在其處理以後才被刪除。
PeekMessage原型以下:BOOL PeekMessage(LPMSG lpMsg,HWNDhWnd,UINT wMsgFilterMin,UINT wMsgFilterMax,UINT wRemoveMsg);
該函數用於查看應用程序的消息隊列,若是其中有消息就將其放入lpMsg所指的結構中,不過,與GetMessage不一樣的是,PeekMessage函數不會等到有消息放入隊列時才返回。一樣,若是hWnd爲NULL,則PeekMessage獲取屬於調用該函數應用程序的任一窗口的消息,若是hWnd=-1,那麼函數只返回把hWnd參數爲NULL的PostAppMessage函數送去的消息。若是 wMsgFilterMin和wMsgFilterMax都是0,則PeekMessage就返回全部可獲得的消息。函數獲取以後將視最後一個參數來決定是否刪除消息隊列中的除 WM_PAINT消息以外的其餘消息,至於WM_PAINT則只有在其處理以後才被刪除。
WaitMessage原型以下:BOOL WaitMessage();當一個應用程序無事可作時,該函數就將控制權交給另外的應用程序,同時將該應用程序掛起,直到一個新的消息被放入應用程序的隊列之中才返回。
消息循環
while(GetMessage(&msg, NULL, 0, 0))
{
if(!TranslateAccelerator(msg.hWnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
首先,GetMessage從進程的主線程的消息隊列中獲取一個消息並將它複製到MSG結構,若是隊列中沒有消息,則GetMessage函數將等待一個消息的到來之後才返回。若是你將一個窗口句柄做爲第二個參數傳入GetMessage,那麼只有指定窗口的的消息能夠從隊列中得到。GetMessage也能夠從消息隊列中過濾消息只接受消息隊列中落在範圍內的消息。這時候就要利用GetMessage/PeekMessage指定一個消息過濾器。這個過濾器是一個消息標識符的範圍或者是一個窗體句柄,或者二者同時指定。當應用程序要查找一個後入消息隊列的消息是頗有用。WM_KEYFIRST 和 WM_KEYLAST 常量用於接受全部的鍵盤消息。 WM_MOUSEFIRST 和 WM_MOUSELAST 常量用於接受全部的鼠標消息。
而後TranslateAccelerator判斷該消息是否是一個按鍵消息而且是一個加速鍵消息,若是是,則該函數將把幾個按鍵消息轉換成一個加速鍵消息傳遞給窗口的回調函數。處理了加速鍵以後,函數TranslateMessage將把兩個按鍵消息WM_KEYDOWN和WM_KEYUP轉換成一個 WM_CHAR,不過須要注意的是,消息WM_KEYDOWN,WM_KEYUP仍然將傳遞給窗口的回調函數。
處理完以後,DispatchMessage函數將把此消息發送給該消息指定的窗口中已設定的回調函數。若是消息是WM_QUIT,則 GetMessage返回0,從而退出循環體。應用程序可使用PostQuitMessage來結束本身的消息循環。一般在主窗口的 WM_DESTROY消息中調用。
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - -- - - - - - - - - - --
下面咱們舉一個常見的小例子來講明這個消息泵的運用:
if (::PeekMessage(&msg, m_hWnd, WM_KEYFIRST,WM_KEYLAST, PM_REMOVE))
{
if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)...
}
這裏咱們接受全部的鍵盤消息,因此就用WM_KEYFIRST 和 WM_KEYLAST做爲參數。最後一個參數能夠是PM_NOREMOVE 或者 PM_REMOVE,表示消息信息是否應該從消息隊列中刪除。
因此這段小代碼就是判斷是否按下了Esc鍵,若是是就進行處理。
- - - - - - - - - - - - -- - - - -- -- - - - - - - - - - - - -- - - - - -- - - - - - /
***************************************************************************此處有休息*************************************************************************
*****************************************************************************************************************************************************************
消息處理函數(也就是文章開頭提到的窗口過程)
窗口過程是一個用於處理全部發送到這個窗口的消息的函數。任何一個窗口類都有一個窗口過程。同一個類的窗口使用一樣的窗口過程來響應消息。系統發送消息給窗口過程將消息數據做爲參數傳遞給他,消息到來以後,按照消息類型排序進行處理,其中的參數則用來區分不一樣的消息,窗口過程使用參數產生合適行爲。
一個窗口過程不常常忽略消息,若是他不處理,它會將消息傳回到執行默認的處理。窗口過程經過調用DefWindowProc來作這個處理。窗口過程必須 return一個值做爲它的消息處理結果。大多數窗口只處理小部分消息和將其餘的經過DefWindowProc傳遞給系統作默認的處理。窗口過程被全部屬於同一個類的窗口共享,能爲不一樣的窗口處理消息。下面咱們來看一下具體的實例:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
TCHAR szHello[MAX_LOADSTRING];
LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here
RECT rt;
GetClientRect(hWnd, &rt);
DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
消息分流器
一般的窗口過程是經過一個switch語句來實現的,這個事情很煩,有沒有更簡便的方法呢?有,那就是消息分流器,利用消息分流器,咱們能夠把switch語句分紅更小的函數,每個消息都對應一個小函數,這樣作的好處就是對消息更容易管理。
之因此被稱爲消息分流器,就是由於它能夠對任何消息進行分流。下面咱們作一個函數就很清楚了:
void MsgCracker(HWND hWnd,int id,HWND hWndCtl,UINT codeNotify)
{
switch(id)
{
case ID_A:
if(codeNotify==EN_CHANGE)
break;
case ID_B:
if(codeNotify==BN_CLICKED)
break;
.
}
}
而後咱們修改一下窗口過程:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
HANDLE_MSG(hWnd,WM_COMMAND,MsgCracker);
HANDLE_MSG(hWnd,WM_DESTROY,MsgCracker);
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
在WindowsX.h中定義了以下的HANDLE_MSG宏:
#define HANDLE_MSG(hwnd,msg,fn) \
switch(msg): return HANDLE_##msg((hwnd),(wParam),(lParam),(fn));
實際上,HANDLE_WM_XXXX都是宏,例如:HANDLE_MSG(hWnd,WM_COMMAND,MsgCracker);將被轉換成以下定義:
#define HANDLE_WM_COMMAND(hwnd,wParam,lParam,fn)\
((fn)((hwnd),(int)(LOWORD(wParam)),(HWND)(lParam),(UINT)HIWORD(wParam)),0L);
好了,事情到了這一步,應該一切都明朗了。
不過,咱們發如今windowsx.h裏面還有一個宏:FORWARD_WM_XXXX,咱們仍是那WM_COMMAND爲例,進行分析:
#define FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, fn) \
(void)(fn)((hwnd), WM_COMMAND, MAKEWPARAM((UINT)(id),(UINT)(codeNotify)), (LPARAM)(HWND)(hwndCtl))
因此實際上,FORWARD_WM_XXXX將消息參數進行了從新構造,生成了wParam &&lParam,而後調用了咱們定義的函數。
8、美味須要加點料!!!!
前面,咱們分析了消息的基本理論和基本的函數及用法,接下來,咱們將進一步討論消息傳遞在MFC中的實現。
但爲了防止視覺過分疲勞,已自做主張將這些內容放到MFC文件夾下的博文《MFC消息機制》中。。