標籤:des style blog http color os io 使用 strong
windows訊息機制(MFC)
訊息分類與訊息佇列
Windows中,訊息使用統一的結構體(MSG)來存放資訊,其中message表明訊息的具體的類型,
而wParam,lParam是其最靈活的兩個變數,為不同的訊息類型時,存放資料的含義也不一樣。
time表示產生訊息的時間,pt表示產生訊息時滑鼠的位置。
按照類型,Windows將訊息分為:
(0) 訊息ID範圍
系統定義訊息ID範圍:[0x0000, 0x03ff]
使用者自訂的訊息ID範圍:
WM_USER: 0x0400-0x7FFF (例:WM_USER+10)
WM_APP(winver> 4.0):0x8000-0xBFFF (例:WM_APP+4)
RegisterWindowMessage:0xC000-0xFFFF【用來和其他應用程式通訊,為了ID的唯一性,使用::RegisterWindowMessage來得到該範圍的訊息ID 】
(1) 視窗訊息:即與視窗的內部運作有關的訊息,如建立視窗,繪製視窗,銷毀視窗等。
可以是一般的視窗,也可以是MainFrame,Dialog,控制項等。
如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL等
(2) 當使用者從菜單選中一個命令項目、按下一個快速鍵或者點擊工具列上的一個按鈕,都將發送WM_COMMAND命令訊息。
LOWORD(wParam)表示功能表項目,工具列按鈕或控制項的ID;如果是控制項, HIWORD(wParam)表示控制項訊息類型。
#define LOWORD(l) ((WORD)(l))
#define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))
(3) 隨著控制項的種類越來越多,越來越複雜(如清單控制項、樹控制項等),僅僅將wParam,lParam將視為一個32位不帶正負號的整數,已經裝不下太多資訊了。
為了給父視窗發送更多的資訊,微軟定義了一個新的WM_NOTIFY訊息來擴充WM_COMMAND訊息。
WM_NOTIFY訊息仍然使用MSG訊息結構,只是此時wParam為控制項ID,lParam為一個NMHDR指標,
不同的控制項可以按照規則對NMHDR進行擴充,因此WM_NOTIFY訊息傳送的資訊量可以相當的大。
註:Window 9x 版及以後的新控制項通告訊息不再通過WM_COMMAND 傳送,而是通過WM_NOTIFY 傳送,
但是老控制項的通告訊息, 比如CBN_SELCHANGE 還是通過WM_COMMAND 訊息發送。
(4) windwos也允許程式員定義自己的訊息,使用SendMessage或PostMessage來發送訊息。
windows訊息還可以分為:
(1) 隊列訊息(Queued Messages)
訊息會先儲存在訊息佇列中,訊息迴圈會從此隊列中取出訊息並分發到各視窗處理
如:WM_PAINT,WM_TIMER,WM_CREATE,WM_QUIT,以及滑鼠,鍵盤訊息等。
其中,WM_PAINT,WM_TIMER只有在隊列中沒有其他訊息的時候才會被處理,
WM_PAINT訊息還會被合并以提高效率。其他所有訊息以先進先出(FIFO)的方式被處理。
(2) 非隊列訊息(NonQueued Messages)
訊息會繞過系統訊息佇列和線程訊息佇列,直接發送到視窗過程進行處理
如:WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR,WM_WINDOWPOSCHANGED
Windows系統的整個訊息系統分為3個層級:
① Windows核心的系統訊息佇列
② App的UI線程訊息佇列
③ 處理訊息的表單對象
Windows核心維護著一個全域的系統訊息佇列;按照線程的不同,系統訊息佇列中的訊息會分發到應用程式的UI線程的訊息佇列中;
應用程式的每一個UI線程都有自己的訊息迴圈,會不停地從自己的訊息佇列取出訊息,並發送給Windows表單對象;
每一個表單對象都使用表單過程函數(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擷取當前訊息產生的時間,
用::GetMessagePos擷取當前訊息產生時滑鼠游標所在的位置。
(1) 各個視窗訊息由各個表單(或控制項)自身的WindowProc(虛函數)接收並處理。
(2) WM_COMMAND命令訊息統一由當前活動主視窗的WindowProc接收,經過繞行後,可被其他的CCmdTarget對象處理。
(3) WM_COMMAND控制項通知統一由子視窗(控制項)的父視窗的WindowProc接收並處理,也可以進行繞行被其他的CCmdTarget對象處理。
(例如:CFormView具備接受WM_COMMAND控制項通知的條件,又具備把WM_COMMAND訊息派發給關聯文檔對象處理的能力,
所以給CFormView的WM_COMMAND控制項通知是可以讓文檔對象處理的。)
另外,WM_COMMAND控制通知會先調用ReflectLastMsg反射通知子視窗(控制項),如果子視窗(控制項)處理了該訊息並返回TRUE,則訊息會停止分發;
否則,會繼續調用OnCmdMsg進行命令發送(如同WM_COMMAND命令訊息一樣)。
註:WM_COMMAND命令訊息與WM_COMMAND控制項通知的相似之處:
WM_COMMAND命令訊息和WM_COMMAND控制通知都是由WindowProc給OnCommand處理,
OnCommand通過wParam和lParam參數區分是命令訊息或通知訊息,然後送給OnCmdMsg處理。
事實上,BN_CLICKED控制項通知訊息的處理和WM_COMMAND命令訊息的處理完全一樣。
因為該訊息的通知代碼是0,ON_BN_CLICKED(id,memberfunction)和ON_COMMAND(id,memberfunction)是等同的。
(4)WM_NOTIFY訊息只是對WM_COMMAND控制項通知進行了擴充,與WM_COMMAND控制項通知具有相同的特點。
SendMessage與PostMessage
PostMessage 把訊息投遞到訊息佇列後,立即返回;
SendMessage把訊息直接送到視窗過程處理,處理完才返回。
GetMessage與PeekMessage
GetMessage 有訊息且該訊息不為WM_QUIT,返回TRUE。
有訊息且該訊息為WM_QUIT,返回FALSE。
沒有訊息時,掛起該UI線程,控制權交還給系統。
PeekMessage 有訊息返回TRUE,如果沒有訊息返回FALSE;不會阻塞。
是否從訊息佇列中刪除此訊息(PM_REMOVE),由函數參數來指定。
要想在沒有訊息時做一些工作,就必須使用PeekMessage來抓取訊息,以便在沒有訊息時,能在OnIdle中執行空閑操作(如下):
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函數來清理一些臨時對象及未使用的動態連結程式庫。
只有在OnIdle返回之後程式才能繼續處理使用者的輸入,因此不應在OnIdle進行較長的任務。
MFC訊息處理
在CWnd中,MFC使用OnWndMsg來分別處理各類訊息:
如果是WM_COMMAND訊息,交給OnCommand處理;然後返回。
如果是WM_NOTIFY訊息,交給OnNotify處理;然後返回。
如果是WM_ACTIVATE訊息,先交給_AfxHandleActivate處理,再繼續下面的處理。
如果是WM_SETCURSOR訊息,先交給_AfxHandleSetCursor處理,然後返回。
如果是其他的視窗訊息(包括WM_ACTIVATE訊息),則
首先在訊息緩衝池(一個hash表,用於加快訊息處理函數的尋找)進行訊息匹配,
若匹配成功,則調用相應的訊息處理函數;
若不成功,則在訊息目標的訊息映射數組中進行尋找匹配,看它是否能處理當前訊息。
如果訊息目標處理了該訊息,則會匹配到訊息處理函數,調用它進行處理;
否則,該訊息沒有被應用程式處理,OnWndMsg返回FALSE。
MFC訊息映射
訊息映射實際是MFC內建的一個訊息指派機制。
把MFC中的宏進行展開(如下),可以得到訊息映射表整個全貌。
註:GetMessageMap為虛函數。
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0}:對象訊息映射表的結束標識
視窗訊息只能由CWnd對象來處理,採用向基類直線上朔的方式,來尋找對應的訊息響應函數進行處理。
一旦找到訊息響應函數(若有傳回值且為TRUE),就停止上朔。因此,我們經常會看到這樣的代碼:
增加一個訊息處理函數來寫我們的邏輯時,MFC ClassWizard會在該函數之前或之後顯示調用其基類對應的函數,保證基類中邏輯被執行。
命令訊息可由CCmdTarget對象接收並處理(OnCmdMsg為虛函數),除了向基類直線上朔方式外,還有命令繞行機制(要防止形成圈,死迴圈)。
在某種程度上,控制通知訊息由視窗對象處理是一種習慣和約定。然而,控制項通知訊息也是可以有CCmdTarget對象接收並處理,並進行命令繞行的。
為MFC經典單文檔視圖架構的命令訊息繞行路線:
函數調用過程如下(如果沒有任何對象處理該條WM_COMMAND訊息,最後會被::DefWindowProc處理)。
非模態對話方塊的訊息處理
1 static CAboutDlg aboutDlg;2 aboutDlg.Create(IDD_ABOUTBOX, this);3 aboutDlg.ShowWindow(SW_SHOW);
應用程式只有一個訊息迴圈。
對於視窗訊息,非模態對話方塊(及其子控制項)與父視窗(及其子控制項)都是用自身的WindowProc函數接收並處理,互不干擾。
對於命令訊息,由當前活動主視窗的WindowProc接收(例如:當前活動主視窗為非模態對話方塊,則命令訊息會被非模態對話方塊接收)。
可以在當前活動主視窗的OnCmdMsg中做命令繞行,使得其他的CCmdTarget對象也可以處理命令訊息。
對於控制項通知,由其父視窗的WindowProc接收並處理,一般不進行命令繞行被其他的CCmdTarget對象處理。
模態對話方塊的訊息處理
1 CAboutDlg aboutDlg;2 aboutDlg.DoModal();
(1) 模態對話方塊彈出來後,首先會讓父視窗失效,使其不能接受使用者的輸入(鍵盤滑鼠訊息)。
1 EnableWindow(hwndParent, FALSE) ;
(2) 父視窗訊息迴圈被阻塞(會卡在DoModal處,等待返回),由模態對話方塊的訊息迴圈來接管(因此整個程式不會卡住)。
接管後,模態對話方塊的訊息迴圈仍然會將屬於父視窗及其子控制項的視窗訊息(不包括鍵盤滑鼠相關的視窗訊息)發送給它們各自的WindowProc視窗函數,進行響應處理。
(3) 模態對話方塊銷毀時(點擊IDOK或IDCANCEL),父視窗訊息迴圈重新啟用,繼續DoModal後的邏輯。
啟用後,父視窗有可以重新接受使用者的輸入(鍵盤滑鼠訊息)。
1 EnableWindow(hwndParent, TRUE) ;
從上面的過程中,我們可以得到如下結論:
對於視窗訊息,模態對話方塊主視窗(及其子控制項)與父視窗(及其子控制項)都是用自身的WindowProc函數接收並處理,互不干擾。
只是父視窗(及其子控制項)無法接受到鍵盤滑鼠訊息相關的視窗訊息。
對於命令訊息,由模態對話方塊主視窗的WindowProc接收。可以在模態對話方塊主視窗的OnCmdMsg中做命令繞行,使得其他的CCmdTarget對象也可以處理命令訊息。
對於控制項通知,由其父視窗的WindowProc接收並處理,一般不進行命令繞行被其他的CCmdTarget對象處理。
參考
《深入淺出MFC》- 侯捷
《MFC教程》- 訊息映射的實現
http://blog.csdn.net/kongfuxionghao/article/details/35882533
windows訊息機制(MFC)