Thinking in MFC---消息機制2
引言
在這一篇,我們會介紹一下在窗口類中,如何將消息路由到消息處理函數中去。
一、AFX_MSGMAP_ENTRY
在開始之前,我們有必要了解這個結構體。
struct AFX_MSGMAP_ENTRY
{
UINTnMessage; //windows message
UINTnCode; //control code or WM_NOTIFY code
UINTnID; //control ID (or 0 for windows messages)
UINTnLastID; //used for entries specifying a range of control id's
UINT_PTRnSig; //signature type (action) or pointer to message #
AFX_PMSGpfn; //routine to call (or special value)
};
其中pfn便是消息處理函數指針。
對於這個結構體中的其他成員變量,從它的命名和註釋應該可以瞭解大概。接下來就看一個相關的宏。
二、GetMessageMap
這個函數就如它的命名,獲得消息映射。
我們在使用MFC ClassWizard添加消息響應函數的時候,會自動在我們的窗口類中添加幾行代碼。
DECLARE_MESSAGE_MAP()
BEGIN_MESSAGE_MAP(yourclass, baseclass)
END_MESSAGE_MAP()
讓我們一個一個看上面三個宏。
第一個DECLARE_MESSAGE_MAP(),從它的命名上就能明白它的作用,它是申明MessageMap。我們去它的定義處看看。
#define DECLARE_MESSAGE_MAP() \
protected: \
static constAFX_MSGMAP* PASCAL GetThisMessageMap(); \
virtual constAFX_MSGMAP* GetMessageMap() const; \
這裏申明瞭兩個函數。至於這兩個函數是做什麼,接下來就看它們的定義。就要看接下來兩個宏。
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
PTM_WARNING_DISABLE\
const AFX_MSGMAP* theClass::GetMessageMap() const \
{ return GetThisMessageMap(); } \
const AFX_MSGMAP* PASCALtheClass::GetThisMessageMap() \
{ \
typedef theClass ThisClass; \
typedef baseClass TheBaseClass; \
static constAFX_MSGMAP_ENTRY _messageEntries[] = \
{
上面的第一個函數GetMessageMap,其實在調用GetThisMessageMap,所以我們重點就看第二個函數。
GetThisMessageMap其實就是定義了 AFX_MSGMAP_ENTRY結構體數組,而這個結構體就是我們第一節中的消息結構體。
接下來看一下
#define END_MESSAGE_MAP() \
{0, 0,0, 0, AfxSig_end, (AFX_PMSG)0 } \
}; \
static constAFX_MSGMAP messageMap = \
{&TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
return &messageMap; \
} \
PTM_WARNING_RESTORE
上面這個宏主要是對GetThisMessageMap的結束處理。
從BEGIN_MESSAGE_MAP和END_MESSAGE_MAP這兩個宏來看,GetThisMessageMap便是返回該類的消息映射結構體的數組。
接下來我們就要看MFC是怎麼使用這個消息映射結構體的。
這裏還有兩個宏PTM_WARNING_DISABLE和PTM_WARNING_RESTORE,這兩個宏作用就是在定義上面兩個函數的時候,不會產生warning。
它們兩個的源碼是這樣的
#define PTM_WARNING_DISABLE \
__pragma(warning( push )) \
__pragma(warning( disable : 4867 ))
#define PTM_WARNING_RESTORE \
__pragma(warning( pop ))
有興趣的讀者可以看一下關於warning的一些知識,這個不是我們這裏的重點,所以就不做介紹了。
接下來看一下這個宏
ON_WM_PAINT()
這個宏你在class Wizard中添加WM_PAINT事件之後,便會自動添加在
BEGIN_MESSAGE_MAP(yourclass, baseclass)
ON_WM_PAINT()
END_MESSAGE_MAP()
讓我們轉向ON_WM_PAINT()這個宏定義處看看
#define ON_WM_PAINT() \
{ WM_PAINT, 0, 0, 0, AfxSig_vv, \
(AFX_PMSG)(AFX_PMSGW) \
(static_cast<void (AFX_MSG_CALL CWnd::*)(void) > ( &ThisClass :: OnPaint)) },
我們聯繫之前所講的BEGIN_MESSAGE_MAP中的代碼,其實這個ON_WM_PAINT()爲messageEntries賦值,這裏是一個結構體AFX_MSGMAP_ENTRY。你可以看到第一個參數是WM_PAINT便是nMessage,最後重要的是消息最終路由到那個函數處理,ThisClass :: OnPaint便是處理函數。
類似ON_WM_PAINT()其它宏都是註冊了消息處理事件,告訴MFC什麼消息交給什麼函數處理。
三、窗口消息路由
至於怎麼路由,筆者在網上大致找到了一下這麼一段代碼,說明這段代碼只是用來介紹,並非MFC的源碼。
BOOLCWnd::OnWndMsg(……)//
{
if(message == WM_COMMAND)
OnCommand(wParam,lParam);
if(message == WM_NOTIFY)
OnNotify(wParam,lParam,&lResult);
pMessage = GetMessageMap();
for(; pMessageMap!=NULL; pMessageMap =pMessageMap->pBaseMap)
{
if((lpEntry=AfxFindMessageEntry(pMessageMap->lpEntries,
message,0,0))!=NULL)
break;
}
(this->*(lpEntry->pnf))(……);//調用消息響應函數
}
AFX_MSGMAP_ENTRYAfxFindMessageEntry(……)
{
while(lpEntry->nSign!=AfxSig_end)
{
if(lpEntry->nMessage==nMsg&&lpEntry->nCode==nCode&&nID>=lpEntry->nID
&&nID<=lpEntry->nLastID)
{
return lpEntry;
}
lpEntry++;
}
}
在上面的代碼中可以找到GetMessageMap的聲影,它返回一個消息映射並存放在pMessage變量中。
接下來進行遍歷,找到符合要求的消息映射,找到之後使用存放在其中的消息處理函數來調用消息處理函數。
上面提到的OnCommand和OnNotify,筆者這裏不做介紹,留在下一篇介紹。
四、總結
開始編程不久,便接觸到了MFC。用了MFC寫了自己第一個視窗程序,寫了第一個遊戲,在學校課題演示的時候也是喜歡用MFC做演示。
現在工作了,項目的界面部分也在使用MFC開發的,值得慶幸的是,現在微軟對於MFC的界面做了很大的改善,想起剛開始不斷重寫控件類,那個真是記憶猶新。
在平時編程的時候,VS這個集成工具確實很方便。但現在想把一些MFC零零碎碎的知識整合起來。所以這個系列可能會深入地去介紹MFC。