轉:Windows訊息編程

來源:互聯網
上載者:User

Windows訊息編程
韓耀旭

下載原始碼

本文主要包括以下內容:

1、簡單理解Windows的訊息
2、通過一個簡單的Win32程式理解Windows訊息
3、通過幾個Win32程式執行個體進一步深入理解Windows訊息
4、隊列訊息和非隊列訊息
5、WM_COMMAND和WM_NOTIFY
6、MFC的訊息映射
7、訊息反射機制

1、簡單理解Windows的訊息

訊息,就是指Windows發出的一個通知,告訴應用程式某個事情發生了。
舉個例子來說,按一下滑鼠某應用程式的一個按鈕。這時,Windows(作業系統)給應用程式發送這個訊息,通知應用程式該按鈕被點擊,應用程式將進行相應反應。
訊息一般用一個32位的數來標識,這個數唯一地標識這個訊息。這些訊息的標識符一般在標頭檔winuser.h 中定義,如:

#define WM_PAINT 0x000F
#define WM_QUIT 0x0012

其實訊息本身是一個MSG結構。MSG結構定義如下:

typedef struct tagMSG {
HWND hwnd; //接受訊息的視窗控制代碼
UINT message; //訊息標識符
WPARAM wParam; //32位附加資訊
LPARAM lParam; //32位附加資訊
DWORD time; //訊息建立的時間
POINT pt; //訊息建立時滑鼠在螢幕座標系中的位置
} MSG;

也就是說,對於任何一個訊息,都有一個MSG變數與之對應,該變數包含了訊息的相關資訊。而我們在一般情況下,只使用訊息的訊息標識符,該標識符也唯一地代表了這個訊息。
舉個例子來說,當我們收到一個字元訊息的時候,message成員變數的值就是WM_CHAR,但使用者到底輸入的是什麼字元,那麼就由wParam和lParam來說明。wParam、lParam表示的資訊隨訊息的不同而不同。
Windows作業系統已經給我們定義了大量的訊息,這些訊息我們稱為系統訊息。除了系統訊息,我們還可以自己定義訊息,即自訂訊息。
值小於0x0400的訊息都是系統訊息,自訂訊息一般都大於0x0400。
系統訊息取值一般有如下規律,如表1:

範圍 意義
0x0001——0x0087 主要是視窗訊息
0x00A0——0x00A9 非客戶區訊息
0x0100——0x0108 鍵盤訊息
0x0111——0x0126 菜單訊息
0x0132——0x0138 顏色控制訊息
0x0200——0x020A 滑鼠訊息
0x0211——0x0213 菜單迴圈訊息
0x0220——0x0230 多文檔訊息
0x03E0——0x03E8 DDE訊息
0x0400 WM_USER
0x0400——0x7FFF 自訂訊息

表1

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

#define WM_USER 0x0400

對於自訂訊息,我們一般採用WM_USER 加一個整數值的方法定義自訂訊息,如:

#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應用程式//通過這個簡單的執行個體講解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){    //視窗類別    WNDCLASS 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;//類名稱    //註冊視窗類別    if(!RegisterClass (&wndclass))    {           MessageBox (NULL, TEXT ("RegisterClass Fail!"),                    szClassName, MB_ICONERROR);           return 0;    }     //建立視窗    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);    ShowWindow(hwnd,iCmdShow);//顯示視窗    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小程式,編譯運行會顯示一個視窗,關閉視窗程序會結束運行。 代碼中已經做了簡單註解,這裡我們不作過多說明。我在這裡再著重講解一下訊息迴圈部分。

//訊息迴圈MSG msg;while(GetMessage(&msg,NULL,0,0))//從訊息佇列中取訊息 {      TranslateMessage (&msg);              //轉換訊息      DispatchMessage (&msg);               //派發訊息}

