【转】深入理解Windows消息机制
轉自:https://blog.csdn.net/liulianglin/article/details/14449577
?
? ? ? 今天我們來學一學Windows消息機制,我們知道在傳統的C語音程序中,當我們需要打開一個文件時,我們可以調用fopen()函數,這個函數最后又會調用操作系統提供的函數以此來打開文件。而在Windows編程中,不僅用戶可以調用系統的API函數,反之,系統也可以調用應用程序,而這些調用就是通過Windows的消息機制來實現的。Windows程序設計是一種完全不同于傳統的DOS方式的程序設計方法,它是一種事件驅動的程序設計模式,主要是基于消息的。
一、那么消息究竟是What 嘞?
? ? ???消息系統對于一個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;
?
二、What ?is消息隊列?
在Windows編程中,每一個Windows應用程序開始執行后,系統都會為該程序創建一個消息隊列,這個消息隊列用來存放該應用程序所創建的窗口的信息。例如,當我們按下鼠標右鍵的時候,這時會產生一個WM_RBUTTONDOWN消息,系統會自動將這個消息放進當前窗口所屬的應用程序的消息隊列中,等待應用程序的結束。Windows將產生的消息以此放進消息隊列中,應用程序則通過一個消息循環不斷的從該消息隊列中讀取消息,并做出響應(后面會詳細講述消息處理過程。。。。)
?
三、消息中的家庭成員?
? ? ? ? 通過前面所羅列的MSG結構體,我們是不是會對消息結構里邊含有的東東有了一個比較清楚的認識呢?如果還沒有,沒關系!!呵呵,那么我再次對那些咋一看就會淚奔的變量做出詳細的解釋:
? ? ? ??hwnd?- - - 一個32位的窗口句柄(我的PC是32 位的^_^),它表示的是消息所屬的窗口。我們通常開發的程序都是窗口應用程序,一般一個消息都是和某個窗口相關聯的。比如我們在某個活動窗口按下鼠標右鍵,此時產生的消息就是發送給該活動窗口的。窗口可以是任何類型的屏幕對象,因為Win32能夠維護大多數可視對象的句柄(窗口、對話框、按鈕、編輯框等)。
(補充一下:“句柄”---在Windows程序中,有各種各樣的資源,系統在創建這些資源的時候,都會為他們分配內存,并返回標識這些資源的標識號,這個標識號就是句柄)
? ? ???message- - - -一個消息的標識符,用于區別其他消息的常量值,這些常量可以是Windows單元中預定義的常量,也可以是自定義的常量。在Windows中消息是由一個數值表示的,不同的消息對應不同的數值。但由于當這些消息種類多到足以挑戰我們的IQ,所以聰明的程序開發者便想到將這些數值定義為WM_XXX宏的形式。例如,鼠標左鍵按下的消息--WM_LBUTTONDOWN,鍵盤按下消息--WM_KEYDOWN,字符消息--WM_CHAR,等等。。。。消息標識符以常量命名的方式指出消息的含義。當窗口過程接收到消息之后,他就會使用消息標識符來決定如何處理消息。例如、WM_PAINT告訴窗口過程窗體客戶區被改變了需要重繪。符號常量指定系統消息屬于的類別,其前綴指明了處理解釋消息的窗體的類型。
? ? ? ??wParam和lParam-?- - 用于指定消息的附加信息。例如,當我們收到一個鍵盤按下消息的時候,message成員變量的值就是WM_KEYDOWN,但是用戶到底按下的是哪一個按鍵,我們就得拜托這二位,由他們來告知我們具體的信息。
time和pt-?- -這倆兄弟分別被用來表示消息投遞到消息隊列中的時間和鼠標當前的位置,一般情況下不怎么使用(但不代表沒用)
?
四、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??? 應用程序自定義私有消息
?
五、原來消息也有分類啊
? ? ? ??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不多說,開始吧。。。。
***************************/
?
六、隊列消息和非隊列消息
從消息的發送途徑來看,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程序
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,然后調用了我們定義的函數。
?
八、美味需要加點料!!!!
前面,我們分析了消息的基本理論和基本的函數及用法,接下來,我們將進一步討論消息傳遞在MFC中的實現。
但為了防止視覺過度疲勞,已自作主張將這些內容放到MFC文件夾下的博文《MFC消息機制》中。。
?
自我勉勵:學習是一個緩慢的過程,只要今天比昨天進步就行,加油!!
總結
以上是生活随笔為你收集整理的【转】深入理解Windows消息机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AMD工程师给21年前的经典单机打补丁:
- 下一篇: 电脑安装系统多少钱_电脑系统安装教学