消息分類與消息隊列html
Windows中,消息使用統一的結構體(MSG)來存放信息,其中message代表消息的具體的類型,程序員
而wParam,lParam是其最靈活的兩個變量,爲不一樣的消息類型時,存放數據的含義也不同。windows
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,控件等。 ui
如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL等this
(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》- 侯捷