這段代碼是訊息迴圈部分,它的作用是迴圈檢測訊息佇列(不懂訊息佇列?沒關係,後面會詳細說明)中的訊息並進行處理。這段代碼涉及GetMessage,TranslateMessage,DispatchMessage這三個函數,相關函數還有PeekMessage,WaitMessage。在此,我們先對這五個函數簡單講解。

1、GetMessage

函數原型:

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

參數:

lpMsg:一個指向MSG結構的指標,該結構用於存放從訊息佇列裡取出的訊息。
hWnd:視窗控制代碼。如果該參數是非零值,則GetMessage只檢索該視窗(也包括其子視窗)訊息,如果為零,則GetMessage檢索整個進程內的訊息。
wMsgFilterMin:指定被檢索的最小訊息值,也就是訊息範圍的下界限參數。
wMsgFilterMax:上界限參數。如果wMsgFilterMin和wMsgFilterMax都為零,則不進行訊息過濾,GetMessage檢索所有有效訊息。

傳回值

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

函數功能:

這個API函數用來從訊息佇列中“摘取”一個訊息,放到lpMsg所指的變數裡。(註:如果所取視窗的訊息佇列中沒有訊息,則程式會暫停在GetMessage(…) 函數裡,不會返回。)
再通俗一點講解GetMessage函數:
當程式執行GetMessage()的時候,會檢查訊息佇列,如果有訊息在訊息佇列裡,它取出該訊息,將該訊息填充到lpMsg所指的MSG結構,並返回TRUE值。如果此時訊息佇列裡沒有訊息(訊息佇列為空白),它會將線程阻塞,也就是將控制權交給系統,直到訊息佇列中有內容時,才喚醒線程繼續執行。
對於GetMessage()函數,還有一點需要說明,就是當從訊息佇列中取出的訊息是WM_QUIT時,函數傳回值是0。我們一般利用這一點退出訊息迴圈,結束程式。

如語句:

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

2 、PeekMessage

函數原型:

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

參數:

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

傳回值:

訊息佇列中有訊息,傳回值為TRUE;訊息佇列中沒有訊息,傳回值為FALSE。

函數功能:

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

(一)、GetMessage()只能從訊息佇列中取走訊息,也就是說,GetMessage()執行後,該訊息將從訊息佇列中移除。
PeekMessage()可以從訊息佇列中取走訊息。也可以讀訊息,讓訊息繼續留在訊息佇列裡。

(二)、當訊息佇列中沒有訊息時,GetMessage()將會阻塞線程,等待訊息;而PeekMessage()與GetMessage()不同,它執行後會立刻返回,訊息佇列中有訊息時,傳回值為TRUE;訊息佇列中沒有訊息時,傳回值為FALSE。

3 、WaitMessage

函數原型:

BOOL WaitMessage(VOID);

函數功能:

這個函數的作用是當訊息佇列中沒有訊息時,將控制權交給其它線程。該函數將會使線程掛起,直到訊息佇列中又有新訊息。
這個函數專門和PeekMessage配合使用,當訊息佇列中沒有訊息時,掛起線程,等待訊息佇列中新訊息的到來,這樣可以減輕CPU的運算負擔。

4 、TranslateMessage

函數原型:

BOOL TranslateMessage(CONST MSG*lpMsg);

參數:

  IpMsg:指向MSG結構的指標,該結構是函數GetMessage或PeekMessage從訊息佇列裡取得的訊息。
  函數功能:該函數將虛擬鍵訊息轉換為字元訊息。字元訊息被寄送到調用線程的訊息佇列裡,當下一次線程調用函數GetMessage或PeekMessage時被讀出。
