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的消息編程有一個瞭解。

二、經過一個簡單的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 : efWindowProc (hwnd, message, wParam, lParam);
}
這是一個很是簡單的Win32小程序,編譯運行會顯示一個窗口,關閉窗口程序會結束運行。 代碼中已經作了簡單註解,這裏咱們不做過多說明。我在這裏再着重講解一下消息循環部分。
//消息循環

MSG msg;
while(GetMessage(&msg,NULL,0,0))//從消息隊列中取消息 
{
      TranslateMessage (&msg);              //轉換消息
      DispatchMessage (&msg);               //派發消息
}
這段代碼是消息循環部分,它的做用是循環檢測消息隊列(不懂消息隊列?不要緊,後面會詳細說明)中的消息並進行處理。這段代碼涉及GetMessage,TranslateMessage,DispatchMessage這三個函數,相關函數還有PeekMessage,WaitMessage。在此,咱們先對這五個函數簡單講解。

一、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(CON ST 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碼值。

五、DispatchMessage

函數原型:

LONG DispatchMessage(CON ST MSG *lpmsg);

函數功能:
它的做用很簡單,就是分派消息到窗口的消息處理函數去執行。
瞭解了這5個函數,消息循環這段代碼就不難理解:

GetMessage()從消息隊列中取消息,對取出的消息進行轉換(TranslateMessage),對於可以將虛擬鍵碼轉化成字符碼的消息,會在消息隊列裏放一條WM_CHAR消息,最後將消息發送到相應的消息處理函數進行處理。循環執行這個處理過程,直到收到WM_QUIT消息,才退出循環,結束程序。

三、經過幾個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,編譯並執行,您會發現程序不停地本身畫圓,這模擬遊戲自動產生激烈的場面。當您按下上、下、左、右箭頭鍵,您就會發現您在相應的方向畫線,這模擬遊戲程序及時處理玩家的消息。

四、隊列消息和非隊列消息
Windows把消息分爲兩種:一種是須要當即處理的消息,另外一種是不須要當即處理的消息。
對於須要當即處理的消息,Windows直接把它送給窗口的消息處理函數進行處理,這類消息咱們叫作非隊列消息;
而對於不須要當即處理的消息,Windows會把它發送給應用程序的消息隊列進行排隊,由應用程序逐個進行處理,咱們把這類消息叫作隊列消息。
爲了更清楚地說明這個問題,咱們參看圖1:


圖1

圖1的解釋:

一、Windows操做系統有一個消息隊列,它存放操做系統收到的消息。如:當按鍵被按下,鍵盤會發送一個消息到操做系統的消息隊列。
二、操做系統把系統消息隊列中的消息分派到各個應用程序的消息隊列。若是它是第1個應用程序的消息,操做系統把它發給第1個應用程序,把它放在第1個應用程序的消息隊列;若是它是第2個應用程序的消息,發送給第2個程序的消息隊列。
三、應用程序的消息循環從本身的消息隊列中取消息,取出的消息調用窗口過程函數進行處理。
四、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消息,沒有通過消息隊列,直接送到窗口過程函數處理。

五、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控件通知消息。

六、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)、利用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的消息編程對用戶來講,相對比較簡單,在此再也不使用實例演示。

七、消息反射機制
什麼叫消息反射?
父窗口將控件發給它的通知消息,反射回控件進行處理(即讓控件處理這個消息),這種通知消息讓控件本身處理的機制叫作消息反射機制。
經過前面的學習咱們知道,通常狀況下,控件向父窗口發送通知消息,由父窗口處理這些通知消息。這樣,父窗口(一般是一個對話框)會對這些消息進行處理,換句話說,控件的這些消息處理必須在父窗口類體內,每當咱們添加子控件的時候,就要在父窗口類中複製這些代碼。很明顯,這對代碼的維護和移植帶來了不便,並且,明顯背離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關聯起來。


編譯,運行程序,觀察運行效果。
相關文章
相關標籤/搜索