深入理解windows 訊息機制

肆空界發表於2024-12-06

深入理解Windows訊息機制

今天我們來學一學Windows訊息機制,我們知道在傳統的C語音程式中,當我們需要開啟一個檔案時,我們可以呼叫fopen()函式,這個函式最後又會呼叫作業系統提供的函式以此來開啟檔案。而在Windows程式設計中,不僅使用者可以呼叫系統的API函式,反之,系統也可以呼叫應用程式,而這些呼叫就是透過Windows的訊息機制來實現的。Windows程式設計是一種完全不同於傳統的DOS方式的程式設計方法,它是一種事件驅動的程式設計模式,主要是基於訊息的。

一、訊息的定義?
訊息系統對於一個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;
二、訊息佇列的定義
在Windows程式設計中,每一個Windows應用程式開始執行後,系統都會為該程式建立一個訊息佇列,這個訊息佇列用來存放該應用程式所建立的視窗的資訊。例如,當我們按下滑鼠右鍵的時候,這時會產生一個WM_RBUTTONDOWN訊息,系統會自動將這個訊息放進當前視窗所屬的應用程式的訊息佇列中,等待應用程式的結束。Windows將產生的訊息以此放進訊息佇列中,應用程式則透過一個訊息迴圈不斷的從該訊息佇列中讀取訊息,並做出響應(後面會詳細講述訊息處理過程..)

三、訊息中的家庭成員

透過前面所羅列的MSG結構體,我們是不是會對訊息結構裡邊包含的元素有了一個比較清楚的認識呢?如果還沒有,沒關係!!呵呵,那麼現在再次對那些咋一看就會淚奔的變數做出詳細的解釋:
hwnd :一個視窗控制代碼(具體位數跟個人PC作業系統位數相關),它表示的是訊息所屬的視窗。我們通常開發的程式都是視窗應用程式,一般一個訊息都是和某個視窗相關聯的。比如我們在某個活動視窗按下滑鼠右鍵,此時產生的訊息就是傳送給該活動視窗的。視窗可以是任何型別的螢幕物件,因為Win32能夠維護大多數可視物件的控制代碼(視窗、對話方塊、按鈕、編輯框等)。補充一下:“控制代碼”---在Windows程式中,有各種各樣的資源,系統在建立這些資源的時候,都會為他們分配記憶體,並返回標識這些資源的標識號,這個標識號就是控制代碼)。
message:一個訊息的識別符號,用於區別其他訊息的常量值,這些常量可以是Windows單元中預定義的常量,也可以是自定義的常量。在Windows中訊息是由一個數值表示的,不同的訊息對應不同的數值。但由於當這些訊息種類多到足以挑戰我們的IQ,所以聰明的程式開發者便想到將這些數值定義為WM_XXX宏的形式。例如,滑鼠左鍵按下的訊息--WM_LBUTTONDOWN,鍵盤按下訊息--WM_KEYDOWN,字元訊息--WM_CHAR,等等。。。。訊息識別符號以常量命名的方式指出訊息的含義。當視窗過程接收到訊息之後,他就會使用訊息識別符號來決定如何處理訊息。例如、WM_PAINT告訴視窗過程窗體客戶區被改變了需要重繪。符號常量指定系統訊息屬於的類別,其字首指明瞭處理解釋訊息的窗體的型別。
wParam和lParam:用於指定訊息的附加資訊。例如,當我們收到一個鍵盤按下訊息的時候,message成員變數的值就是WM_KEYDOWN,但是使用者到底按下的是哪一個按鍵,我們就得拜託這二位,由他們來告知我們具體的資訊。

time和pt:這倆個變數分別被用來表示訊息投遞到訊息佇列中的時間和滑鼠當前的位置,一般情況下不怎麼使用(但不代表沒用)

四、訊息識別符號
系統保留訊息識別符號的值在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 應用程式自定義私有訊息
五、訊息的分類

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 列表框獲得輸入焦點

六、佇列訊息和非佇列訊息

從訊息的傳送途徑來看,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訊息。一些函式也傳送非佇列訊息,例如下面我們要談到的函式。

七、一個簡單的Win32程式
現在讓我們透過這個程式來更進一步的理解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;
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
1、SendMessage

LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam),
這個函式主要是向一個或多個視窗傳送一條訊息,一直等到訊息被處理之後才會返回。不過需要注意的是,如果接收訊息的視窗是同一個應用程式的一部分,那麼這個視窗的視窗函式就被作為一個子程式馬上被呼叫;如果接收訊息的視窗是被另外的執行緒所建立的,那麼視窗系統就切換到相應的執行緒並且呼叫相應的視窗函式,這條訊息不會被放進目標應用程式佇列中。函式的返回值是由接收訊息的視窗的視窗函式返回,返回的值取決於被髮送的訊息。
2、PostMessage

BOOL PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam),
該函式把一條訊息放置到建立hWnd視窗的執行緒的訊息佇列中,該函式不等訊息被處理就馬上將控制返回。需要注意的是,如果hWnd引數為 HWND_BROADCAST,那麼,訊息將被寄送給系統中的所有的重疊視窗和彈出視窗,但是子視窗不會收到該訊息;如果hWnd引數為NULL,則該函式類似於將dwThreadID引數設定成當前執行緒的標誌來呼叫PostThreadMEssage函式。
  

從上面的這2個具有代表性的函式,我們可以看出訊息的傳送方式和寄送方式的區別所在:被髮送的訊息會被立即處理,處理完畢後函式才會返回;被寄送的訊息不會被立即處理,他被放到一個先進先出的佇列中,一直等到應用程式空線的時候才會被處理,不過函式放置訊息後立即返回。
  實際上,傳送訊息到一個視窗處理過程和直接呼叫視窗處理過程之間並沒有太大的區別,他們直接的唯一區別就在於你可以要求作業系統截獲所有被髮送的訊息,但是不能夠截獲對視窗處理過程的直接呼叫。
  以寄送方式傳送的訊息通常是與使用者輸入事件相對應的,因為這些事件不是十分緊迫,可以進行緩慢的緩衝處理,例如滑鼠、鍵盤訊息會被寄送,而按鈕等訊息則會被髮送。

3、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。

1、GetMessage

BOOL GetMessage(LPMSG lpMsg,HWND hWnd,UINTwMsgFilterMin,UINT wMsgFilterMax);
該函式用來獲取與hWnd引數所指定的視窗相關的且wMsgFilterMin和wMsgFilterMax引數所給出的訊息值範圍內的訊息。需要注意的是,如果hWnd為NULL,則GetMessage獲取屬於呼叫該函式應用程式的任一視窗的訊息,如果 wMsgFilterMin和wMsgFilterMax都是0,則GetMessage就返回所有可得到的訊息。函式獲取之後將刪除訊息佇列中的除 WM_PAINT訊息之外的其他訊息,至於WM_PAINT則只有在其處理之後才被刪除。

2、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則只有在其處理之後才被刪除。

3、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;
}

上面的 HANDLE_MSG 是一個定義在WindowsX.h中的宏:

   #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,然後呼叫了我們定義的函式。
————————————————

轉載>>>>>


https://blog.csdn.net/liulianglin/article/details/14449577

相關文章