什麼是虛擬鍵碼呢?Windows為了方便輸入管理,減少程式對裝置的依賴性,將鍵盤上所有的按鍵都用一個兩位十六進位數對應,這些數稱為虛擬鍵碼。虛擬鍵碼一般以VK_開頭,如:Esc鍵對應的虛擬鍵碼是VK_ESCAPE;空格鍵對應的虛擬鍵碼是VK_SPACE;VK_LWIN與左邊的Windows徽標鍵相對應。
當一個按鍵被按下時,會觸發WM_KEYDOWN訊息, WM_KEYDOWN訊息的wParam參數值就是虛擬索引值。通過這個值就可以判斷哪個鍵被按下了。
為什麼我們要把虛擬鍵碼轉換為字元碼呢?
比如我們按下了‘A’鍵,此時我們得到的字元可能是‘A’,也可能是小寫‘a’,這由當時的大寫狀態(Caps Lock)以及是否同步選取了Shift鍵有關。TranslateMessage()函數的作用就是不用我們考慮這些問題,而是根據這些情況,自動返回一個ASCII碼值,以方便使用者使用。
並不是所有的虛擬鍵碼值都會Translate成字元碼。字母、數字鍵都有字元碼相對應,而像方向方向鍵、F1—F12功能鍵這些按鍵就沒有字元碼相對應。當虛擬鍵碼需要轉化成字元碼時,TranslateMessage()函數就在訊息佇列裡放一條WM_CHAR訊息,WM_CHAR訊息的wParam參數值就是轉換後的ASCII碼值。

5、DispatchMessage

函數原型:

LONG DispatchMessage(CONST MSG *lpmsg);

函數功能:
它的作用很簡單,就是指派訊息到視窗的訊息處理函數去執行。
瞭解了這5個函數,訊息迴圈這段代碼就不難理解:

GetMessage()從訊息佇列中取訊息,對取出的訊息進行轉換(TranslateMessage),對於能夠將虛擬鍵碼轉化成字元碼的訊息,會在訊息佇列裡放一條WM_CHAR訊息,最後將訊息發送到相應的訊息處理函數進行處理。迴圈執行這個處理過程,直到收到WM_QUIT訊息,才退出迴圈,結束程式。

3、通過幾個Win32程式執行個體進一步深入理解Windows訊息

常式2:對比使用GetMessage和PeekMessage處理訊息迴圈(見附帶源碼 工程M2)
同工程M1,建立工程M2,將工程M1的原始碼全部拷貝到M2,並將訊息迴圈部分的代碼改為:

//訊息迴圈MSG msg;while(true){      if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) //從訊息佇列中取訊息      {              if(msg.message == WM_QUIT)                    break;              TranslateMessage (&msg);           //轉換訊息              DispatchMessage (&msg);            //派發訊息       }       else              WaitMessage();} //End of while(true)

編譯、運行工程M2,觀察運行效果,可以看出,使用PeekMessage處理訊息迴圈同樣能夠達到與GetMessage相同的效果。
PeekMessage處理訊息迴圈比GetMessage還要靈活,尤其體現在遊戲編程中。遊戲編程者不希望玩家在沒有鍵盤或滑鼠輸入時遊戲是靜止不動的,他們希望怪獸從後面衝出來,圍攻玩家,追捕玩家。為了做到這樣的效果,需要這樣一種訊息迴圈:當遇到需要處理的訊息時去處理訊息,其餘的時間都讓程式碼自動產生激烈的場面。
下面的常式3將類比這種訊息迴圈。
常式3:類比示範遊戲編程如何進行訊息處理(見附帶源碼工程M3)。
詳細的代碼參看工程M3,編譯並執行,您會發現程式不停地自己畫圓,這類比遊戲自動產生激烈的場面。當您按下上、下、左、右方向鍵,您就會發現您在相應的方向畫線,這類比遊戲程式及時處理玩家的訊息。

4、隊列訊息和非隊列訊息
Windows把訊息分為兩種:一種是需要立即處理的訊息,另一種是不需要立即處理的訊息。
對於需要立即處理的訊息,Windows直接把它送給視窗的訊息處理函數進行處理,這類訊息我們叫做非隊列訊息;
而對於不需要立即處理的訊息,Windows會把它發送給應用程式的訊息佇列進行排隊,由應用程式逐個進行處理,我們把這類訊息叫做隊列訊息。
為了更清楚地說明這個問題,我們參看圖1:

