日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深入浅出MFC之6大技术 消息映射( DECLARE_MESSAGE_MAP) 和命令传递 ON_NOTIFY ON_COMMAND ON_MESSAGE 三大难点解析

發布時間:2023/12/9 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入浅出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 三大难点解析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。