深入浅出MFC之6大技术 消息映射( DECLARE_MESSAGE_MAP) 和命令传递 ON_NOTIFY ON_COMMAND ON_MESSAGE 三大难点解析
mfc把消息分為3大類
1.命令消息(wm_command)?
? ? 一般來自工具欄和菜單欄,凡是派生自CCmdTarget的類,都可以接收命令消息。
2,標準消息(wm_)
? ? 凡是派生自cwnd類,都可以接收該消息。
3.notify
?由控件產生,向其父窗口通知某種情況。
Windows 消息
消息可以分為系統定義消息和應用定義消息兩大類。
- 系統保留的消息標識符值的范圍是 0x0000 到 0x03FF(WM_USER?- 1)。應用不能使用這些值作為私有消息
- 在 0x0400(WM_USER)到 0x7FFFF 范圍內的值用于私有窗口類的消息標識符。
? windows使用兩種方法將消派發到一個窗口消息處理函數:一是將消息放到消息隊列(先進先出隊列),二是不放到消息隊列,直接發送到窗口消息處理函數,讓窗口處理函數來處理消息。
?? 派發到消息隊列的消息被稱為排隊消息(Queued messages)。它們主要是用戶輸入事件,比如說鼠標或鍵盤消息盤,有WM_MOUSEMOVE消息,WM_LBUTTONDOWN,WM_KEYDOWN,和WM_CHAR消息。還有一些其他的,包括WM_TIMER,WM_PAINT,以及WM_QUIT。大多數其他的消息息,這是直接發送到窗口過程,被稱為非隊列消息(non queued messages)。?
(1) 隊列(Queued)消息
?? ? windows可同時顯示任意數量的窗口。此時,系統使用消息隊列來將鍵盤和鼠標事件正確的派發到正確的窗口。?
windows維護著一個系統消息隊列,以及分別為每個GUI線程維護一個各自的線程消息隊列。為了避免非GUI線程的創建線程消息隊列的開銷,所有線程創建初始化時,均不創建消息隊列。只有當線程第一次調用GDI函數時,系統才會為線程創建消息隊列。所以那些非GUI線程是沒有消息隊列的。
?? ?每當用戶移動鼠標,點擊按鈕或鍵盤時,鼠標或鍵盤的設備驅動程序會將輸入轉換成消息,并將消息放在系統消息隊列里。刪windows會檢查自己的消息隊列,如果消息隊列不為空,則每次取出并刪除一個消息,然后確定消息的目標窗口,然后把消息放到創建這個窗口的線程的線程消息隊列里。線程的消息隊列接收由線程創建的窗口的所有的鼠標和鍵盤消息。然后線程會從隊列中刪除信息,并告訴系統把它們派發到對應的窗口消息處理函數。?
?? 除了WM_PAINT, WM_TIMER和WM_QUIT消息以外,系統總是派發放在在消息隊列的末尾的消息。這將保證讓一個窗口以first-in, first-out的順序接收消息。WM_PAINT,WM_TIMER,和WM_QUIT消息,會一直被保存在隊列中,只有在隊列中沒有其他消息時才會被派發到窗口消息處理函數。此外,同一個窗口的多個WM_PAINT消息被合并成一個WM_PAINT消息,客戶區的所有無效部分也會被合并。這樣是為了減少窗口重繪客戶區的次數。
系統通過填充一個?MSG?結構來將消息投遞到線程的消息隊列,隨后將其拷貝到消息隊列中。?MSG?結構的信息包括:指定窗口的句柄,消息標識符,兩個消息參數,消息投遞的時間,以及鼠標光標的位置。通過使用?PostMessage?(異步的)和?PostThreadMessage?函數,線程可以將一個消息投遞到自己的消息隊列或其他線程的消息隊列。
應用可以使用?GetMessage?來刪除隊列中的消息。要在不刪除消息的情況下檢查隊列消息,應用可以使用?PeekMessage?函數,該函數會使用消息填充?MSG?。
在從隊列刪除消息后,應用可以使用?DispatchMessage?函數來指示系統把消息發送給窗口過程進行處理。DispatchMessage?接收一個?MSG?結構的指針,該結構已經使用?GetMessage?或?PeekMessage?填充過。DispatchMessage?將窗口句柄,消息標識符,和兩個消息參數傳遞給窗口過程,但它不會傳遞時間和鼠標光標位置。應用在處理消息時可以通過?GetMessageTime?和?GetMessagePos?函數檢索時間和位置信息。
?? ?(2) 非隊列(Nonqueued)消息
?? ?Nonqueued消息被立即送往目的地的窗口消息處理函數,繞過了系統的消息隊列和線程消息隊列。系統通常會發送nonqueued消息,來通知那些會影響窗口的事件。例如,當用戶激活一個新的應用程序窗口時,系統會發送一些列消息到窗口,包括WM_ACTIVATE,WM_SETFOCUS,WM_SETCURSOR。這些消息通知窗口被激活,鍵盤輸入被定向到窗口,并且鼠標光標也移到窗口的邊界內。
?? ?Nonqueued消息也有可能來源于應用程序調用系統函數。例如,系統調用SetWindowPos函數移動一個窗口后會發送WM_WINDOWPOSCHANGED消息。 一些函數也發送nonqueued消息, 有BroadcastSystemMessage,BroadcastSystemMessageEx,SendMessage(同步),SendMessageTimeout,和SendNotifyMessage。
常見的消息
WM_CREATE
當應用通過調用?CreateWindow?或?CreateWindow?要求創建一個窗口時,會發送這個消息(在函數返回前消息就被發送)。新窗口的窗口過程在窗口創建后會接收到這個消息,但是是在窗口可見之前。
這是窗口過程接收到的第一個消息。
接收該消息時,窗口過程的?wParam?參數值不被使用,它的?lParam?是一個指向?CREATESTRUCT?結構的指針。這個結構包含了窗口初始化的參數。
處理該消息后,窗口過程應該返回 0 以繼續窗口的創建。如果窗口過程返回 -1,窗口會被銷毀,CreateWindow?或?CreateWindowEx?會返回空句柄。
WM_SIZE
在窗口尺寸改變后向窗口發送該消息。
wParam?是尺寸改變的類型,它可以是下列值中的一個;
- SIZE_MAXHIDE,當其他窗口最大化時,該消息會發送到所有彈出(pop-up)窗口
- SIZE_MAXIMIZED,窗口已經最大化了
- SIZE_MAXSHOW,當其他窗口恢復到之前尺寸時,該消息會發送到所有彈出窗口
- SIZE_MINIMIZED,窗口已經最小化了
- SIZE_RESTORED,窗口的尺寸改變了,但不是最大化和最小化
lParam?的低位是客戶區的新寬度,高位是客戶區的新高度。雖然窗口的寬度和高度是 32 位值,lParam?只包含寬高值的低 16 位。
窗口過程處理該消息后應該返回 0。
WM_PAINT
當系統或其他應用要求對應用窗口的部分進行繪制時,會發送該消息。調用?UpdateWindow?或?RedrawWindow?函數時,或在應用通過使用?GetMessage?或?PeekMessage?獲得?WM_PAINT?并調用?DispatchMessage?函數后,消息會被發送到窗口過程。
lParam?和?wParam?都不被使用。
Invalidate在消息隊列中加入一條WM_PAINT消息,其無效區為整個客戶區。而UpdateWindow直接發送一個WM_PAINT消息,其無效區范圍就是消息隊列中WM_PAINT消息(最多只有一條)的無效區。效果很明顯,調用Invalidate之后,屏幕不一定馬上更新,因為WM_PAINT消息不一定在隊列頭部,而調用UpdateWindow會使WM_PAINT消息馬上執行的,繞過了消息隊列。如果你調用Invalidate之后想馬上更新屏幕,那就加上UpdateWindow()這條語句。
WM_DESTROY
當窗口將被銷毀時發送該消息。在窗口被從屏幕刪除后,它會被發送到刪除的窗口的窗口過程。
該消息首先被發送到被銷毀的窗口,之后發送到子窗口(如果有的話)。在消息的主窗口處理過程中,可以假設所有子窗口還是存在的。
wParam?和?lParam?不被使用。如果處理了該消息,窗口過程應返回 0。
如果被銷毀的窗口是剪切板鏈的一部分,在?WM_DESTROY?消息處理返回前,窗口必須將它從鏈條中移除。
WM_COMMAND
當用戶從菜單選中一個命令時會發送,當控件向它的父窗口發送提醒消息時會發送,當加速鍵被翻譯時會發送。
如果應用處理了該消息,它應該返回 0。
在消息來源是菜單時,wParam?的高位是 0,低位是菜單標識符(IDM_*),lParam?是 0。
消息來源是加速鍵時,wParam?的高位是 1,低位是加速鍵標識符(IDM_*),lParam?是 0。
消息來源是控件時,wParam?的高位是控件特定的通知碼,低位是控件標識符,lParam?是控件窗口句柄。
命令
ON_COMMAND用來響應相應工具欄和菜單欄的命令WM_COMMAND,不用自己解開WM_COMMAND中wParam和lParam中傳送的控件ID。ON_COMMAND對應的消息ID一直都是WM_COMMAND.
操作方法:在類向導中,命令tab頁 對象id為選擇需要的控件ID,消息為command.然后添加處理程序。相當于在
在頭文件添加了 afx_msg void func();? ?
在源文件的BEGIN_MESSAGE_MAP添加了ON_COMMAND(控件ID,func);
消息
如果是系統已定義好的消息,其格式為ON_WM_XX的形式。
操作辦法是:在類向導中,消息tab頁選擇某一個WM_XXX即可。
相當于在在頭文件添加了 afx_msg void func();? ?
在源文件的BEGIN_MESSAGE_MAP添加了ON_WM_XX。
ON_MESSAGE用來響應自定義消息,能夠處理所有的消息響應,在程序中需要自己設定相應的消息響應函數。
ON_MESSAGE(message, memberFxn )?參數:
message:消息的ID。
memberFxn?:映射message的消息函數,該函數的類型必須是以下類型的
afx_msg LRESULT (CWnd::*)(WPARAM, LPARAM)。
操作辦法
1.在頭文件定義 #define WM_MYMESSAGE (WM_USER + 100)? //消息id
2.在頭文件定義afx_msg LRESULT? func(?WPARAM wparam, LPARAM lparam)。
3.在BEGIN_MESSAGE_MAP 添加ON_MESSAGE(WM_MYMESSAGE, OnMyMessage)
4.在源文件添加實現
LRESULT 類名::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
? ? return 0;
}
需要調用postMessage 或者 SendMessage 實現關聯。
ON_NOTIFY是控件向其父窗口發送消息處理的宏,擴展了ON_COMMAND的功能,使用了相應的NMHDR結構.
ON_NOTIFY( wNotifyCode, id, memberFxn )
wNotifyCode:要被處理的通告消息代碼,如 LVN_KEYDOWN。
id:發送通告消息的控件ID。
memberFxn:通告消息發送后被調用的成員函數。
做法
1在頭文件定義
afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result );
2.在BEGIN_MESSAGE_MAP 添加 ON_NOTIFY( wNotifyCode, id, memberFxn)
3.在源文件實現void 類名::memberFxn(NMHDR* pNMHDR, LRESULT* pResult)
{
*pResult = 0;
}
虛函數OnCommand
virtual BOOL?OnCommand(WPARAM?wParam,LPARAM?lParam);
相當于一個“接口”實現多個命令的處理。可以直接在類向導操作。
wParam:表示具體是哪個控件ID.
虛函數OnOntify
virtual BOOL?OnNotify(WPARAM?wParam,?LPARAM?lParam,?LRESULT*?pResult)
?NMHDR*?pNMHDR?=?(NMHDR*)lParam;
NMHDR?{?
HWnd?hWndFrom??相當于原WM_COMMAND傳遞方式的lParam?
UINT?idFrom??相當于原WM_COMMAND傳遞方式的wParam(low-order)?
UINT?code??相當于原WM_COMMAND傳遞方式的Notify?Code(wParam"s?high-order)
?};?
?
WindowProc? ?//回調函數,處理發送給窗口的消息? ?手段更豐富了。? ? ? ? 是一個虛函數,可以通過類向導添加。
LRESULT CALLBACK WindowProc(????????? HWND hwnd,? ? ? ? ? ?
??? UINT uMsg,
??? WPARAM wParam,
??? LPARAM lParam
);
hwnd:指向窗口的句柄。
uMsg:指定消息類型。uMsg可以是WM_COMMAND、?WM_NOTIFY 從而可以實現ON_NOTIFY ?ON_COMMAND的處理。
wParam:指定其余的、消息特定的信息。該參數的內容與UMsg參數值有關。
IParam:指定其余的、消息特定的信息。該參數的內容與uMsg參數值有關
CWnd::WindowProc?調用?OnWndMsg?用來分辨并處理消息;如果是命令消息,交給?OnCommand?處理,如果是通知消息(Notification),交給?OnNotify?處理。而一般的?Windows?消息,就直接在消息映射表中上溯,尋找其歸宿(消息處理程序)
在CWnd中,MFC使用OnWndMsg來分別處理各類消息:
如果是WM_COMMAND消息,交給OnCommand處理;然后返回。
如果是WM_NOTIFY消息,交給OnNotify處理;然后返回。
其中:OnCommand、OnNotify也是虛函數
一般?windows?消息:直線上溯,即從派生類流向到基類。
WM_COMMAND命令消息:拐彎上溯
DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP、END_MESSAGE_MAP宏
?具體方式是在類或者結構體末尾添加DECLARE_MESSAGE_MAP(無分號),然后在定義類成員函數的.CPP文件中,使用BEGIN_MESSAGE_MAP()宏和?END_MESSAGE_MAP()宏來實現對消息的處理。?其中BEGIN_MESSAGE_MAP(參數1,參數2),參數1為該類的類名,參數2為該類基類的類名。
DECLARE_MESSAGE_MAP
#define DECLARE_MESSAGE_MAP() \
protected: \
?? ?static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
?? ?virtual const AFX_MSGMAP* GetMessageMap() const; \
?struct AFX_MSGMAP
?{
? ? ?const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
? ? ?const AFX_MSGMAP_ENTRY* lpEntries;
?};
struct AFX_MSGMAP_ENTRY
?{
??UINT nMessage;?? // windows message
??UINT nCode;????? // control code or WM_NOTIFY code
??UINT nID;??????? // control ID (or 0 for windows messages)
??UINT nLastID;??? // used for entries specifying a range of control id's
??UINT_PTR nSig;?????? // signature type (action) or pointer to message #
? ?AFX_PMSG pfn;??? // routine to call (or special value)
?};
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);
BEGIN_MESSAGE_MAP
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
?? ?PTM_WARNING_DISABLE \
?? ?const AFX_MSGMAP* theClass::GetMessageMap() const \
?? ??? ?{ return GetThisMessageMap(); } \
?? ?const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
?? ?{ \
?? ??? ?typedef theClass ThisClass;?? ??? ??? ??? ??? ??? ? ? \
?? ??? ?typedef baseClass TheBaseClass;?? ??? ??? ??? ??? ? ? \
?? ??? ?static const AFX_MSGMAP_ENTRY _messageEntries[] = ?\
?? ??? ?{
END_MESSAGE_MAP
#define END_MESSAGE_MAP() \
?? ??? ?{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
?? ?}; \
?? ??? ?static const AFX_MSGMAP messageMap = \
?? ??? ?{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
?? ??? ?return &messageMap; \
?? ?}?? ?
可以通過類的成員函數GetMessageMap獲取到消息映射表的信息。
在MFC中消息映射表是從CCmdTarget開始繼承下去的。
會發現這和?RTTI?一樣,還是一個鏈表。將子類的消息映射表和父類的消息映射表聯系起來。
下圖為?MFC?消息映射表
消息傳遞
1.如果是一般的Windows消息(wm_xx),則一定是由派生類流向基類。沒有旁流的可能。
2.如果是命令消息WM_COMMAND,就有奇特的路線。
3.cwinthread 沒有declare、begin、end 宏組。所以BEGIN_MESSAGE_MAP(CWinApp, CCmdTarget)是CCmdTarget而不是cwinthread,所以CWinApp可以跳過cwinthread直接連上CCmdTarget。
4.通過pbasemap 實現消息映射的繼承性。即子類可以繼承基類的消息。類似于虛函數,但不是虛函數的機制,可以減少額外的內存負擔。
5.? 在cwinapp::run 調用pumpmessage,而pumpmessage先調用GetMessage函數獲得或者TranslateMessage函數,然后又調用DispatchMessage傳遞消息,從而把消息推送到afxwndproc,最后流向pwnd->windowproc。消息被分發到回調函數(過程函數),作用是消息傳遞給操作系統,然后操作系統去調用我們的回調函數,也就是說我們在窗體的過程函數中處理消息
在mfc2.5時代(九幾年代),所有窗口類共享同一個窗口函數(即afxwndproc)。
但現在使用的是鉤子技術(即hook),所以要關聯hook章節一起看。
hook操作是在每一個cwnd派生類之對象產生之際發生。表示每一個從cwnd派生的類都有一個鉤子函數。如frame、view。
即:每個窗口類有一個窗口消息處理函數。
所以DispatchMessage把消息推送到hook技術中的afxwndproc.然后afxwndproc ->?AfxCallWndProc ->?WindowProc(該函數是虛函數,可重寫實現自己的內容) ->OnWndMsg(用來分辨并處理消息,
如果是命令消息,就調用OnCommand(該函數也是虛函數,好多派生類都可以重寫,如cwnd、cframe);
如果是通知消息,就調用OnNotify。
而一般Windows消息,直接在消息映射表中上溯,即在OnWndMsg中可以實現)
下面圖是:一般?windows?消息:直線上溯。
說明了基類的函數先執行,派生類的重寫函數、或者重載函數后執行。
能夠上溯,使用的技術是消息映射機制,因為在消息映射中就和基類(pbasemap)進行關聯
下面圖是:WM_COMMAND命令消息
在cframewnd::oncmdmsg處分3路處理。
說明:當framewnd收到wm_command時,消息傳遞的路線是 cview -> cdoc? -> cframewnd -> cwinapp 這樣的先后順序。
延伸:
除了message map還有 data? 、dispatchmap、event map.
總結
以上是生活随笔為你收集整理的深入浅出MFC之6大技术 消息映射( DECLARE_MESSAGE_MAP) 和命令传递 ON_NOTIFY ON_COMMAND ON_MESSAGE 三大难点解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Vb如何设计编程计算机,vb程序设计论文
- 下一篇: 地推话术 地推活动策划方案 活动策划方案