圖1

圖1的解釋:

1、Windows作業系統有一個訊息佇列,它存放作業系統收到的訊息。如:當按鍵被按下,鍵盤會發送一個訊息到作業系統的訊息佇列。
2、作業系統把系統訊息佇列中的訊息指派到各個應用程式的訊息佇列。如果它是第1個應用程式的訊息,作業系統把它發給第1個應用程式,把它放在第1個應用程式的訊息佇列;如果它是第2個應用程式的訊息,發送給第2個程式的訊息佇列。
3、應用程式的訊息迴圈從自己的訊息佇列中取訊息,取出的訊息調用視窗過程函數進行處理。
4、PostMessage是寄送訊息,函數執行後立即返回。寄送的訊息是隊列訊息,放在程式的訊息佇列中排隊處理。一般來說,新寄送的訊息排在訊息佇列的末尾,這樣可以保證視窗以先進先出的順序處理訊息。
SendMessage是發送訊息,它發出的訊息是非隊列訊息,直接調用視窗過程函數處理。SendMessage函數一直等訊息處理完成後才返回。

我們有必要再專門學習一下SendMessage和PostMessage函數。

SendMessage的函數原型:

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

這個函數向視窗發送一條訊息,一直等到訊息被處理之後才返回。也就是說,接收訊息的視窗的視窗函數立即被調用。函數的傳回值由接收訊息的視窗的視窗函數返回。

PostMessage的函數原型:

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

該函數把一條訊息放置到建立hWnd視窗的線程的訊息佇列中,該函數不等訊息被處理就馬上將控制返回。
從上面這兩個函數,我們可以看出訊息的發送方式和寄送方式的區別:被發送的訊息會被立即處理,處理完畢後函數才返回;被寄送的訊息不會被立即處理,他被放到一個先進先出的隊列中,按次序等候處理,而且函數放置訊息後立即返回。
以寄送方式發送的訊息通常是與使用者輸入事件相對應的,因為這些事件不是十分緊迫,可以進行緩衝處理,例如滑鼠、鍵盤訊息都是寄送訊息。應用程式調用系統函數,系統一般會發送非隊列訊息。例如,當程式調用SetWindowPos,系統會發送WM_WINDOWPOSCHANGED訊息。
常式M4,測試訊息佇列的容量(見附帶源碼工程M4)
代碼中已經作了註解,編譯、運行程式,您就會發現訊息佇列的最大容量是10000。
常式M5,用記事本查看訊息佇列和視窗過程函數處理的訊息
這個常式的出發點是利用記事本分別捕獲訊息佇列中的訊息和視窗過程函數處理過的訊息。
該常式還示範了PostMessage和SendMessage的不同。
由於該常式相對複雜一些,常式中的註解也相對多一些。編譯、運行程式,彈出如下視窗:

關閉該視窗,退出運行,檢查M5常式所在的路徑,您就會發現多了兩個檔案MessageQueue.txt和MessageWndProc.txt,MessageQueue.txt檔案中記錄的是應用程式M5從運行到關閉訊息佇列中處理過的訊息;MessageWndProc.txt中記錄的M5視窗過程函數處理過的訊息。
開啟MessageQueue.txt檔案,如:

檔案中記錄了訊息佇列中的各個訊息以及訊息的ID號,其中有一條訊息是WM_POSTMESSAGE,這說明PostMessage寄送的WM_POSTMESSAGE訊息確實放到了訊息佇列中。
再開啟MessageWndProc.txt檔案,如:

檔案中記錄了視窗過程處理的各個訊息和訊息的ID號,其中有兩條訊息WM_POSTMESSAGE和WM_SENDMESSAGE,這說明了兩個問題:WM_POSTMESSAGE訊息從訊息佇列取出,再次派發到視窗過程函數處理;SendMessage發送的WM_SENDMESSAGE訊息,沒有經過訊息佇列,直接送到視窗過程函數處理。

