Windows中,消息使用統(tǒng)一的結(jié)構(gòu)體(MSG)來存放信息,其中message表明消息的具體的類型,
而wParam,lParam是其最靈活的兩個變量,為不同的消息類型時,存放數(shù)據(jù)的含義也不一樣。
time表示產(chǎn)生消息的時間,pt表示產(chǎn)生消息時鼠標(biāo)的位置。
按照類型,Windows將消息分為:
(0) 消息ID范圍
系統(tǒng)定義消息ID范圍:[0x0000, 0x03ff]用戶自定義的消息ID范圍: WM_USER: 0x0400-0x7FFF (例:WM_USER+10) WM_APP(winver> 4.0):0x8000-0xBFFF (例:WM_APP+4) RegisterWindowMessage:0xC000-0xFFFF【用來和其他應(yīng)用程序通信,為了ID的唯一性,使用::RegisterWindowMessage來得到該范圍的消息ID 】
(1) 窗口消息:即與窗口的內(nèi)部運(yùn)作有關(guān)的消息,如創(chuàng)建窗口,繪制窗口,銷毀窗口等。
可以是一般的窗口,也可以是MainFrame,Dialog,控件等。
如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL等
(2) 當(dāng)用戶從菜單選中一個命令項目、按下一個快捷鍵或者點(diǎn)擊工具欄上的一個按鈕,都將發(fā)送WM_COMMAND命令消息。
LOWord(wParam)表示菜單項,工具欄按鈕或控件的ID;如果是控件, HIWORD(wParam)表示控件消息類型。
#define LOWORD(l) ((WORD)(l))
#define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))
(3) 隨著控件的種類越來越多,越來越復(fù)雜(如列表控件、樹控件等),僅僅將wParam,lParam將視為一個32位無符號整數(shù),已經(jīng)裝不下太多信息了。
為了給父窗口發(fā)送更多的信息,微軟定義了一個新的WM_NOTIFY消息來擴(kuò)展WM_COMMAND消息。
WM_NOTIFY消息仍然使用MSG消息結(jié)構(gòu),只是此時wParam為控件ID,lParam為一個NMHDR指針,
不同的控件可以按照規(guī)則對NMHDR進(jìn)行擴(kuò)充,因此WM_NOTIFY消息傳送的信息量可以相當(dāng)?shù)拇蟆?/p>
注:Window 9x 版及以后的新控件通告消息不再通過WM_COMMAND 傳送,而是通過WM_NOTIFY 傳送, 但是老控件的通告消息, 比如CBN_SELCHANGE 還是通過WM_COMMAND 消息發(fā)送。
(4) windwos也允許程序員定義自己的消息,使用SendMessage或PostMessage來發(fā)送消息。
windows消息還可以分為:
(1) 隊列消息(Queued Messages) 消息會先保存在消息隊列中,消息循環(huán)會從此隊列中取出消息并分發(fā)到各窗口處理 如:WM_PAINT,WM_TIMER,WM_CREATE,WM_QUIT,以及鼠標(biāo),鍵盤消息等。其中,WM_PAINT,WM_TIMER只有在隊列中沒有其他消息的時候才會被處理,WM_PAINT消息還會被合并以提高效率。其他所有消息以先進(jìn)先出(FIFO)的方式被處理。
(2) 非隊列消息(NonQueued Messages) 消息會繞過系統(tǒng)消息隊列和線程消息隊列,直接發(fā)送到窗口過程進(jìn)行處理 如:WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR,WM_WINDOWPOSCHANGED
Windows系統(tǒng)的整個消息系統(tǒng)分為3個層級:
① Windows內(nèi)核的系統(tǒng)消息隊列
② App的UI線程消息隊列
③ 處理消息的窗體對象
Windows內(nèi)核維護(hù)著一個全局的系統(tǒng)消息隊列;按照線程的不同,系統(tǒng)消息隊列中的消息會分發(fā)到應(yīng)用程序的UI線程的消息隊列中;
應(yīng)用程序的每一個UI線程都有自己的消息循環(huán),會不停地從自己的消息隊列取出消息,并發(fā)送給Windows窗體對象;
每一個窗體對象都使用窗體過程函數(shù)(WindowPRoc)來處理接收到的各種消息。
1 LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 2 { 3 PAINTSTRUCT ps; 4 HDC hdc; 5 6 switch (message) 7 { 8 case WM_COMMAND: 9 break;10 case WM_PAINT:11 hdc = BeginPaint(hWnd, &ps);12 // TODO: 在此添加任意繪圖代碼...13 EndPaint(hWnd, &ps);14 break;15 case WM_DESTROY:16 PostQuitMessage(0);17 break;18 default:19 return DefWindowProc(hWnd, message, wParam, lParam);20 }21 return 0;22 }需要的話,在WindowProc中,可以用::GetMessageTime獲取當(dāng)前消息產(chǎn)生的時間,用::GetMessagePos獲取當(dāng)前消息產(chǎn)生時鼠標(biāo)光標(biāo)所在的位置。
(1) 各個窗口消息由各個窗體(或控件)自身的WindowProc(虛函數(shù))接收并處理。
(2) WM_COMMAND命令消息統(tǒng)一由當(dāng)前活動主窗口的WindowProc接收,經(jīng)過繞行后,可被其他的CCmdTarget對象處理。
(3) WM_COMMAND控件通知統(tǒng)一由子窗口(控件)的父窗口的WindowProc接收并處理,也可以進(jìn)行繞行被其他的CCmdTarget對象處理。
(例如:CFormView具備接受WM_COMMAND控件通知的條件,又具備把WM_COMMAND消息派發(fā)給關(guān)聯(lián)文檔對象處理的能力,
所以給CFormView的WM_COMMAND控件通知是可以讓文檔對象處理的。)
另外,WM_COMMAND控制通知會先調(diào)用ReflectLastMsg反射通知子窗口(控件),如果子窗口(控件)處理了該消息并返回TRUE,則消息會停止分發(fā);
否則,會繼續(xù)調(diào)用OnCmdMsg進(jìn)行命令發(fā)送(如同WM_COMMAND命令消息一樣)。
注:WM_COMMAND命令消息與WM_COMMAND控件通知的相似之處:WM_COMMAND命令消息和WM_COMMAND控制通知都是由WindowProc給OnCommand處理,OnCommand通過wParam和lParam參數(shù)區(qū)分是命令消息或通知消息,然后送給OnCmdMsg處理。事實(shí)上,BN_CLICKED控件通知消息的處理和WM_COMMAND命令消息的處理完全一樣。因?yàn)樵撓⒌耐ㄖa是0,ON_BN_CLICKED(id,memberfunction)和ON_COMMAND(id,memberfunction)是等同的。
(4)WM_NOTIFY消息只是對WM_COMMAND控件通知進(jìn)行了擴(kuò)展,與WM_COMMAND控件通知具有相同的特點(diǎn)。
SendMessage與PostMessage
PostMessage 把消息投遞到消息隊列后,立即返回; SendMessage把消息直接送到窗口過程處理,處理完才返回。
GetMessage與PeekMessage
GetMessage 有消息且該消息不為WM_QUIT,返回TRUE。 有消息且該消息為WM_QUIT,返回FALSE。 沒有消息時,掛起該UI線程,控制權(quán)交還給系統(tǒng)。PeekMessage 有消息返回TRUE,如果沒有消息返回FALSE;不會阻塞。 是否從消息隊列中刪除此消息(PM_REMOVE),由函數(shù)參數(shù)來指定。
要想在沒有消息時做一些工作,就必須使用PeekMessage來抓取消息,以便在沒有消息時,能在OnIdle中執(zhí)行空閑操作(如下):
1 while (TRUE) 2 { 3 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) 4 { 5 if (msg.message == WM_QUIT) 6 break; 7 TranslateMessage(&msg); 8 DispatchMessage(&msg); 9 }10 else 11 {12 OnIdle();13 }14 }例如:MFC使用OnIdle函數(shù)來清理一些臨時對象及未使用的動態(tài)鏈接庫。
只有在OnIdle返回之后程序才能繼續(xù)處理用戶的輸入,因此不應(yīng)在OnIdle進(jìn)行較長的任務(wù)。
MFC消息處理
在CWnd中,MFC使用OnWndMsg來分別處理各類消息:
如果是WM_COMMAND消息,交給OnCommand處理;然后返回。
如果是WM_NOTIFY消息,交給OnNotify處理;然后返回。
如果是WM_ACTIVATE消息,先交給_AfxHandleActivate處理,再繼續(xù)下面的處理。
如果是WM_SETCURSOR消息,先交給_AfxHandleSetCursor處理,然后返回。
如果是其他的窗口消息(包括WM_ACTIVATE消息),則
首先在消息緩沖池(一個hash表,用于加快消息處理函數(shù)的查找)進(jìn)行消息匹配, 若匹配成功,則調(diào)用相應(yīng)的消息處理函數(shù); 若不成功,則在消息目標(biāo)的消息映射數(shù)組中進(jìn)行查找匹配,看它是否能處理當(dāng)前消息。 如果消息目標(biāo)處理了該消息,則會匹配到消息處理函數(shù),調(diào)用它進(jìn)行處理;
否則,該消息沒有被應(yīng)用程序處理,OnWndMsg返回FALSE。
MFC消息映射
消息映射實(shí)際是MFC內(nèi)建的一個消息分派機(jī)制。
把MFC中的宏進(jìn)行展開(如下),可以得到消息映射表整個全貌。
注:GetMessageMap為虛函數(shù)。 {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0}:對象消息映射表的結(jié)束標(biāo)識
窗口消息只能由CWnd對象來處理,采用向基類直線上朔的方式,來查找對應(yīng)的消息響應(yīng)函數(shù)進(jìn)行處理。
一旦找到消息響應(yīng)函數(shù)(若有返回值且為TRUE),就停止上朔。因此,我們經(jīng)常會看到這樣的代碼:
增加一個消息處理函數(shù)來寫我們的邏輯時,MFC ClassWizard會在該函數(shù)之前或之后顯示調(diào)用其基類對應(yīng)的函數(shù),保證基類中邏輯被執(zhí)行。
命令消息可由CCmdTarget對象接收并處理(OnCmdMsg為虛函數(shù)),除了向基類直線上朔方式外,還有命令繞行機(jī)制(要防止形成圈,死循環(huán))。
在某種程度上,控制通知消息由窗口對象處理是一種習(xí)慣和約定。然而,控件通知消息也是可以有CCmdTarget對象接收并處理,并進(jìn)行命令繞行的。
下圖為MFC經(jīng)典單文檔視圖框架的命令消息繞行路線:
函數(shù)調(diào)用過程如下(如果沒有任何對象處理該條WM_COMMAND消息,最后會被::DefWindowProc處理)。
非模態(tài)對話框的消息處理
1 static CAboutDlg aboutDlg;2 aboutDlg.Create(IDD_ABOUTBOX, this);3 aboutDlg.ShowWindow(SW_SHOW);應(yīng)用程序只有一個消息循環(huán)。
對于窗口消息,非模態(tài)對話框(及其子控件)與父窗口(及其子控件)都是用自身的WindowProc函數(shù)接收并處理,互不干擾。
對于命令消息,由當(dāng)前活動主窗口的WindowProc接收(例如:當(dāng)前活動主窗口為非模態(tài)對話框,則命令消息會被非模態(tài)對話框接收)。
可以在當(dāng)前活動主窗口的OnCmdMsg中做命令繞行,使得其他的CCmdTarget對象也可以處理命令消息。
對于控件通知,由其父窗口的WindowProc接收并處理,一般不進(jìn)行命令繞行被其他的CCmdTarget對象處理。
模態(tài)對話框的消息處理
1 CAboutDlg aboutDlg;2 aboutDlg.DoModal();(1) 模態(tài)對話框彈出來后,首先會讓父窗口失效,使其不能接受用戶的輸入(鍵盤鼠標(biāo)消息)。
1 EnableWindow(hwndParent, FALSE) ;(2) 父窗口消息循環(huán)被阻塞(會卡在DoModal處,等待返回),由模態(tài)對話框的消息循環(huán)來接管(因此整個程序不會卡?。?。
接管后,模態(tài)對話框的消息循環(huán)仍然會將屬于父窗口及其子控件的窗口消息(不包括鍵盤鼠標(biāo)相關(guān)的窗口消息)發(fā)送給它們各自的WindowProc窗口函數(shù),進(jìn)行響應(yīng)處理。
(3) 模態(tài)對話框銷毀時(點(diǎn)擊IDOK或IDCANCEL),父窗口消息循環(huán)重新激活,繼續(xù)DoModal后的邏輯。
激活后,父窗口有可以重新接受用戶的輸入(鍵盤鼠標(biāo)消息)。
1 EnableWindow(hwndParent, TRUE) ;從上面的過程中,我們可以得到如下結(jié)論:
對于窗口消息,模態(tài)對話框主窗口(及其子控件)與父窗口(及其子控件)都是用自身的WindowProc函數(shù)接收并處理,互不干擾。
只是父窗口(及其子控件)無法接受到鍵盤鼠標(biāo)消息相關(guān)的窗口消息。
對于命令消息,由模態(tài)對話框主窗口的WindowProc接收。可以在模態(tài)對話框主窗口的OnCmdMsg中做命令繞行,使得其他的CCmdTarget對象也可以處理命令消息。
對于控件通知,由其父窗口的WindowProc接收并處理,一般不進(jìn)行命令繞行被其他的CCmdTarget對象處理。
參考
《深入淺出MFC》- 侯捷
《MFC教程》- 消息映射的實(shí)現(xiàn)
http://blog.csdn.net/kongfuxionghao/article/details/35882533
新聞熱點(diǎn)
疑難解答