5、WM_COMMAND和WM_NOTIFY

控制項通知訊息,是指這樣一種訊息,一個視窗內的控制項發生了一些事情,需要通知父視窗。當使用者與控制項視窗互動時,控制項通知訊息就會從控制項視窗發送到它的主視窗,這種訊息一般不是為了處理使用者命令,而是為了讓主視窗能夠改變控制項。
WM_COMMAND和WM_NOTIFY都是控制項通知訊息。
在最初的Windows 3.x中,還沒有WM_NOTIFY,只存在WM_COMMAND訊息,wParam參數中包含一個通知碼和控制項ID,lParam中包含控制項控制代碼。這樣一來,wParam和lParam都被填充了,沒有額外的空間來傳遞一些其它資訊,如滑鼠按下的位置和時間。
為瞭解決這個問題,Windows 3.x就提出了一個解決方案策略,那就是給一些訊息添加一些附加訊息,比如控制項自畫用到的DRAWITEMSTRUCT等,這樣,不同的訊息附加的內容不同,結果是非常混亂。
在Win32中,微軟又提出了一個更好的解決方案,引進了NMHDR結構。這個結構的引進把訊息統一起來,利用它可以傳遞各種複雜的訊息。
NMHDR結構內容如下:

NMHDR
{
HWND hWndFrom;//相當於原WM_COMMAND訊息的lParam
UINT idFrom; //相當於原WM_COMMAND訊息的wParam(LOWORD)
UINT code; //相當於原WM_COMMAND訊息的wParam(HIWORD)通知碼
}

使用這個結構,WM_NOTIFY還可以附帶更多的資訊,您可以定義一個更大的結構,這個結構的第一個元素就是NMHDR結構,在該元素的後面您還可以放置其它附加資訊。由於在這個大結構中,第一個成員是NMHDR,這樣一來,我們就可以利用指向NMHDR的指標來指向這個結構,不論後面有沒有其它內容。
可見,WM_NOTIFY和WM_COMMAND相比,是一種更靈活的訊息格式,lParam中放的是一個稱為NMHDR結構的指標。在wParam中放的則是控制項的ID。最初Windows 3.x就有的控制項,如Edit,Combo,List,Button等,發送的控制項通知訊息的格式是WM_COMMAND;而後期的Win32通用控制項,如List View,Image List,IP Address,Tree View,Toolbar等,發送的都是WM_NOTIFY控制項通知訊息。
另外,當使用者選擇菜單的一個命令項,也會發送WM_COMMAND訊息。
當使用者選擇菜單的一個命令項或控制項給父視窗發送通知訊息,都可以使用WM_COMMAND訊息。為了區分這兩種情況,規定它們有以下區別,如表2:

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

菜單
0
菜單標識符 (IDM_*)
0

控制項
控制項定義的通知碼
控制項ID
控制項視窗的控制代碼

表2

常式M6,示範菜單發出WM_COMMAND訊息和子控制項發送WM_COMMAND訊息的區別(見附帶源碼工程M6)
開啟VC++ 6.0,建立Win32 Application工程M6,然後在該工程中建立C++ Source File,檔案名稱為M6,M6的檔案內容具體見常式M6。
在常式M6所在的路徑開啟M6檔案夾,建立一個文字文件,如:


將“建立文字文件.txt”改名為“M6.rc”,如:

按右鍵M6.rc,在彈出的捷徑功能表中使用“寫字板”開啟,如:

添加的內容具體見M6.rc,儲存後退出。編譯、運行工程M6,彈出如下視窗:

分別單擊“FirstButton”按鈕和“Menu1”菜單,會彈出相應的提示訊息框。
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 //處理子控制項觸發的WM_COMMAND控制項通知訊息      {        //(LOWORD(wParam))是控制項ID        switch(LOWORD(wParam))        {        case ButtonID1:          if(HIWORD(wParam) == BN_CLICKED)          {            MessageBox(NULL,"按鈕被點擊","M6",MB_OK);          }          break;        }      }    }    break;

對於WM_COMMAND訊息,因為菜單和子控制項都能觸發。我們首先判斷lParam,如果lParam為0,是菜單觸發的WM_COMMAND訊息;如果lParam不為0,是子控制項觸發的WM_COMMAND控制項通知訊息。對於菜單觸發的WM_COMMAND訊息,我們再通過(LOWORD(wParam))(菜單的標識ID)判斷是哪個菜單觸發的訊息;對於控制項觸發的WM_COMMAND訊息,我們通過(LOWORD(wParam))(控制項ID)知道是哪個控制項觸發的訊息,而且通過(HIWORD(wParam))(控制項定義的通知碼)知道控制項到底觸發了什麼訊息。
本常式我們純手工添加並編輯資源檔M6.rc,之所以這樣做是為了讓您瞭解資源檔的實質。實際編程中,您完全可以利用資源編輯器更加方便地添加、編輯資源檔,後面的常式將會示範說明。
常式M7,示範WM_NOTIFY控制項通知訊息(見附帶源碼 工程M7)
WM_NOTIFY訊息是通用控制項發送給其父視窗的訊息,其中參數wParam 是發送訊息的通用控制項的ID,參數lParam 是一個指標,這個指標指向一個 NMHDR 結構,該結構包含了通知碼和其它附加資訊。

下面我們看結構NMHDR:

typedef struct tagNMHDR {
//發送訊息的控制項的控制代碼,相當於原WM_COMMAND訊息的lParam
HWND hwndFrom;
//發送訊息的控制項的ID,相當於原WM_COMMAND訊息的wParam(LOWORD)
UINT idFrom;
//通知碼,也就是發送的具體訊息,相當於原WM_COMMAND訊息的wParam(HIWORD)通知碼
UINT code;
} NMHDR;

開啟VC++ 6.0,建立Win32 Application工程M7,然後在該工程中建立C++ Source File,檔案名稱為M7,M7的檔案內容具體見常式M7。
下面,我們利用資源編輯器添加資源。單擊“檔案”->“建立”,在“建立”對話方塊中選中“Resource Script”,檔案名稱為“M7”,如:

單擊“確定”,添加M7資源檔。
右擊“M7.RC”檔案夾,選中“Insert…”功能表項目,如:

彈出“插入資源”對話方塊,

選中“Dialog”,點擊“建立”按鈕,建立一個對話方塊資源。
右擊建立的“IDD_DIALOG1”,在屬性對話方塊中將ID改為“IDC_DIALOG”,關閉屬性框。

雙擊“IDC_DIALOG”,開啟該對話方塊,調整至合適大小,在對話方塊上添加一個清單控制項(List Control),將該清單控制項的ID設定為IDC_LIST,如:

並且把清單控制項改為“Report”類型,如:

編輯並運行程式,程式運行會彈出如下對話方塊:

分別用滑鼠雙擊第一行或第二行,會彈出相應訊息框。
程式碼都有詳細注釋,您可以閱讀代碼,細細體會WM_NOTIFY控制項通知訊息。

6、MFC的訊息映射

使用MFC編程時,訊息發送和處理的本質和Win32相同,但是,它對訊息處理進行了封裝,簡化了程式員編程時訊息處理的複雜性,它通過訊息映射機制來處理訊息,程式員不必去設計和實現自己的視窗過程。
說白了,MFC中的訊息映射機制實質是一張巨大的訊息及其處理函數對應表。訊息映射基本上分為兩大部分:
在標頭檔(.h)中有一個宏DECLARE_MESSAGE_MAP(),它放在類的末尾,是一個public屬性的;與之對應的是在實現部分(.cpp)增加了一個訊息映射表,內容如下:

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

訊息的入口項

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

但是僅是這兩項還不足以完成一條訊息,要是一個訊息工作,必須還有以下3個部分去協作:
1、在類的定義中加入相應的函式宣告;
2、在類的訊息映射表中加入相應的訊息映射入口項;
3、在類的實現中加入相應的函數體;

訊息的添加

(1)、利用Class Wizard實現自動添加
在菜單中選擇View -> Class Wizard啟用Class Wizard,選擇Message Map標籤,從Class name組合框中選取我們想要添加訊息的類。在Object IDs列表框中,選取類的名稱。此時,Messages列表框顯示該類的可重載成員函數和視窗訊息。可重載成員函數顯示在列表的上部,以實際虛構成員函數的大小寫字母來表示。其他為視窗訊息,以大寫字母出現。選中我們要添加的訊息,單擊Add Funtion按鈕,Class Wizard自動將該訊息添加進來。
有時候,我們想要添加的訊息在Message列表中找不到,我們可以利用Class Wizard上Class Info標籤以擴充訊息列表。在該頁中,找到Message Filter組合框,通過它可以改變首頁中Messages列表框中的選項。

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

對於能夠使用Class Wizard添加的訊息,盡量使用Class Wizard添加,以減少我們的工作量;對於不能使用Class Wizard添加的訊息和自訂訊息,需要手動添加。總體說來,MFC的訊息編程對使用者來說,相對比較簡單,在此不再使用執行個體示範。

7、訊息反射機制
什麼叫訊息反射?
父視窗將控制項發給它的通知訊息,反射回控制項進行處理(即讓控制項處理這個訊息),這種通知訊息讓控制項自己處理的機制叫做訊息反射機制。
通過前面的學習我們知道,一般情況下,控制項向父視窗發送通知訊息,由父視窗處理這些通知訊息。這樣,父視窗(通常是一個對話方塊)會對這些訊息進行處理,換句話說,控制項的這些訊息處理必須在父視窗類體內,每當我們添加子控制項的時候,就要在父視窗類中複製這些代碼。很明顯,這對代碼的維護和移植帶來了不便,而且,明顯背離C++的對象編程原則。
從4.0版開始,MFC提供了一種訊息反射機制(Message Reflection),可以把控制項通知訊息反射回控制項。具體地講,對於反射訊息,如果控制項有該訊息的處理函數,那麼就由控制項自己處理該訊息,如果控制項不處理該訊息,則架構會把該訊息繼續送給父視窗,這樣父視窗繼續處理該訊息。可見,新的訊息反射機制並不破壞原來的通知訊息處理機制。

 訊息反射機製為控制項提供了處理通知訊息的機會,這是很有用的。如果按傳統的方法,由父視窗來處理這個訊息,則加重了控制項對象對父視窗的依賴程度,這顯然違背了物件導向的原則。若由控制項自己處理訊息,則使得控制項對象具有更大的獨立性,大大方便了代碼的維護和移植。
執行個體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”,您是否發現,WM_CTLCOLOR訊息前面有一個等號,它表示該訊息是反射訊息,也就是說,前面有等號的訊息是可以反射的訊息。

訊息反射函數代碼如下:

HBRUSH CMyEdit::CtlColor(CDC* pDC, UINT nCtlColor) {    // TODO: Change any attributes of the DC here    pDC->SetTextColor( m_clrText );//設定文本顏色    pDC->SetBkColor( m_clrBkgnd );//設定背景顏色     //請注意,在我們改寫該函數的內容前,函數返回NULL,即return NULL;    //函數返回NULL將會執行父視窗的CtlColor函數,而不執行控制項的CtlColor函數    //所以,我們讓函數返回背景刷,而不返回NULL,目的就是為了實現訊息反射    return m_brBkgnd; //返回背景刷}

在IDD_M8_DIALOG對話方塊中添加一個Edit控制項,使用ClassWizard給該Edit控制項添加一個CMyEdit類型的變數m_edit1,把Edit控制項和CMyEdit關聯起來。

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.