http://www.blog.edu.cn/user2/28873/archives/2005/252013.shtml
http://www.vczx.com/tutorial/mfc/mfc4.php
消息映射的實(shí)現(xiàn)
-
Windows消息概述
Windows應(yīng)用程序的輸入由Windows系統(tǒng)以消息的形式發(fā)送給應(yīng)用程序的窗口。這些窗口通過(guò)窗口過(guò)程來(lái)接收和處理消息,然后把控制返還給Windows。
-
消息的分類(lèi)
- 隊(duì)列消息和非隊(duì)列消息
從消息的發(fā)送途徑上看,消息分兩種:隊(duì)列消息和非隊(duì)列消息。隊(duì)列消息送到系統(tǒng)消息隊(duì)列,然后到線(xiàn)程消息隊(duì)列;非隊(duì)列消息直接送給目的窗口過(guò)程。
這里,對(duì)消息隊(duì)列闡述如下:
Windows維護(hù)一個(gè)系統(tǒng)消息隊(duì)列(System message queue),每個(gè)GUI線(xiàn)程有一個(gè)線(xiàn)程消息隊(duì)列(Thread message queue)。
鼠標(biāo)、鍵盤(pán)事件由鼠標(biāo)或鍵盤(pán)驅(qū)動(dòng)程序轉(zhuǎn)換成輸入消息并把消息放進(jìn)系統(tǒng)消息隊(duì)列,例如WM_MOUSEMOVE、WM_LBUTTONUP、WM_KEYDOWN、WM_CHAR等等。Windows每次從系統(tǒng)消息隊(duì)列移走一個(gè)消息,確定它是送給哪個(gè)窗口的和這個(gè)窗口是由哪個(gè)線(xiàn)程創(chuàng)建的,然后,把它放進(jìn)窗口創(chuàng)建線(xiàn)程的線(xiàn)程消息隊(duì)列。線(xiàn)程消息隊(duì)列接收送給該線(xiàn)程所創(chuàng)建窗口的消息。線(xiàn)程從消息隊(duì)列取出消息,通過(guò)Windows把它送給適當(dāng)?shù)拇翱谶^(guò)程來(lái)處理。
除了鍵盤(pán)、鼠標(biāo)消息以外,隊(duì)列消息還有WM_PAINT、WM_TIMER和WM_QUIT。
這些隊(duì)列消息以外的絕大多數(shù)消息是非隊(duì)列消息。
- 系統(tǒng)消息和應(yīng)用程序消息
從消息的來(lái)源來(lái)看,可以分為:系統(tǒng)定義的消息和應(yīng)用程序定義的消息。
系統(tǒng)消息ID的范圍是從0到WM_USER-1,或0X80000到0XBFFFF;應(yīng)用程序消息從WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF;WM_USER到0X7FFF范圍的消息由應(yīng)用程序自己使用;0XC000到0XFFFF范圍的消息用來(lái)和其他應(yīng)用程序通信,為了ID的唯一性,使用::RegisterWindowMessage來(lái)得到該范圍的消息ID。
-
消息結(jié)構(gòu)和消息處理
-
消息的結(jié)構(gòu)
為了從消息隊(duì)列獲取消息信息,需要使用MSG結(jié)構(gòu)。例如,::GetMessage函數(shù)(從消息隊(duì)列得到消息并從隊(duì)列中移走)和::PeekMessage函數(shù)(從消息隊(duì)列得到消息但是可以不移走)都使用了該結(jié)構(gòu)來(lái)保存獲得的消息信息。
MSG結(jié)構(gòu)的定義如下:
typedef struct tagMSG { // msg
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
該結(jié)構(gòu)包括了六個(gè)成員,用來(lái)描述消息的有關(guān)屬性:
接收消息的窗口句柄、消息標(biāo)識(shí)(ID)、第一個(gè)消息參數(shù)、第二個(gè)消息參數(shù)、消息產(chǎn)生的時(shí)間、消息產(chǎn)生時(shí)鼠標(biāo)的位置。
-
應(yīng)用程序通過(guò)窗口過(guò)程來(lái)處理消息
如前所述,每個(gè)“窗口類(lèi)”都要登記一個(gè)如下形式的窗口過(guò)程:
LRESULT CALLBACK MainWndProc (
HWND hwnd,// 窗口句柄
UINT msg,// 消息標(biāo)識(shí)
WPARAM wParam,//消息參數(shù)1
LPARAM lParam//消息參數(shù)2
)
應(yīng)用程序通過(guò)窗口過(guò)程來(lái)處理消息:非隊(duì)列消息由Windows直接送給目的窗口的窗口過(guò)程,隊(duì)列消息由::DispatchMessage等派發(fā)給目的窗口的窗口過(guò)程。窗口過(guò)程被調(diào)用時(shí),接受四個(gè)參數(shù):
a window handle(窗口句柄);
a message identifier(消息標(biāo)識(shí));
two 32-bit values called message parameters(兩個(gè)32位的消息參數(shù));
需要的話(huà),窗口過(guò)程用::GetMessageTime獲取消息產(chǎn)生的時(shí)間,用::GetMessagePos獲取消息產(chǎn)生時(shí)鼠標(biāo)光標(biāo)所在的位置。
在窗口過(guò)程里,用switch/case分支處理語(yǔ)句來(lái)識(shí)別和處理消息。
-
應(yīng)用程序通過(guò)消息循環(huán)來(lái)獲得對(duì)消息的處理
每個(gè)GDI應(yīng)用程序在主窗口創(chuàng)建之后,都會(huì)進(jìn)入消息循環(huán),接受用戶(hù)輸入、解釋和處理消息。
消息循環(huán)的結(jié)構(gòu)如下:
while (GetMessage(&msg, (HWND) NULL, 0, 0)) {//從消息隊(duì)列得到消息
if (hwndDlgModeless == (HWND) NULL ||
!IsDialogMessage(hwndDlgModeless, &msg) &&
!TranslateAccelerator(hwndMain, haccel, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg); //發(fā)送消息
}
}
消息循環(huán)從消息隊(duì)列中得到消息,如果不是快捷鍵消息或者對(duì)話(huà)框消息,就進(jìn)行消息轉(zhuǎn)換和派發(fā),讓目的窗口的窗口過(guò)程來(lái)處理。
當(dāng)?shù)玫较M_QUIT,或者::GetMessage出錯(cuò)時(shí),退出消息循環(huán)。
- MFC消息處理
使用MFC框架編程時(shí),消息發(fā)送和處理的本質(zhì)也如上所述。但是,有一點(diǎn)需要強(qiáng)調(diào)的是,所有的MFC窗口都使用同一窗口過(guò)程,程序員不必去設(shè)計(jì)和實(shí)現(xiàn)自己的窗口過(guò)程,而是通過(guò)MFC提供的一套消息映射機(jī)制來(lái)處理消息。因此,MFC簡(jiǎn)化了程序員編程時(shí)處理消息的復(fù)雜性。
所謂消息映射,簡(jiǎn)單地講,就是讓程序員指定要某個(gè)MFC類(lèi)(有消息處理能力的類(lèi))處理某個(gè)消息。MFC提供了工具ClassWizard來(lái)幫助實(shí)現(xiàn)消息映射,在處理消息的類(lèi)中添加一些有關(guān)消息映射的內(nèi)容和處理消息的成員函數(shù)。程序員將完成消息處理函數(shù),實(shí)現(xiàn)所希望的消息處理能力。
如果派生類(lèi)要覆蓋基類(lèi)的消息處理函數(shù),就用ClassWizard在派生類(lèi)中添加一個(gè)消息映射條目,用同樣的原型定義一個(gè)函數(shù),然后實(shí)現(xiàn)該函數(shù)。這個(gè)函數(shù)覆蓋派生類(lèi)的任何基類(lèi)的同名處理函數(shù)。
下面幾節(jié)將分析MFC的消息機(jī)制的實(shí)現(xiàn)原理和消息處理的過(guò)程。為此,首先要分析ClassWizard實(shí)現(xiàn)消息映射的內(nèi)幕,然后討論MFC的窗口過(guò)程,分析MFC窗口過(guò)程是如何實(shí)現(xiàn)消息處理的。
-
消息映射的定義和實(shí)現(xiàn)
-
MFC處理的三類(lèi)消息
根據(jù)處理函數(shù)和處理過(guò)程的不同,MFC主要處理三類(lèi)消息:
- Windows消息,前綴以“WM_”打頭,WM_COMMAND例外。Windows消息直接送給MFC窗口過(guò)程處理,窗口過(guò)程調(diào)用對(duì)應(yīng)的消息處理函數(shù)。一般,由窗口對(duì)象來(lái)處理這類(lèi)消息,也就是說(shuō),這類(lèi)消息處理函數(shù)一般是MFC窗口類(lèi)的成員函數(shù)。
- 控制通知消息,是控制子窗口送給父窗口的WM_COMMAND通知消息。窗口過(guò)程調(diào)用對(duì)應(yīng)的消息處理函數(shù)。一般,由窗口對(duì)象來(lái)處理這類(lèi)消息,也就是說(shuō),這類(lèi)消息處理函數(shù)一般是MFC窗口類(lèi)的成員函數(shù)。
需要指出的是,Win32使用新的WM_NOFITY來(lái)處理復(fù)雜的通知消息。WM_COMMAND類(lèi)型的通知消息僅僅能傳遞一個(gè)控制窗口句柄(lparam)、控制窗ID和通知代碼(wparam)。WM_NOTIFY能傳遞任意復(fù)雜的信息。
- 命令消息,這是來(lái)自菜單、工具條按鈕、加速鍵等用戶(hù)接口對(duì)象的WM_COMMAND通知消息,屬于應(yīng)用程序自己定義的消息。通過(guò)消息映射機(jī)制,MFC框架把命令按一定的路徑分發(fā)給多種類(lèi)型的對(duì)象(具備消息處理能力)處理,如文檔、窗口、應(yīng)用程序、文檔模板等對(duì)象。能處理消息映射的類(lèi)必須從CCmdTarget類(lèi)派生。
在討論了消息的分類(lèi)之后,應(yīng)該是討論各類(lèi)消息如何處理的時(shí)候了。但是,要知道怎么處理消息,首先要知道如何映射消息。
-
MFC消息映射的實(shí)現(xiàn)方法
MFC使用ClassWizard幫助實(shí)現(xiàn)消息映射,它在源碼中添加一些消息映射的內(nèi)容,并聲明和實(shí)現(xiàn)消息處理函數(shù)?,F(xiàn)在來(lái)分析這些被添加的內(nèi)容。
在類(lèi)的定義(頭文件)里,它增加了消息處理函數(shù)聲明,并添加一行聲明消息映射的宏DECLARE_MESSAGE_MAP。
在類(lèi)的實(shí)現(xiàn)(實(shí)現(xiàn)文件)里,實(shí)現(xiàn)消息處理函數(shù),并使用IMPLEMENT_MESSAGE_MAP宏實(shí)現(xiàn)消息映射。一般情況下,這些聲明和實(shí)現(xiàn)是由MFC的ClassWizard自動(dòng)來(lái)維護(hù)的??匆粋€(gè)例子:
在A(yíng)ppWizard產(chǎn)生的應(yīng)用程序類(lèi)的源碼中,應(yīng)用程序類(lèi)的定義(頭文件)包含了類(lèi)似如下的代碼:
//{{AFX_MSG(CTttApp)
afx_msg void OnAppAbout();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
應(yīng)用程序類(lèi)的實(shí)現(xiàn)文件中包含了類(lèi)似如下的代碼:
BEGIN_MESSAGE_MAP(CTApp, CWinApp)
//{{AFX_MSG_MAP(CTttApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
頭文件里是消息映射和消息處理函數(shù)的聲明,實(shí)現(xiàn)文件里是消息映射的實(shí)現(xiàn)和消息處理函數(shù)的實(shí)現(xiàn)。它表示讓?xiě)?yīng)用程序?qū)ο筇幚砻钕D_APP_ABOUT,消息處理函數(shù)是OnAppAbout。
為什么這樣做之后就完成了一個(gè)消息映射?這些聲明和實(shí)現(xiàn)到底作了些什么呢?接著,將討論這些問(wèn)題。
-
在聲明與實(shí)現(xiàn)的內(nèi)部
-
DECLARE_MESSAGE_MAP宏:
首先,看DECLARE_MESSAGE_MAP宏的內(nèi)容:
#ifdef _AFXDLL
#define DECLARE_MESSAGE_MAP() \
private: \
static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
static AFX_DATA const AFX_MSGMAP messageMap; \
static const AFX_MSGMAP* PASCAL _GetBaseMessageMap(); \
virtual const AFX_MSGMAP* GetMessageMap() const; \
#else
#define DECLARE_MESSAGE_MAP() \
private: \
static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
static AFX_DATA const AFX_MSGMAP messageMap; \
virtual const AFX_MSGMAP* GetMessageMap() const; \
#endif
DECLARE_MESSAGE_MAP定義了兩個(gè)版本,分別用于靜態(tài)或者動(dòng)態(tài)鏈接到MFC DLL的情形。
-
BEGIN_MESSAE_MAP宏
然后,看BEGIN_MESSAE_MAP宏的內(nèi)容:
#ifdef _AFXDLL
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \
{ return &baseClass::messageMap; } \
const AFX_MSGMAP* theClass::GetMessageMap() const \
{ return &theClass::messageMap; } \
AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
{ &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; \
const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
{ \
#else
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
const AFX_MSGMAP* theClass::GetMessageMap() const \
{ return &theClass::messageMap; } \
AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
{ &baseClass::messageMap, &theClass::_messageEntries[0] }; \
const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
{ \
#endif
#define END_MESSAGE_MAP() \
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
}; \
對(duì)應(yīng)地,BEGIN_MESSAGE_MAP定義了兩個(gè)版本,分別用于靜態(tài)或者動(dòng)態(tài)鏈接到MFC DLL的情形。END_MESSAGE_MAP相對(duì)簡(jiǎn)單,就只有一種定義。
- ON_COMMAND宏
最后,看ON_COMMAND宏的內(nèi)容:
#define ON_COMMAND(id, memberFxn) \
{\
WM_COMMAND,\
CN_COMMAND,\
(WORD)id,\
(WORD)id,\
AfxSig_vv,\
(AFX_PMSG)memberFxn\
};
-
消息映射聲明的解釋
在清楚了有關(guān)宏的定義之后,現(xiàn)在來(lái)分析它們的作用和功能。
消息映射聲明的實(shí)質(zhì)是給所在類(lèi)添加幾個(gè)靜態(tài)成員變量和靜態(tài)或虛擬函數(shù),當(dāng)然它們是與消息映射相關(guān)的變量和函數(shù)。
- 成員變量
有兩個(gè)成員變量被添加,第一個(gè)是_messageEntries,第二個(gè)是messageMap。
AFX_MSGMAP_ENTRY _messageEntries[]
這是一個(gè)AFX_MSGMAP_ENTRY類(lèi)型的數(shù)組變量,是一個(gè)靜態(tài)成員變量,用來(lái)容納類(lèi)的消息映射條目。一個(gè)消息映射條目可以用AFX_MSGMAP_ENTRY結(jié)構(gòu)來(lái)描述。
AFX_MSGMAP_ENTRY結(jié)構(gòu)的定義如下:
struct AFX_MSGMAP_ENTRY
{
//Windows消息ID
UINT nMessage;
//控制消息的通知碼
UINT nCode;
//Windows Control的ID
UINT nID;
//如果是一定范圍的消息被映射,則nLastID指定其范圍
UINT nLastID;
UINT nSig;//消息的動(dòng)作標(biāo)識(shí)
//響應(yīng)消息時(shí)應(yīng)執(zhí)行的函數(shù)(routine to call (or special value))
AFX_PMSG pfn;
};
從上述結(jié)構(gòu)可以看出,每條映射有兩部分的內(nèi)容:第一部分是關(guān)于消息ID的,包括前四個(gè)域;第二部分是關(guān)于消息對(duì)應(yīng)的執(zhí)行函數(shù),包括后兩個(gè)域。
在上述結(jié)構(gòu)的六個(gè)域中,pfn是一個(gè)指向CCmdTarger成員函數(shù)的指針。函數(shù)指針的類(lèi)型定義如下:
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);
當(dāng)使用一條或者多條消息映射條目初始化消息映射數(shù)組時(shí),各種不同類(lèi)型的消息函數(shù)都被轉(zhuǎn)換成這樣的類(lèi)型:不接收參數(shù),也不返回參數(shù)的類(lèi)型。因?yàn)樗锌梢杂邢⒂成涞念?lèi)都是從CCmdTarge派生的,所以可以實(shí)現(xiàn)這樣的轉(zhuǎn)換。
nSig是一個(gè)標(biāo)識(shí)變量,用來(lái)標(biāo)識(shí)不同原型的消息處理函數(shù),每一個(gè)不同原型的消息處理函數(shù)對(duì)應(yīng)一個(gè)不同的nSig。在消息分發(fā)時(shí),MFC內(nèi)部根據(jù)nSig把消息派發(fā)給對(duì)應(yīng)的成員函數(shù)處理,實(shí)際上,就是根據(jù)nSig的值把pfn還原成相應(yīng)類(lèi)型的消息處理函數(shù)并執(zhí)行它。
AFX_MSGMAP messageMap;
這是一個(gè)AFX_MSGMAP類(lèi)型的靜態(tài)成員變量,從其類(lèi)型名稱(chēng)和變量名稱(chēng)可以猜出,它是一個(gè)包含了消息映射信息的變量。的確,它把消息映射的信息(消息映射數(shù)組)和相關(guān)函數(shù)打包在一起,也就是說(shuō),得到了一個(gè)消息處理類(lèi)的該變量,就得到了它全部的消息映射數(shù)據(jù)和功能。AFX_MSGMAP結(jié)構(gòu)的定義如下:
struct AFX_MSGMAP
{
//得到基類(lèi)的消息映射入口地址的數(shù)據(jù)或者函數(shù)
#ifdef _AFXDLL
//pfnGetBaseMap指向_GetBaseMessageMap函數(shù)
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
#else
//pBaseMap保存基類(lèi)消息映射入口_messageEntries的地址
const AFX_MSGMAP* pBaseMap;
#endif
//lpEntries保存消息映射入口_messageEntries的地址
const AFX_MSGMAP_ENTRY* lpEntries;
};
從上面的定義可以看出,通過(guò)messageMap可以得到類(lèi)的消息映射數(shù)組_messageEntries和函數(shù)_GetBaseMessageMap的地址(不使用MFC DLL時(shí),是基類(lèi)消息映射數(shù)組的地址)。
-
成員函數(shù)
用來(lái)得到基類(lèi)消息映射的函數(shù)。
用來(lái)得到自身消息映射的函數(shù)。
-
消息映射實(shí)現(xiàn)的解釋
消息映射實(shí)現(xiàn)的實(shí)質(zhì)是初始化聲明中定義的靜態(tài)成員函數(shù)_messageEntries和messageMap,實(shí)現(xiàn)所聲明的靜態(tài)或虛擬函數(shù)GetMessageMap、_GetBaseMessageMap。
這樣,在進(jìn)入WinMain函數(shù)之前,每個(gè)可以響應(yīng)消息的MFC類(lèi)都生成了一個(gè)消息映射表,程序運(yùn)行時(shí)通過(guò)查詢(xún)?cè)摫砼袛嗍欠裥枰憫?yīng)某條消息。
- 對(duì)消息映射入口表(消息映射數(shù)組)的初始化
如前所述,消息映射數(shù)組的元素是消息映射條目,條目的格式符合結(jié)構(gòu)AFX_MESSAGE_ENTRY的描述。所以,要初始化消息映射數(shù)組,就必須使用符合該格式的數(shù)據(jù)來(lái)填充:如果指定當(dāng)前類(lèi)處理某個(gè)消息,則把和該消息有關(guān)的信息(四個(gè))和消息處理函數(shù)的地址及原型組合成為一個(gè)消息映射條目,加入到消息映射數(shù)組中。
顯然,這是一個(gè)繁瑣的工作。為了簡(jiǎn)化操作,MFC根據(jù)消息的不同和消息處理方式的不同,把消息映射劃分成若干類(lèi)別,每一類(lèi)的消息映射至少有一個(gè)共性:消息處理函數(shù)的原型相同。對(duì)每一類(lèi)消息映射,MFC定義了一個(gè)宏來(lái)簡(jiǎn)化初始化消息數(shù)組的工作。例如,前文提到的ON_COMMAND宏用來(lái)映射命令消息,只要指定命令I(lǐng)D和消息處理函數(shù)即可,因?yàn)閷?duì)這類(lèi)命令消息映射條目,其他四個(gè)屬性都是固定的。ON_COMMAND宏的初始化內(nèi)容如下:
{WM_COMMAND,
CN_COMMAND,
(WORD)ID_APP_ABOUT,
(WORD)ID_APP_ABOUT,
AfxSig_vv,
(AFX_PMSG)OnAppAbout
}
這個(gè)消息映射條目的含義是:消息ID是ID_APP_ABOUT,OnAppAbout被轉(zhuǎn)換成AFX_PMSG指針類(lèi)型,AfxSig_vv是MFC預(yù)定義的枚舉變量,用來(lái)標(biāo)識(shí)OnAppAbout的函數(shù)類(lèi)型為參數(shù)空(Void)、返回空(Void)。
在消息映射數(shù)組的最后,是宏END_MESSAGE_MAP的內(nèi)容,它標(biāo)識(shí)消息處理類(lèi)的消息映射條目的終止。
- 對(duì)messageMap的初始化
如前所述,messageMap的類(lèi)型是AFX_MESSMAP。
經(jīng)過(guò)初始化,域lpEntries保存了消息映射數(shù)組_messageEntries的地址;如果動(dòng)態(tài)鏈接到MFC DLL,則pfnGetBaseMap保存了_GetBaseMessageMap成員函數(shù)的地址;否則pBaseMap保存了基類(lèi)的消息映射數(shù)組的地址。
- 對(duì)函數(shù)的實(shí)現(xiàn)
_GetBaseMessageMap()
它返回基類(lèi)的成員變量messagMap(當(dāng)使用MFC DLL時(shí)),使用該函數(shù)得到基類(lèi)消息映射入口表。
GetMessageMap():
它返回成員變量messageMap,使用該函數(shù)得到自身消息映射入口表。
順便說(shuō)一下,消息映射類(lèi)的基類(lèi)CCmdTarget也實(shí)現(xiàn)了上述和消息映射相關(guān)的函數(shù),不過(guò),它的消息映射數(shù)組是空的。
既然消息映射宏方便了消息映射的實(shí)現(xiàn),那么有必要詳細(xì)的討論消息映射宏。下一節(jié),介紹消息映射宏的分類(lèi)、用法和用途。
-
消息映射宏的種類(lèi)
為了簡(jiǎn)化程序員的工作,MFC定義了一系列的消息映射宏和像AfxSig_vv這樣的枚舉變量,以及標(biāo)準(zhǔn)消息處理函數(shù),并且具體地實(shí)現(xiàn)這些函數(shù)。這里主要討論消息映射宏,常用的分為以下幾類(lèi)。
- 用于Windows消息的宏,前綴為“ON_WM_”。
這樣的宏不帶參數(shù),因?yàn)樗鼘?duì)應(yīng)的消息和消息處理函數(shù)的函數(shù)名稱(chēng)、函數(shù)原型是確定的。MFC提供了這類(lèi)消息處理函數(shù)的定義和缺省實(shí)現(xiàn)。每個(gè)這樣的宏處理不同的Windows消息。
例如:宏ON_WM_CREATE()把消息WM_CREATE映射到OnCreate函數(shù),消息映射條目的第一個(gè)成員nMessage指定為要處理的Windows消息的ID,第二個(gè)成員nCode指定為0。
- 用于命令消息的宏ON_COMMAND
這類(lèi)宏帶有參數(shù),需要通過(guò)參數(shù)指定命令I(lǐng)D和消息處理函數(shù)。這些消息都映射到WM_COMMAND上,也就是將消息映射條目的第一個(gè)成員nMessage指定為WM_COMMAND,第二個(gè)成員nCode指定為CN_COMMAND(即0)。消息處理函數(shù)的原型是void (void),不帶參數(shù),不返回值。
除了單條命令消息的映射,還有把一定范圍的命令消息映射到一個(gè)消息處理函數(shù)的映射宏ON_COMMAND_RANGE。這類(lèi)宏帶有參數(shù),需要指定命令I(lǐng)D的范圍和消息處理函數(shù)。這些消息都映射到WM_COMMAND上,也就是將消息映射條目的第一個(gè)成員nMessage指定為WM_COMMAND,第二個(gè)成員nCode指定為CN_COMMAND(即0),第三個(gè)成員nID和第四個(gè)成員nLastID指定了映射消息的起止范圍。消息處理函數(shù)的原型是void (UINT),有一個(gè)UINT類(lèi)型的參數(shù),表示要處理的命令消息ID,不返回值。
(3)用于控制通知消息的宏
這類(lèi)宏可能帶有三個(gè)參數(shù),如ON_CONTROL,就需要指定控制窗口ID,通知碼和消息處理函數(shù);也可能帶有兩個(gè)參數(shù),如具體處理特定通知消息的宏ON_BN_CLICKED、ON_LBN_DBLCLK、ON_CBN_EDITCHANGE等,需要指定控制窗口ID和消息處理函數(shù)。
控制通知消息也被映射到WM_COMMAND上,也就是將消息映射條目的第一個(gè)成員的nMessage指定為WM_COMMAND,但是第二個(gè)成員nCode是特定的通知碼,第三個(gè)成員nID是控制子窗口的ID,第四個(gè)成員nLastID等于第三個(gè)成員的值。消息處理函數(shù)的原型是void (void),沒(méi)有參數(shù),不返回值。
還有一類(lèi)宏處理通知消息ON_NOTIFY,它類(lèi)似于ON_CONTROL,但是控制通知消息被映射到WM_NOTIFY。消息映射條目的第一個(gè)成員的nMessage被指定為WM_NOTIFY,第二個(gè)成員nCode是特定的通知碼,第三個(gè)成員nID是控制子窗口的ID,第四個(gè)成員nLastID等于第三個(gè)成員的值。消息處理函數(shù)的原型是void (NMHDR*, LRESULT*),參數(shù)1是NMHDR指針,參數(shù)2是LRESULT指針,用于返回結(jié)果,但函數(shù)不返回值。
對(duì)應(yīng)地,還有把一定范圍的控制子窗口的某個(gè)通知消息映射到一個(gè)消息處理函數(shù)的映射宏,這類(lèi)宏包括ON__CONTROL_RANGE和ON_NOTIFY_RANGE。這類(lèi)宏帶有參數(shù),需要指定控制子窗口ID的范圍和通知消息,以及消息處理函數(shù)。
對(duì)于ON__CONTROL_RANGE,是將消息映射條目的第一個(gè)成員的nMessage指定為WM_COMMAND,但是第二個(gè)成員nCode是特定的通知碼,第三個(gè)成員nID和第四個(gè)成員nLastID等于指定了控制窗口ID的范圍。消息處理函數(shù)的原型是void (UINT),參數(shù)表示要處理的通知消息是哪個(gè)ID的控制子窗口發(fā)送的,函數(shù)不返回值。
對(duì)于ON__NOTIFY_RANGE,消息映射條目的第一個(gè)成員的nMessage被指定為WM_NOTIFY,第二個(gè)成員nCode是特定的通知碼,第三個(gè)成員nID和第四個(gè)成員nLastID指定了控制窗口ID的范圍。消息處理函數(shù)的原型是void (UINT, NMHDR*, LRESULT*),參數(shù)1表示要處理的通知消息是哪個(gè)ID的控制子窗口發(fā)送的,參數(shù)2是NMHDR指針,參數(shù)3是LRESULT指針,用于返回結(jié)果,但函數(shù)不返回值。
(4)用于用戶(hù)界面接口狀態(tài)更新的ON_UPDATE_COMMAND_UI宏
這類(lèi)宏被映射到消息WM_COMMND上,帶有兩個(gè)參數(shù),需要指定用戶(hù)接口對(duì)象ID和消息處理函數(shù)。消息映射條目的第一個(gè)成員nMessage被指定為WM_COMMAND,第二個(gè)成員nCode被指定為-1,第三個(gè)成員nID和第四個(gè)成員nLastID都指定為用戶(hù)接口對(duì)象ID。消息處理函數(shù)的原型是 void (CCmdUI*),參數(shù)指向一個(gè)CCmdUI對(duì)象,不返回值。
對(duì)應(yīng)地,有更新一定ID范圍的用戶(hù)接口對(duì)象的宏ON_UPDATE_COMMAND_UI_RANGE,此宏帶有三個(gè)參數(shù),用于指定用戶(hù)接口對(duì)象ID的范圍和消息處理函數(shù)。消息映射條目的第一個(gè)成員nMessage被指定為WM_COMMAND,第二個(gè)成員nCode被指定為-1,第三個(gè)成員nID和第四個(gè)成員nLastID用于指定用戶(hù)接口對(duì)象ID的范圍。消息處理函數(shù)的原型是 void (CCmdUI*),參數(shù)指向一個(gè)CCmdUI對(duì)象,函數(shù)不返回值。之所以不用當(dāng)前用戶(hù)接口對(duì)象ID作為參數(shù),是因?yàn)镃CmdUI對(duì)象包含了有關(guān)信息。
(5)用于其他消息的宏
例如用于用戶(hù)定義消息的ON_MESSAGE。這類(lèi)宏帶有參數(shù),需要指定消息ID和消息處理函數(shù)。消息映射條目的第一個(gè)成員nMessage被指定為消息ID,第二個(gè)成員nCode被指定為0,第三個(gè)成員nID和第四個(gè)成員也是0。消息處理的原型是LRESULT (WPARAM, LPARAM),參數(shù)1和參數(shù)2是消息參數(shù)wParam和lParam,返回LRESULT類(lèi)型的值。
(6)擴(kuò)展消息映射宏
很多普通消息映射宏都有對(duì)應(yīng)的擴(kuò)展消息映射宏,例如:ON_COMMAND對(duì)應(yīng)的ON_COMMAND_EX,ON_ONTIFY對(duì)應(yīng)的ON_ONTIFY_EX,等等。擴(kuò)展宏除了具有普通宏的功能,還有特別的用途。關(guān)于擴(kuò)展宏的具體討論和分析,見(jiàn)4.4.3.2節(jié)。
作為一個(gè)總結(jié),下表列出了這些常用的消息映射宏。
表4-1 常用的消息映射宏
消息映射宏
|
用途
|
ON_COMMAND
|
把command message映射到相應(yīng)的函數(shù)
|
ON_CONTROL
|
把control notification message映射到相應(yīng)的函數(shù)。MFC根據(jù)不同的控制消息,在此基礎(chǔ)上定義了更具體的宏,這樣用戶(hù)在使用時(shí)就不需要指定通知代碼ID,如ON_BN_CLICKED。
|
ON_MESSAGE
|
把user-defined message.映射到相應(yīng)的函數(shù)
|
ON_REGISTERED_MESSAGE
|
把registered user-defined message映射到相應(yīng)的函數(shù),實(shí)際上nMessage等于0x0C000,nSig等于宏的消息參數(shù)。nSig的真實(shí)值為Afxsig_lwl。
|
ON_UPDATE_COMMAND_UI
|
把user interface user update command message映射到相應(yīng)的函數(shù)上。
|
ON_COMMAND_RANGE
|
把一定范圍內(nèi)的command IDs 映射到相應(yīng)的函數(shù)上
|
ON_UPDATE_COMMAND_UI_RANGE
|
把一定范圍內(nèi)的user interface user update command message映射到相應(yīng)的函數(shù)上
|
ON_CONTROL_RANGE
|
把一定范圍內(nèi)的control notification message映射到相應(yīng)的函數(shù)上
|
在表4-1中,宏ON_REGISTERED_MESSAGE的定義如下:
#define ON_REGISTERED_MESSAGE(nMessageVariable, memberFxn) \
{ 0xC000, 0, 0, 0,\
(UINT)(UINT*)(&nMessageVariable), \
/*implied 'AfxSig_lwl'*/ \
(AFX_PMSG)(AFX_PMSGW)(LRESULT\
(AFX_MSG_CALL CWnd::*)\
(WPARAM, LPARAM))&memberFxn }
從上面的定義可以看出,實(shí)際上,該消息被映射到WM_COMMAND(0XC000),指定的registered消息ID存放在nSig域內(nèi),nSig的值在這樣的映射條目下隱含地定為AfxSig_lwl。由于ID和正常的nSig域存放的值范圍不同,所以MFC可以判斷出是否是registered消息映射條目。如果是,則使用AfxSig_lwl把消息處理函數(shù)轉(zhuǎn)換成參數(shù)1為Word、參數(shù)2為long、返回值為long的類(lèi)型。
在介紹完了消息映射的內(nèi)幕之后,應(yīng)該討論消息處理過(guò)程了。由于CCmdTarge的特殊性和重要性,在4.3節(jié)先對(duì)其作一個(gè)大略的介紹。
-
CcmdTarget類(lèi)
除了CObject類(lèi)外,還有一個(gè)非常重要的類(lèi)CCmdTarget。所有響應(yīng)消息或事件的類(lèi)都從它派生。例如,CWinapp,CWnd,CDocument,CView,CDocTemplate,CFrameWnd,等等。
CCmdTarget類(lèi)是MFC處理命令消息的基礎(chǔ)、核心。MFC為該類(lèi)設(shè)計(jì)了許多成員函數(shù)和一些成員數(shù)據(jù),基本上是為了解決消息映射問(wèn)題的,而且,很大一部分是針對(duì)OLE設(shè)計(jì)的。在OLE應(yīng)用中,CCmdTarget是MFC處理模塊狀態(tài)的重要環(huán)節(jié),它起到了傳遞模塊狀態(tài)的作用:其構(gòu)造函數(shù)獲取當(dāng)前模塊狀態(tài),并保存在成員變量m_pModuleState里頭。關(guān)于模塊狀態(tài),在后面章節(jié)講述。
CCmdTarget有兩個(gè)與消息映射有密切關(guān)系的成員函數(shù):DispatchCmdMsg和OnCmdMsg。
-
靜態(tài)成員函數(shù)DispatchCmdMsg
CCmdTarget的靜態(tài)成員函數(shù)DispatchCmdMsg,用來(lái)分發(fā)Windows消息。此函數(shù)是MFC內(nèi)部使用的,其原型如下:
static BOOL DispatchCmdMsg(
CCmdTarget* pTarget,
UINT nID,
int nCode,
AFX_PMSG pfn,
void* pExtra,
UINT nSig,
AFX_CMDHANDLERINFO* pHandlerInfo)
關(guān)于此函數(shù)將在4.4.3.2章節(jié)命令消息的處理中作更詳細(xì)的描述。
-
虛擬函數(shù)OnCmdMsg
CCmdTarget的虛擬函數(shù)OnCmdMsg,用來(lái)傳遞和發(fā)送消息、更新用戶(hù)界面對(duì)象的狀態(tài),其原型如下:
OnCmdMsg(
UINT nID,
int nCode,
void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
框架的命令消息傳遞機(jī)制主要是通過(guò)該函數(shù)來(lái)實(shí)現(xiàn)的。其參數(shù)描述參見(jiàn)4.3.3.2章節(jié)DispacthCMdMessage的參數(shù)描述。
在本書(shū)中,命令目標(biāo)指希望或者可能處理消息的對(duì)象;命令目標(biāo)類(lèi)指命令目標(biāo)的類(lèi)。
CCmdTarget對(duì)OnCmdMsg的默認(rèn)實(shí)現(xiàn):在當(dāng)前命令目標(biāo)(this所指)的類(lèi)和基類(lèi)的消息映射數(shù)組里搜索指定命令消息的消息處理函數(shù)(標(biāo)準(zhǔn)Windows消息不會(huì)送到這里處理)。
這里使用虛擬函數(shù)GetMessageMap得到命令目標(biāo)類(lèi)的消息映射入口數(shù)組_messageEntries,然后在數(shù)組里匹配指定的消息映射條目。匹配標(biāo)準(zhǔn):命令消息ID相同,控制通知代碼相同。因?yàn)镚etMessageMap是虛擬函數(shù),所以可以確認(rèn)當(dāng)前命令目標(biāo)的確切類(lèi)。
如果找到了一個(gè)匹配的消息映射條目,則使用DispachCmdMsg調(diào)用這個(gè)處理函數(shù);
如果沒(méi)有找到,則使用_GetBaseMessageMap得到基類(lèi)的消息映射數(shù)組,查找,直到找到或搜尋了所有的基類(lèi)(到CCmdTarget)為止;
如果最后沒(méi)有找到,則返回FASLE。
每個(gè)從CCmdTarget派生的命令目標(biāo)類(lèi)都可以覆蓋OnCmdMsg,利用它來(lái)確定是否可以處理某條命令,如果不能,就通過(guò)調(diào)用下一命令目標(biāo)的OnCmdMsg,把該命令送給下一個(gè)命令目標(biāo)處理。通常,派生類(lèi)覆蓋OnCmdMsg時(shí),要調(diào)用基類(lèi)的被覆蓋的OnCmdMsg。
在MFC框架中,一些MFC命令目標(biāo)類(lèi)覆蓋了OnCmdMsg,如框架窗口類(lèi)覆蓋了該函數(shù),實(shí)現(xiàn)了MFC的標(biāo)準(zhǔn)命令消息發(fā)送路徑。具體實(shí)現(xiàn)見(jiàn)后續(xù)章節(jié)。
必要的話(huà),應(yīng)用程序也可以覆蓋OnCmdMsg,改變一個(gè)或多個(gè)類(lèi)中的發(fā)送規(guī)定,實(shí)現(xiàn)與標(biāo)準(zhǔn)框架發(fā)送規(guī)定不同的發(fā)送路徑。例如,在以下情況可以作這樣的處理:在要打斷發(fā)送順序的類(lèi)中把命令傳給一個(gè)非MFC默認(rèn)對(duì)象;在新的非默認(rèn)對(duì)象中或在可能要傳出命令的命令目標(biāo)中。
本節(jié)對(duì)CCmdTarget的兩個(gè)成員函數(shù)作一些討論,是為了對(duì)MFC的消息處理有一個(gè)大致印象。后面4.4.3.2節(jié)和4.4.3.3節(jié)將作進(jìn)一步的討論。
-
MFC窗口過(guò)程
前文曾經(jīng)提到,所有的消息都送給窗口過(guò)程處理,MFC的所有窗口都使用同一窗口過(guò)程,消息或者直接由窗口過(guò)程調(diào)用相應(yīng)的消息處理函數(shù)處理,或者按MFC命令消息派發(fā)路徑送給指定的命令目標(biāo)處理。
那么,MFC的窗口過(guò)程是什么?怎么處理標(biāo)準(zhǔn)Windows消息?怎么實(shí)現(xiàn)命令消息的派發(fā)?這些都將是下文要回答的問(wèn)題。
-
MFC窗口過(guò)程的指定
從前面的討論可知,每一個(gè)“窗口類(lèi)”都有自己的窗口過(guò)程。正常情況下使用該“窗口類(lèi)”創(chuàng)建的窗口都使用它的窗口過(guò)程。
MFC的窗口對(duì)象在創(chuàng)建HWND窗口時(shí),也使用了已經(jīng)注冊(cè)的“窗口類(lèi)”,這些“窗口類(lèi)”或者使用應(yīng)用程序提供的窗口過(guò)程,或者使用Windows提供的窗口過(guò)程(例如Windows控制窗口、對(duì)話(huà)框等)。那么,為什么說(shuō)MFC創(chuàng)建的所有HWND窗口使用同一個(gè)窗口過(guò)程呢?
在MFC中,的確所有的窗口都使用同一個(gè)窗口過(guò)程:AfxWndProc或AfxWndProcBase(如果定義了_AFXDLL)。它們的原型如下:
LRESULT CALLBACK
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
LRESULT CALLBACK
AfxWndProcBase(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
這兩個(gè)函數(shù)的原型都如4.1.1節(jié)描述的窗口過(guò)程一樣。
如果動(dòng)態(tài)鏈接到MFC DLL(定義了_AFXDLL),則AfxWndProcBase被用作窗口過(guò)程,否則AfxWndProc被用作窗口過(guò)程。AfxWndProcBase首先使用宏AFX_MANAGE_STATE設(shè)置正確的模塊狀態(tài),然后調(diào)用AfxWndProc。
下面,假設(shè)不使用MFC DLL,討論MFC如何使用AfxWndProc取代各個(gè)窗口的原窗口過(guò)程。
窗口過(guò)程的取代發(fā)生在窗口創(chuàng)建的過(guò)程時(shí),使用了子類(lèi)化(Subclass)的方法。所以,從窗口的創(chuàng)建過(guò)程來(lái)考察取代過(guò)程。從前面可以知道,窗口創(chuàng)建最終是通過(guò)調(diào)用CWnd::CreateEx函數(shù)完成的,分析該函數(shù)的流程,如圖4-1所示。
圖4-1中的CREATESTRUCT結(jié)構(gòu)類(lèi)型的變量cs包含了傳遞給窗口過(guò)程的初始化參數(shù)。CREATESTRUCT結(jié)構(gòu)描述了創(chuàng)建窗口所需要的信息,定義如下:
typedef struct tagCREATESTRUCT {
LPVOID lpCreateParams; //用來(lái)創(chuàng)建窗口的數(shù)據(jù)
HANDLE hInstance; //創(chuàng)建窗口的實(shí)例
HMENU hMenu; //窗口菜單
HWND hwndParent; //父窗口
int cy; //高度
int cx; //寬度
int y; //原點(diǎn)Y坐標(biāo)
int x;//原點(diǎn)X坐標(biāo)
LONG style; //窗口風(fēng)格
LPCSTR lpszName; //窗口名
LPCSTR lpszClass; //窗口類(lèi)
DWORD dwExStyle; //窗口擴(kuò)展風(fēng)格
} CREATESTRUCT;
cs表示的創(chuàng)建參數(shù)可以在創(chuàng)建窗口之前被程序員修改,程序員可以覆蓋當(dāng)前窗口類(lèi)的虛擬成員函數(shù)PreCreateWindow,通過(guò)該函數(shù)來(lái)修改cs的style域,改變窗口風(fēng)格。這里cs的主要作用是保存創(chuàng)建窗口的各種信息,::CreateWindowEx函數(shù)使用cs的各個(gè)域作為參數(shù)來(lái)創(chuàng)建窗口,關(guān)于該函數(shù)見(jiàn)2.2.2節(jié)。
在創(chuàng)建窗口之前,創(chuàng)建了一個(gè)WH_CBT類(lèi)型的鉤子(Hook)。這樣,創(chuàng)建窗口時(shí)所有的消息都會(huì)被鉤子過(guò)程函數(shù)_AfxCbtFilterHook截獲。
AfxCbtFilterHook函數(shù)首先檢查是不是希望處理的Hook──HCBT_CREATEWND。如果是,則先把MFC窗口對(duì)象(該對(duì)象必須已經(jīng)創(chuàng)建了)和剛剛創(chuàng)建的Windows窗口對(duì)象捆綁在一起,建立它們之間的映射(見(jiàn)后面模塊-線(xiàn)程狀態(tài));然后,調(diào)用::SetWindowLong設(shè)置窗口過(guò)程為AfxWndProc,并保存原窗口過(guò)程在窗口類(lèi)成員變量m_pfnSuper中,這樣形成一個(gè)窗口過(guò)程鏈。需要的時(shí)候,原窗口過(guò)程地址可以通過(guò)窗口類(lèi)成員函數(shù)GetSuperWndProcAddr得到。
這樣,AfxWndProc就成為CWnd或其派生類(lèi)的窗口過(guò)程。不論隊(duì)列消息,還是非隊(duì)列消息,都送到AfxWndProc窗口過(guò)程來(lái)處理(如果使用MFC DLL,則AfxWndProcBase被調(diào)用,然后是AfxWndProc)。經(jīng)過(guò)消息分發(fā)之后沒(méi)有被處理的消息,將送給原窗口過(guò)程處理。
最后,有一點(diǎn)可能需要解釋?zhuān)簽槭裁床恢苯又付ù翱谶^(guò)程為AfxWndProc,而要這么大費(fèi)周折呢?這是因?yàn)樵翱谶^(guò)程(“窗口類(lèi)”指定的窗口過(guò)程)常常是必要的,是不可缺少的。
接下來(lái),討論AfxWndProc窗口過(guò)程如何使用消息映射數(shù)據(jù)實(shí)現(xiàn)消息映射。Windows消息和命令消息的處理不一樣,前者沒(méi)有消息分發(fā)的過(guò)程。
-
對(duì)Windows消息的接收和處理
Windows消息送給AfxWndProc窗口過(guò)程之后,AfxWndProc得到HWND窗口對(duì)應(yīng)的MFC窗口對(duì)象,然后,搜索該MFC窗口對(duì)象和其基類(lèi)的消息映射數(shù)組,判定它們是否處理當(dāng)前消息,如果是則調(diào)用對(duì)應(yīng)的消息處理函數(shù),否則,進(jìn)行缺省處理。
下面,以一個(gè)應(yīng)用程序的視窗口創(chuàng)建時(shí),對(duì)WM_CREATE消息的處理為例,詳細(xì)地討論Windows消息的分發(fā)過(guò)程。
用第一章的例子,類(lèi)CTview要處理WM_CREATE消息,使用ClassWizard加入消息處理函數(shù)CTview::OnCreate。下面,看這個(gè)函數(shù)怎么被調(diào)用:
視窗口最終調(diào)用::CreateEx函數(shù)來(lái)創(chuàng)建。由Windows系統(tǒng)發(fā)送WM_CREATE消息給視的窗口過(guò)程AfxWndProc,參數(shù)1是創(chuàng)建的視窗口的句柄,參數(shù)2是消息ID(WM_CREATE),參數(shù)3、4是消息參數(shù)。圖4-2描述了其余的處理過(guò)程。圖中函數(shù)的類(lèi)屬限制并非源碼中所具有的,而是根據(jù)處理過(guò)程得出的判斷。例如,“CWnd::WindowProc”表示CWnd類(lèi)的虛擬函數(shù)WindowProc被調(diào)用,并不一定當(dāng)前對(duì)象是CWnd類(lèi)的實(shí)例,事實(shí)上,它是CWnd派生類(lèi)CTview類(lèi)的實(shí)例;而“CTview::OnCreate”表示CTview的消息處理函數(shù)OnCreate被調(diào)用。下面描述每一步的詳細(xì)處理。
-
從窗口過(guò)程到消息映射
首先,分析AfxWndProc窗口過(guò)程函數(shù)。
LRESULT AfxWndProc(HWND hWnd,
UINT nMsg, WPARAM wParam, LPARAM lParam)
如果收到的消息nMsg不是WM_QUERYAFXWNDPROC(該消息被MFC內(nèi)部用來(lái)確認(rèn)窗口過(guò)程是否使用AfxWndProc),則從hWnd得到對(duì)應(yīng)的MFC Windows對(duì)象(該對(duì)象必須已存在,是永久性<Permanent>對(duì)象)指針pWnd。pWnd所指的MFC窗口對(duì)象將負(fù)責(zé)完成消息的處理。這里,pWnd所指示的對(duì)象是MFC視窗口對(duì)象,即CTview對(duì)象。
然后,把pWnd和AfxWndProc接受的四個(gè)參數(shù)傳遞給函數(shù)AfxCallWndProc執(zhí)行。
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd,
UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0)
MFC使用AfxCallWndProc函數(shù)把消息送給CWnd類(lèi)或其派生類(lèi)的對(duì)象。該函數(shù)主要是把消息和消息參數(shù)(nMsg、wParam、lParam)傳遞給MFC窗口對(duì)象的成員函數(shù)WindowProc(pWnd->WindowProc)作進(jìn)一步處理。如果是WM_INITDIALOG消息,則在調(diào)用WindowProc前后要作一些處理。
WindowProc的函數(shù)原型如下:
LRESULT CWnd::WindowProc(UINT message,
WPARAM wParam, LPARAM lParam)
這是一個(gè)虛擬函數(shù),程序員可以在CWnd的派生類(lèi)中覆蓋它,改變MFC分發(fā)消息的方式。例如,MFC的CControlBar就覆蓋了WindowProc,對(duì)某些消息作了自己的特別處理,其他消息處理由基類(lèi)的WindowProc函數(shù)完成。
但是在當(dāng)前例子中,當(dāng)前對(duì)象的類(lèi)CTview沒(méi)有覆蓋該函數(shù),所以CWnd的WindowProc被調(diào)用。
這個(gè)函數(shù)把下一步的工作交給OnWndMsg函數(shù)來(lái)處理。如果OnWndMsg沒(méi)有處理,則交給DefWindowProc來(lái)處理。
OnWndMsg和DefWindowProc都是CWnd類(lèi)的虛擬函數(shù)。
BOOL CWnd::OnWndMsg( UINT message,
WPARAM wParam, LPARAM lParam,RESULT*pResult );
該函數(shù)是虛擬函數(shù)。
和WindowProc一樣,由于當(dāng)前對(duì)象的類(lèi)CTview沒(méi)有覆蓋該函數(shù),所以CWnd的OnWndMsg被調(diào)用。
在CWnd中,MFC使用OnWndMsg來(lái)分別處理各類(lèi)消息:
如果是WM_COMMAND消息,交給OnCommand處理;然后返回。
如果是WM_NOTIFY消息,交給OnNotify處理;然后返回。
如果是WM_ACTIVATE消息,先交給_AfxHandleActivate處理(后面5.3.3.7節(jié)會(huì)解釋它的處理),再繼續(xù)下面的處理。
如果是WM_SETCURSOR消息,先交給_AfxHandleSetCursor處理;然后返回。
如果是其他的Windows消息(包括WM_ACTIVATE),則
首先在消息緩沖池進(jìn)行消息匹配,
若匹配成功,則調(diào)用相應(yīng)的消息處理函數(shù);
若不成功,則在消息目標(biāo)的消息映射數(shù)組中進(jìn)行查找匹配,看它是否處理當(dāng)前消息。這里,消息目標(biāo)即CTview對(duì)象。
如果消息目標(biāo)處理了該消息,則會(huì)匹配到消息處理函數(shù),調(diào)用它進(jìn)行處理;
否則,該消息沒(méi)有被應(yīng)用程序處理,OnWndMsg返回FALSE。
關(guān)于Windows消息和消息處理函數(shù)的匹配,見(jiàn)下一節(jié)。
缺省處理函數(shù)DefWindowProc將在討論對(duì)話(huà)框等的實(shí)現(xiàn)時(shí)具體分析。
-
Windows消息的查找和匹配
CWnd或者派生類(lèi)的對(duì)象調(diào)用OnWndMsg搜索本對(duì)象或者基類(lèi)的消息映射數(shù)組,尋找當(dāng)前消息的消息處理函數(shù)。如果當(dāng)前對(duì)象或者基類(lèi)處理了當(dāng)前消息,則必定在其中一個(gè)類(lèi)的消息映射數(shù)組中匹配到當(dāng)前消息的處理函數(shù)。
消息匹配是一個(gè)比較耗時(shí)的任務(wù),為了提高效率,MFC設(shè)計(jì)了一個(gè)消息緩沖池,把要處理的消息和匹配到的消息映射條目(條目包含了消息處理函數(shù)的地址)以及進(jìn)行消息處理的當(dāng)前類(lèi)等信息構(gòu)成一條緩沖信息,放到緩沖池中。如果以后又有同樣的消息需要同一個(gè)類(lèi)處理,則直接從緩沖池查找到對(duì)應(yīng)的消息映射條目就可以了。
MFC用哈希查找來(lái)查詢(xún)消息映射緩沖池。消息緩沖池相當(dāng)于一個(gè)哈希表,它是應(yīng)用程序的一個(gè)全局變量,可以放512條最新用到的消息映射條目的緩沖信息,每一條緩沖信息是哈希表的一個(gè)入口。
采用AFX_MSG_CACHE結(jié)構(gòu)描述每條緩沖信息,其定義如下:
struct AFX_MSG_CACHE
{
UINT nMsg;
const AFX_MSGMAP_ENTRY* lpEntry;
const AFX_MSGMAP* pMessageMap;
};
nMsg存放消息ID,每個(gè)哈希表入口有不同的nMsg。
lpEnty存放和消息ID匹配的消息映射條目的地址,它可能是this所指對(duì)象的類(lèi)的映射條目,也可能是這個(gè)類(lèi)的某個(gè)基類(lèi)的映射條目,也可能是空。
pMessageMap存放消息處理函數(shù)匹配成功時(shí)進(jìn)行消息處理的當(dāng)前類(lèi)(this所指對(duì)象的類(lèi))的靜態(tài)成員變量messageMap的地址,它唯一的標(biāo)識(shí)了一個(gè)類(lèi)(每個(gè)類(lèi)的messageMap變量都不一樣)。
this所指對(duì)象是一個(gè)CWnd或其派生類(lèi)的實(shí)例,是正在處理消息的MFC窗口對(duì)象。
哈希查找:使用消息ID的值作為關(guān)鍵值進(jìn)行哈希查找,如果成功,即可從lpEntry獲得消息映射條目的地址,從而得到消息處理函數(shù)及其原型。
如何判斷是否成功匹配呢?有兩條標(biāo)準(zhǔn):
第一,當(dāng)前要處理的消息message在哈希表(緩沖池)中有入口;第二,當(dāng)前窗口對(duì)象(this所指對(duì)象)的類(lèi)的靜態(tài)變量messageMap的地址應(yīng)該等于本條緩沖信息的pMessagMap。MFC通過(guò)虛擬函數(shù)GetMessagMap得到messageMap的地址。
如果在消息緩沖池中沒(méi)有找到匹配,則搜索當(dāng)前對(duì)象的消息映射數(shù)組,看是否有合適的消息處理函數(shù)。
如果匹配到一個(gè)消息處理函數(shù),則把匹配結(jié)果加入到消息緩沖池中,即填寫(xiě)該條消息對(duì)應(yīng)的哈希表入口:
nMsg=message;
pMessageMap=this->GetMessageMap;
lpEntry=查找結(jié)果
然后,調(diào)用匹配到的消息處理函數(shù)。否則(沒(méi)有找到),使用_GetBaseMessageMap得到基類(lèi)的消息映射數(shù)組,查找和匹配;直到匹配成功或搜尋了所有的基類(lèi)(到CCmdTarget)為止。
如果最后沒(méi)有找到,則也把該條消息的匹配結(jié)果加入到緩沖池中。和匹配成功不同的是:指定lpEntry為空。這樣OnWndMsg返回,把控制權(quán)返還給AfxCallWndProc函數(shù),AfxCallWndProc將繼續(xù)調(diào)用DefWndProc進(jìn)行缺省處理。
消息映射數(shù)組的搜索在CCmdTarget::OnCmdMsg函數(shù)中也用到了,而且算法相同。為了提高速度,MFC把和消息映射數(shù)組條目逐一比較、匹配的函數(shù)AfxFindMessageEntry用匯編書(shū)寫(xiě)。
const AFX_MSGMAP_ENTRY* AFXAPI
AfxFindMessageEntry(const AFX_MSGMAP_ENTRY* lpEntry,
UINT nMsg, UINT nCode, UINT nID)
第一個(gè)參數(shù)是要搜索的映射數(shù)組的入口;第二個(gè)參數(shù)是Windows消息標(biāo)識(shí);第三個(gè)參數(shù)是控制通知消息標(biāo)識(shí);第四個(gè)參數(shù)是命令消息標(biāo)識(shí)。
對(duì)Windows消息來(lái)說(shuō),nMsg是每條消息不同的,nID和nCode為0。
對(duì)命令消息來(lái)說(shuō),nMsg固定為WM_COMMAND,nID是每條消息不同,nCode都是CN_COMMAND(定義為0)。
對(duì)控制通知消息來(lái)說(shuō),nMsg固定為WM_COMMAND或者WM_NOTIFY,nID和nCode是每條消息不同。
對(duì)于Register消息,nMsg指定為0XC000,nID和nCode為0。在使用函數(shù)AfxFindMessageEntry得到匹配結(jié)果之后,還必須判斷nSig是否等于message,只有相等才調(diào)用對(duì)應(yīng)的消息處理函數(shù)。
-
Windows消息處理函數(shù)的調(diào)用
對(duì)一個(gè)Windows消息,匹配到了一個(gè)消息映射條目之后,將調(diào)用映射條目所指示的消息處理函數(shù)。
調(diào)用處理函數(shù)的過(guò)程就是轉(zhuǎn)換映射條目的pfn指針為適當(dāng)?shù)暮瘮?shù)類(lèi)型并執(zhí)行它:MFC定義了一個(gè)成員函數(shù)指針mmf,首先把消息處理函數(shù)的地址賦值給該函數(shù)指針,然后根據(jù)消息映射條目的nSig值轉(zhuǎn)換指針的類(lèi)型。但是,要給函數(shù)指針mmf賦值,必須使該指針可以指向所有的消息處理函數(shù),為此則該指針的類(lèi)型是所有類(lèi)型的消息處理函數(shù)指針的聯(lián)合體。
對(duì)上述過(guò)程,MFC的實(shí)現(xiàn)大略如下:
union MessageMapFunctions mmf;
mmf.pfn = lpEntry->pfn;
swithc (value_of_nsig){
…
case AfxSig_is: //OnCreate就是該類(lèi)型
lResult = (this->*mmf.pfn_is)((LPTSTR)lParam);
break;
…
default:
ASSERT(FALSE); break;
}
…
LDispatchRegistered: // 處理registered windows messages
ASSERT(message >= 0xC000);
mmf.pfn = lpEntry->pfn;
lResult = (this->*mmf.pfn_lwl)(wParam, lParam);
…
如果消息處理函數(shù)有返回值,則返回該結(jié)果,否則,返回TRUE。
對(duì)于圖4-1所示的例子,nSig等于A(yíng)fxSig_is,所以將執(zhí)行語(yǔ)句
(this->*mmf.pfn_is)((LPTSTR)lParam)
也就是對(duì)CTview::OnCreate的調(diào)用。
順便指出,對(duì)于Registered窗口消息,消息處理函數(shù)都是同一原型,所以都被轉(zhuǎn)換成lwl型(關(guān)于Registered窗口消息的映射,見(jiàn)4.4.2節(jié))。
綜上所述,標(biāo)準(zhǔn)Windwos消息和應(yīng)用程序消息中的Registered消息,由窗口過(guò)程直接調(diào)用相應(yīng)的處理函數(shù)處理:
如果某個(gè)類(lèi)型的窗口(C++類(lèi))處理了某條消息(覆蓋了CWnd或直接基類(lèi)的處理函數(shù)),則對(duì)應(yīng)的HWND窗口(Winodws window)收到該消息時(shí)就調(diào)用該覆蓋函數(shù)來(lái)處理;如果該類(lèi)窗口沒(méi)有處理該消息,則調(diào)用實(shí)現(xiàn)該處理函數(shù)最直接的基類(lèi)(在C++的類(lèi)層次上接近該類(lèi))來(lái)處理,上述例子中如果CTview不處理WM_CREATE消息,則調(diào)用上一層的CWnd::OnCreate處理;
如果基類(lèi)都不處理該消息,則調(diào)用DefWndProc來(lái)處理。
-
消息映射機(jī)制完成虛擬函數(shù)功能的原理
綜合對(duì)Windows消息的處理來(lái)看,MFC使用消息映射機(jī)制完成了C++虛擬函數(shù)的功能。這主要基于以下幾點(diǎn):
- 所有處理消息的類(lèi)從CCmdTarget派生。
- 使用靜態(tài)成員變量_messageEntries數(shù)組存放消息映射條目,使用靜態(tài)成員變量messageMap來(lái)唯一地區(qū)別和得到類(lèi)的消息映射。
- 通過(guò)GetMessage虛擬函數(shù)來(lái)獲取當(dāng)前對(duì)象的類(lèi)的messageMap變量,進(jìn)而得到消息映射入口。
- 按照先底層,后基層的順序在類(lèi)的消息映射數(shù)組中搜索消息處理函數(shù)?;谶@樣的機(jī)制,一般在覆蓋基類(lèi)的消息處理函數(shù)時(shí),應(yīng)該調(diào)用基類(lèi)的同名函數(shù)。
以上論斷適合于MFC其他消息處理機(jī)制,如對(duì)命令消息的處理等。不同的是其他消息處理有一個(gè)命令派發(fā)/分發(fā)的過(guò)程。
下一節(jié),討論命令消息的接受和處理。
-
對(duì)命令消息的接收和處理
-
MFC標(biāo)準(zhǔn)命令消息的發(fā)送
在SDI或者M(jìn)DI應(yīng)用程序中,命令消息由用戶(hù)界面對(duì)象(如菜單、工具條等)產(chǎn)生,然后送給主邊框窗口。主邊框窗口使用標(biāo)準(zhǔn)MFC窗口過(guò)程處理命令消息。窗口過(guò)程把命令傳遞給MFC主邊框窗口對(duì)象,開(kāi)始命令消息的分發(fā)。MFC邊框窗口類(lèi)CFrameWnd提供了消息分發(fā)的能力。
下面,還是通過(guò)一個(gè)例子來(lái)說(shuō)明命令消息的處理過(guò)程。
使用AppWizard產(chǎn)生一個(gè)單文檔應(yīng)用程序t。從help菜單選擇“About”,就會(huì)彈出一個(gè)ABOUT對(duì)話(huà)框。下面,討論從命令消息的發(fā)出到對(duì)話(huà)框彈出的過(guò)程。
首先,選擇“ About”菜單項(xiàng)的動(dòng)作導(dǎo)致一個(gè)Windows命令消息ID_APP_ABOUT的產(chǎn)生。Windows系統(tǒng)發(fā)送該命令消息到邊框窗口,導(dǎo)致它的窗口過(guò)程AfxWndProc被調(diào)用,參數(shù)1是邊框窗口的句柄,參數(shù)2是消息ID(即WM_COMMAND),參數(shù)3、4是消息參數(shù),參數(shù)3的值是ID_APP_ABOUT。接著的系列調(diào)用如圖4-3所示。
下面分別講述每一層所調(diào)用的函數(shù)。
前4步同對(duì)Windows消息的處理。這里接受消息的HWND窗口是主邊框窗口,因此,AfxWndProc根據(jù)HWND句柄得到的MFC窗口對(duì)象是MFC邊框窗口對(duì)象。
在4.2.2節(jié)談到,如果CWnd::OnWndMsg判斷要處理的消息是命令消息(WM_COMMAND),就調(diào)用OnCommand進(jìn)一步處理。由于OnCommand是虛擬函數(shù),當(dāng)前MFC窗口對(duì)象是邊框窗口對(duì)象,它的類(lèi)從CFrameWnd類(lèi)導(dǎo)出,沒(méi)有覆蓋CWnd的虛擬函數(shù)OnCommand,而CFrameWnd覆蓋了CWnd的OnCommand,所以,CFrameWnd的OnCommand被調(diào)用。換句話(huà)說(shuō),CFrameWnd的OnCommand被調(diào)用是動(dòng)態(tài)約束的結(jié)果。接著介紹的本例子的有關(guān)調(diào)用,也是通過(guò)動(dòng)態(tài)約束而實(shí)際發(fā)生的函數(shù)調(diào)用。
接著的有關(guān)調(diào)用,將不進(jìn)行為什么調(diào)用某個(gè)類(lèi)的虛擬或者消息處理函數(shù)的分析。
(1)CFrameWnd的OnCommand函數(shù)
BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)
參數(shù)wParam的低階word存放了菜單命令nID或控制子窗口ID;如果消息來(lái)自控制窗口,高階word存放了控制通知消息;如果消息來(lái)自加速鍵,高階word值為1;如果消息來(lái)自菜單,高階word值為0。
如果是通知消息,參數(shù)lParam存放了控制窗口的句柄hWndCtrl,其他情況下lParam是0。
在這個(gè)例子里,低階word是ID_APP_ABOUT,高階word是1;lParam是0。
MFC對(duì)CFrameWnd的缺省實(shí)現(xiàn)主要是獲得一個(gè)機(jī)會(huì)來(lái)檢查程序是否運(yùn)行在HELP狀態(tài),需要執(zhí)行上下文幫助,如果不需要,則調(diào)用基類(lèi)的CWnd::OnCommand實(shí)現(xiàn)正常的命令消息發(fā)送。
(2)CWnd的OnCommand函數(shù)
BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
它按一定的順序處理命令或者通知消息,如果發(fā)送成功,返回TRUE,否則,F(xiàn)ALSE。處理順序如下:
如果是命令消息,則調(diào)用OnCmdMsg(nID, CN_UPDATE_COMMAND_UI, &state, NULL)測(cè)試nID命令是否已經(jīng)被禁止,如果這樣,返回FALSE;否則,調(diào)用OnCmdMsg進(jìn)行命令發(fā)送。關(guān)于CN_UPDATE_COMMAND_UI通知消息,見(jiàn)后面用戶(hù)界面狀態(tài)的更新處理。
如果是控制通知消息,則先用ReflectLastMsg反射通知消息到子窗口。如果子窗口處理了該消息,則返回TRUE;否則,調(diào)用OnCmdMsg進(jìn)行命令發(fā)送。關(guān)于通知消息的反射見(jiàn)后面4.4.4.3節(jié)。OnCommand給OnCmdMsg傳遞四個(gè)參數(shù):nID,即命令消息ID;nCode,如果是通知消息則為通知代碼,如果是命令消息則為NC_COMMAND(即0);其余兩個(gè)參數(shù)為空。
(3)CFrameWnd的OnCmdMsg函數(shù)
BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
參數(shù)1是命令I(lǐng)D;如果是通知消息(WM_COMMAND或者WM_NOTIFY),則參數(shù)2表示通知代碼,如果是命令消息,參數(shù)2是0;如果是WM_NOTIFY,參數(shù)3包含了一些額外的信息;參數(shù)4在正常消息處理中應(yīng)該是空。
在這個(gè)例子里,參數(shù)1是命令I(lǐng)D,參數(shù)2為0,參數(shù)3空。
OnCmdMsg是虛擬函數(shù),CFrameWnd覆蓋了該函數(shù),當(dāng)前對(duì)象(this所指)是MFC單文檔的邊框窗口對(duì)象。故CFrameWnd的OnCmdMsg被調(diào)用。CFrameWnd::OnCmdMsg在MFC消息發(fā)送中占有非常重要的地位,MFC對(duì)該函數(shù)的缺省實(shí)現(xiàn)確定了MFC的標(biāo)準(zhǔn)命令發(fā)送路徑:
- 送給活動(dòng)(Active)視處理,調(diào)用活動(dòng)視的OnCmdMsg。由于當(dāng)前對(duì)象是MFC視對(duì)象,所以,OnCmdMsg將搜索CTview及其基類(lèi)的消息映射數(shù)組,試圖得到相應(yīng)的處理函數(shù)。
- 如果視對(duì)象自己不處理,則視得到和它關(guān)聯(lián)的文檔,調(diào)用關(guān)聯(lián)文檔的OnCmdMsg。由于當(dāng)前對(duì)象是MFC視對(duì)象,所以,OnCmdMsg將搜索CTdoc及其基類(lèi)的消息映射數(shù)組,試圖得到相應(yīng)的處理函數(shù)。
- 如果文檔對(duì)象不處理,則它得到管理文檔的文檔模板對(duì)象,調(diào)用文檔模板的OnCmdMsg。由于當(dāng)前對(duì)象是MFC文檔模板對(duì)象,所以,OnCmdMsg將搜索文檔模板類(lèi)及其基類(lèi)的消息映射數(shù)組,試圖得到相應(yīng)的處理函數(shù)。
- 如果文檔模板不處理,則把沒(méi)有處理的信息逐級(jí)返回:文檔模板告訴文檔對(duì)象,文檔對(duì)象告訴視對(duì)象,視對(duì)象告訴邊框窗口對(duì)象。最后,邊框窗口得知,視、文檔、文檔模板都沒(méi)有處理消息。
- CFrameWnd的OnCmdMsg繼續(xù)調(diào)用CWnd::OnCmdMsg(斜體表示有類(lèi)屬限制)來(lái)處理消息。由于CWnd沒(méi)有覆蓋OnCmdMsg,故實(shí)際上調(diào)用了函數(shù)CCmdTarget::OnCmdMsg。由于當(dāng)前對(duì)象是MFC邊框窗口對(duì)象,所以O(shè)nCmdMsg函數(shù)將搜索CMainFrame類(lèi)及其所有基類(lèi)的消息映射數(shù)組,試圖得到相應(yīng)的處理函數(shù)。CWnd沒(méi)有實(shí)現(xiàn)OnCmdMsg卻指定要執(zhí)行其OnCmdMsg函數(shù),可能是為了以后MFC給CWnd實(shí)現(xiàn)了OnCmdMsg之后其他代碼不用改變。
這一步是邊框窗口自己嘗試處理消息。
- 如果邊框窗口對(duì)象不處理,則送給應(yīng)用程序?qū)ο筇幚?。調(diào)用CTApp的OnCmdMsg,由于實(shí)際上CTApp及其基類(lèi)CWinApp沒(méi)有覆蓋OnCmdMsg,故實(shí)際上調(diào)用了函數(shù)CCmdTarget::OnCmdMsg。由于當(dāng)前對(duì)象是MFC應(yīng)用程序?qū)ο?,所以O(shè)nCmdMsg函數(shù)將搜索CTApp類(lèi)及其所有基類(lèi)的的消息映射入口數(shù)組,試圖得到相應(yīng)的處理函數(shù)
- 如果應(yīng)用程序?qū)ο蟛惶幚?,則返回FALSE,表明沒(méi)有命令目標(biāo)處理當(dāng)前的命令消息。這樣,函數(shù)逐級(jí)別返回,OnCmdMsg告訴OnCommand消息沒(méi)有被處理,OnCommand告訴OnWndMsg消息沒(méi)有被處理,OnWndMsg告訴WindowProc消息沒(méi)有被處理,于是WindowProc調(diào)用DefWindowProc進(jìn)行缺省處理。
本例子在第六步中,應(yīng)用程序?qū)D_APP_ABOUT消息作了處理。它找到處理函數(shù)CTApp::OnAbout,使用DispatchCmdMsg派發(fā)消息給該函數(shù)處理。
如果是MDI邊框窗口,標(biāo)準(zhǔn)發(fā)送路徑還有一個(gè)環(huán)節(jié),該環(huán)節(jié)和第二、三、四步所涉及的OnCmdMsg函數(shù),將在下兩節(jié)再次具體分析。
-
命令消息的派發(fā)和消息的多次處理
- 命令消息的派發(fā)
如前3.1所述,CCmdTarget的靜態(tài)成員函數(shù)DispatchCmdMsg用來(lái)派發(fā)命令消息給指定的命令目標(biāo)的消息處理函數(shù)。
static BOOL DispatchCmdMsg(CCmdTarget* pTarget,
UINT nID, int nCode,
AFX_PMSG pfn, void* pExtra, UINT nSig,
AFX_CMDHANDLERINFO* pHandlerInfo)
前面在講CCmdTarget時(shí),提到了該函數(shù)。這里講述它的實(shí)現(xiàn):
第一個(gè)參數(shù)指向處理消息的對(duì)象;第二個(gè)參數(shù)是命令I(lǐng)D;第三個(gè)是通知消息等;第四個(gè)是消息處理函數(shù)地址;第五個(gè)參數(shù)用于存放一些有用的信息,根據(jù)nCode的值表示不同的意義,例如當(dāng)消息是WM_NOFITY,指向一個(gè)NMHDR結(jié)構(gòu)(關(guān)于WM_NOTIFY,參見(jiàn)4.4.4.2節(jié)通知消息的處理);第六個(gè)參數(shù)標(biāo)識(shí)消息處理函數(shù)原型;第七個(gè)參數(shù)是一個(gè)指針,指向AFX_CMDHANDLERINFO結(jié)構(gòu)。前六個(gè)參數(shù)(除了第五個(gè)外)都是向函數(shù)傳遞信息,第五個(gè)和第七個(gè)參數(shù)是雙向的,既向函數(shù)傳遞信息,也可以向調(diào)用者返回信息。
關(guān)于A(yíng)FX_CMDHANDLERINFO結(jié)構(gòu):
struct AFX_CMDHANDLERINFO
{
CCmdTarget* pTarget;
void (AFX_MSG_CALL CCmdTarget::*pmf)(void);
};
第一個(gè)成員是一個(gè)指向命令目標(biāo)對(duì)象的指針,第二個(gè)成員是一個(gè)指向CCmdTarget成員函數(shù)的指針。
該函數(shù)的實(shí)現(xiàn)流程可以如下描述:
首先,它檢查參數(shù)pHandlerInfo是否空,如果不空,則用pTarget和pfn填寫(xiě)其指向的結(jié)構(gòu),返回TRUE;通常消息處理時(shí)傳遞來(lái)的pHandlerInfo空,而在使用OnCmdMsg來(lái)測(cè)試某個(gè)對(duì)象是否處理某條命令時(shí),傳遞一個(gè)非空的pHandlerInfo指針。若返回TRUE,則表示可以處理那條消息。
如果pHandlerInfo空,則進(jìn)行消息處理函數(shù)的調(diào)用。它根據(jù)參數(shù)nSig的值,把參數(shù)pfn的類(lèi)型轉(zhuǎn)換為要調(diào)用的消息處理函數(shù)的類(lèi)型。這種指針轉(zhuǎn)換技術(shù)和前面講述的Windows消息的處理是一樣的。
- 消息的多次處理
如果消息處理函數(shù)不返回值,則DispatchCmdMsg返回TRUE;否則,DispatchCmdMsg返回消息處理函數(shù)的返回值。這個(gè)返回值沿著消息發(fā)送相反的路徑逐級(jí)向上傳遞,使得各個(gè)環(huán)節(jié)的OnCmdMsg和OnCommand得到返回的處理結(jié)果:TRUE或者FALSE,即成功或者失敗。
這樣就產(chǎn)生了一個(gè)問(wèn)題,如果消息處理函數(shù)有意返回一個(gè)FALSE,那么不就傳遞了一個(gè)錯(cuò)誤的信息?例如,OnCmdMsg函數(shù)得到FALSE返回值,就認(rèn)為消息沒(méi)有被處理,它將繼續(xù)發(fā)送消息到下一環(huán)節(jié)。的確是這樣的,但是這不是MFC的漏洞,而是有意這么設(shè)計(jì)的,用來(lái)處理一些特別的消息映射宏,實(shí)現(xiàn)同一個(gè)消息的多次處理。
通常的命令或者通知消息是沒(méi)有返回值的(見(jiàn)4.4.2節(jié)的消息映射宏),僅僅一些特殊的消息處理函數(shù)具有返回值,這類(lèi)消息的消息處理函數(shù)是使用擴(kuò)展消息映射宏映射的,例如:
ON_COMMAND對(duì)應(yīng)的ON_COMMAND_EX
擴(kuò)展映射宏和對(duì)應(yīng)的普通映射宏的參數(shù)個(gè)數(shù)相同,含義一樣。但是擴(kuò)展映射宏的消息處理函數(shù)的原型和對(duì)應(yīng)的普通映射宏相比,有兩個(gè)不同之處:一是多了一個(gè)UINT類(lèi)型的參數(shù),另外就是有返回值(返回BOOL類(lèi)型)。回顧4.4.2章節(jié),范圍映射宏ON_COMMAND_RANGE的消息處理函數(shù)也有一個(gè)這樣的參數(shù),該參數(shù)在兩處的含義是一樣的,例如:命令消息擴(kuò)展映射宏ON_COMMAND_EX定義的消息處理函數(shù)解釋該參數(shù)是當(dāng)前要處理的命令消息ID。有返回值的意義在于:如果擴(kuò)展映射宏的消息處理函數(shù)返回FALSE,則導(dǎo)致當(dāng)前消息被發(fā)送給消息路徑上的下一個(gè)消息目標(biāo)處理。
綜合來(lái)看,ON_COMMAND_EX宏有兩個(gè)功能:
一是可以把多個(gè)命令消息指定給一個(gè)消息處理函數(shù)處理。這類(lèi)似于ON_COMMAND_RANGE宏的作用。不過(guò),這里的多條消息的命令I(lǐng)D或者控制子窗口ID可以不連續(xù),每條消息都需要一個(gè)ON_COMMAND_EX宏。
二是可以讓幾個(gè)消息目標(biāo)處理同一個(gè)命令或者通知或者反射消息。如果消息發(fā)送路徑上較前的命令目標(biāo)不處理消息或者處理消息后返回FALSE,則下一個(gè)命令目標(biāo)將繼續(xù)處理該消息。
對(duì)于通知消息、反射消息,它們也有擴(kuò)展映射宏,而且上述論斷也適合于它們。例如:
ON_NOTIFY對(duì)應(yīng)的ON_NOTIFY_EX
ON_CONTROL對(duì)應(yīng)的ON_CONTROL_EX
ON_CONTROL_REFLECT對(duì)應(yīng)的ON_CONTROL_REFLECT_EX
等等。
范圍消息映射宏也有對(duì)應(yīng)的擴(kuò)展映射宏,例如:
ON_NOTIFY_RANGE對(duì)應(yīng)的ON_NOTIFY_EX_RANGE
ON_COMMAND_RANGE對(duì)應(yīng)的ON_COMMAND_EX_RANGE
使用這些宏的目的在于利用擴(kuò)展宏的第二個(gè)功能:實(shí)現(xiàn)消息的多次處理。
關(guān)于擴(kuò)展消息映射宏的例子,參見(jiàn)13.2..4.4節(jié)和13.2.4.6節(jié)。
-
一些消息處理類(lèi)的OnCmdMsg的實(shí)現(xiàn)
從以上論述知道,OnCmdMsg虛擬函數(shù)在MFC命令消息的發(fā)送中扮演了重要的角色,CFrameWnd的OnCmdMsg實(shí)現(xiàn)了MFC的標(biāo)準(zhǔn)命令消息發(fā)送路徑。
那么,就產(chǎn)生一個(gè)問(wèn)題:如果命令消息不送給邊框窗口對(duì)象,那么就不會(huì)有按標(biāo)準(zhǔn)命令發(fā)送路徑發(fā)送消息的過(guò)程?答案是肯定的。例如一個(gè)菜單被一個(gè)對(duì)話(huà)框窗口所擁有,那么,菜單命令將送給MFC對(duì)話(huà)框窗口對(duì)象處理,而不是MFC邊框窗口處理,當(dāng)然不會(huì)和CFrameWnd的處理流程相同。
但是,有一點(diǎn)需要指出,一般標(biāo)準(zhǔn)的SDI和MDI應(yīng)用程序,只有主邊框窗口擁有菜單和工具條等用戶(hù)接口對(duì)象,只有在用戶(hù)與用戶(hù)接口對(duì)象進(jìn)行交互時(shí),才產(chǎn)生命令,產(chǎn)生的命令必然是送給SDI或者M(jìn)DI程序的主邊框窗口對(duì)象處理。
下面,討論幾個(gè)MFC類(lèi)覆蓋OnCmdMsg虛擬函數(shù)時(shí)的實(shí)現(xiàn)。這些類(lèi)的OnCmdMsg或者可能是標(biāo)準(zhǔn)MFC命令消息路徑的一個(gè)環(huán)節(jié),或者可能是一個(gè)獨(dú)立的處理過(guò)程(對(duì)于其中的MFC窗口類(lèi))。
從分析CView的OnCmdMsg實(shí)現(xiàn)開(kāi)始。
CView::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
首先,調(diào)用CWnd::OnCmdMsg,結(jié)果是搜索當(dāng)前視的類(lèi)和基類(lèi)的消息映射數(shù)組,搜索順序是從下層到上層。若某一層實(shí)現(xiàn)了對(duì)命令消息nID的處理,則調(diào)用它的實(shí)現(xiàn)函數(shù);否則,調(diào)用m_pDocument->OnCmdMsg,把命令消息送給文檔類(lèi)處理。m_pDocument是和當(dāng)前視關(guān)聯(lián)的文檔對(duì)象指針。如果文檔對(duì)象類(lèi)實(shí)現(xiàn)了OnCmdMsg,則調(diào)用它的覆蓋函數(shù);否則,調(diào)用基類(lèi)(例如CDocument)的OnCmdMsg。
接著,討論CDocument的實(shí)現(xiàn)。
BOOL CDocument::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
首先,調(diào)用CCmdTarget::OnCmdMsg,導(dǎo)致當(dāng)前對(duì)象(this)的類(lèi)和基類(lèi)的消息映射數(shù)組被搜索,看是否有對(duì)應(yīng)的消息處理函數(shù)可用。如果有,就調(diào)用它;如果沒(méi)有,則調(diào)用文檔模板的OnCmdMsg函數(shù)(m_pTemplate->OnCmdMsg)把消息送給文檔模板處理。
MFC文檔模板沒(méi)有覆蓋OnCmdMsg,導(dǎo)致基類(lèi)CCmdTarget的OnCmdMsg被調(diào)用,看是否有文檔模板類(lèi)或基類(lèi)實(shí)現(xiàn)了對(duì)消息的處理。是的話(huà),調(diào)用對(duì)應(yīng)的消息處理函數(shù),否則,返回FALSE。從前面的分析知道,CCmdTarget類(lèi)的消息映射數(shù)組是空的,所以這里返回FALSE。
BOOL CDialog::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
- 調(diào)用CWnd::OnCmdMsg,讓對(duì)話(huà)框或其基類(lèi)處理消息。
- 如果還沒(méi)有處理,而且是控制消息或系統(tǒng)命令或非命令按鈕,則返回FALSE,不作進(jìn)一步處理。否則,調(diào)用父窗口的OnCmdmsg(GetParent()->OnCmdmsg)把消息送給父窗口處理。
- 如果仍然沒(méi)有處理,則調(diào)用當(dāng)前線(xiàn)程的OnCmdMsg(GetThread()->OnCmdMsg)把消息送給線(xiàn)程對(duì)象處理。
- 如果最后沒(méi)有處理,返回FALSE。
對(duì)于MDI應(yīng)用程序,MDI主邊框窗口首先是把命令消息發(fā)送給活動(dòng)的MDI文檔邊框窗口進(jìn)行處理。MDI主邊框窗口對(duì)OnCmdMsg的實(shí)現(xiàn)函數(shù)的原型如下:
BOOL CMDIFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
- 如果有激活的文檔邊框窗口,則調(diào)用它的OnCmdMsg(MDIGetActive()->OnCmdMsg)把消息交給它進(jìn)行處理。MFC的文檔邊框窗口類(lèi)并沒(méi)有覆蓋OnCmdMsg函數(shù),所以基類(lèi)CFrameWnd的函數(shù)被調(diào)用,導(dǎo)致文檔邊框窗口的活動(dòng)視、文檔邊框窗口本身、應(yīng)用程序?qū)ο笠来蝸?lái)進(jìn)行消息處理。
- 如果文檔邊框窗口沒(méi)有處理,調(diào)用CFrameWnd::OnCmdMsg把消息按標(biāo)準(zhǔn)路徑發(fā)送,重復(fù)第一次的步驟,不過(guò)對(duì)于MDI邊框窗口來(lái)說(shuō)不存在活動(dòng)視,所以省卻了讓視處理消息的必要;接著讓MDI邊框窗口本身來(lái)處理消息,如果它還沒(méi)有處理,則讓?xiě)?yīng)用程序?qū)ο筮M(jìn)行消息處理──雖然這是一個(gè)無(wú)用的重復(fù)。
除了CView、CDocument和CMDIFrameWnd類(lèi),還有幾個(gè)OLE相關(guān)的類(lèi)覆蓋了OnCmdMsg函數(shù)。OLE的處理本書(shū)暫不涉及,CDialog::OnCmdMsg將在對(duì)話(huà)框章節(jié)專(zhuān)項(xiàng)討論其具體實(shí)現(xiàn)。
-
一些消息處理類(lèi)的OnCommand的實(shí)現(xiàn)
除了虛擬函數(shù)OnCmdMsg,還有一個(gè)虛擬函數(shù)OnCommand在命令消息的發(fā)送中占有重要地位。在處理命令或者通知消息時(shí),OnCommand被MFC窗口過(guò)程調(diào)用,然后它調(diào)用OnCmdMsg按一定路徑傳送消息。除了CWnd類(lèi)和一些OLE相關(guān)類(lèi)外,MFC里主要還有MDI邊框窗口實(shí)現(xiàn)了OnCommand。
BOOL CMDIFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)
第一,如果存在活動(dòng)的文檔邊框窗口,則使用AfxCallWndProc調(diào)用它的窗口過(guò)程,把消息送給文檔邊框窗口來(lái)處理。這將導(dǎo)致文檔邊框窗口的OnCmdMsg作如下的處理:
活動(dòng)視處理消息→與視關(guān)聯(lián)的文檔處理消息→本文檔邊框窗口處理消息→應(yīng)用程序?qū)ο筇幚硐ⅰ臋n邊框窗口缺省處理
任何一個(gè)環(huán)節(jié)如果處理消息,則不再向下發(fā)送消息,處理終止。如果消息仍然沒(méi)有被處理,就只有交給主邊框窗口了。
第二,第一步?jīng)]有處理命令,繼續(xù)調(diào)用CFrameWnd::OnCommand,將導(dǎo)致CMDIFrameWnd的OnCmdMsg被調(diào)用。從前面的分析知道,將再次把消息送給MDI邊框窗口的活動(dòng)文檔邊框窗口,第一步的過(guò)程除了文檔邊框窗口缺省處理外都將被重復(fù)。具體的處理過(guò)程見(jiàn)前文的CMDIFrameWnd::OnCmdMsg函數(shù)。
- 對(duì)于MDI消息,如果主邊框窗口還不處理的話(huà),交給CMDIFrameWnd的DefWindowProc作缺省處理。
- 消息沒(méi)有處理,返回FALSE。
上述分析綜合了OnCommand和OnCmdMsg的處理,它們是在MFC內(nèi)部MDI邊框窗口處理命令消息的完整的流程和標(biāo)準(zhǔn)的步驟。整個(gè)處理過(guò)程再次表明了邊框窗口在處理命令消息時(shí)的中心作用。從程序員的角度來(lái)看,可以認(rèn)為整個(gè)標(biāo)準(zhǔn)處理路徑如下:
活動(dòng)視處理消息→與視關(guān)聯(lián)的文檔處理消息→本文檔邊框窗口處理消息→應(yīng)用程序?qū)ο筇幚硐ⅰ臋n邊框窗口缺省處理→MDI邊框窗口處理消息→MDI邊框窗口缺省處理
任何一個(gè)環(huán)節(jié)如果處理消息,不再向下發(fā)送消息,急處理終止。
-
對(duì)控制通知消息的接收和處理
-
WM_COMMAND控制通知消息的處理
WM_COMMAND控制通知消息的處理和WM_COMMAND命令消息的處理類(lèi)似,但是也有不同之處。
首先,分析處理WM_COMMAND控制通知消息和命令消息的相似處。如前所述,命令消息和控制通知消息都是由窗口過(guò)程給OnCommand處理(參見(jiàn)CWnd::OnWndMsg的實(shí)現(xiàn)),OnCommand通過(guò)wParam和lParam參數(shù)區(qū)分是命令消息或通知消息,然后送給OnCmdMsg處理(參見(jiàn)CWnd::OnCommnd的實(shí)現(xiàn))。
其次,兩者的不同之處是:
- 命令消息一般是送給主邊框窗口的,這時(shí),邊框窗口的OnCmdMsg被調(diào)用;而控制通知消息送給控制子窗口的父窗口,這時(shí),父窗口的OnCmdMsg被調(diào)用。
- OnCmdMsg處理命令消息時(shí),通過(guò)命令分發(fā)可以由多種命令目標(biāo)處理,包括非窗口對(duì)象如文檔對(duì)象等;而處理控制通知消息時(shí),不會(huì)有消息分發(fā)的過(guò)程,控制通知消息最終肯定是由窗口對(duì)象處理的。
不過(guò),在某種程度上可以說(shuō),控制通知消息由窗口對(duì)象處理是一種習(xí)慣和約定。當(dāng)使用ClassWizard進(jìn)行消息映射時(shí),它不提供把控制通知消息映射到非窗口對(duì)象的機(jī)會(huì)。但是,手工地添加消息映射,讓非窗口對(duì)象處理控制通知消息的可能是存在的。例如,對(duì)于CFormView,一方面它具備接受WM_COMMAND通知消息的條件,另一方面,具備把WM_COMMAND消息派發(fā)給關(guān)聯(lián)文檔對(duì)象處理的能力,所以給CFormView的通知消息是可以讓文檔對(duì)象處理的。
事實(shí)上,BN_CLICKED控制通知消息的處理和命令消息的處理完全一樣,因?yàn)樵撓⒌耐ㄖa是0,ON_BN_CLICKED(id,memberfunction)和ON_COMMAND(id,memberfunction)是等同的。
此外,MFC的狀態(tài)更新處理機(jī)制就是建立在通知消息可以發(fā)送給各種命令目標(biāo)的基礎(chǔ)之上的。關(guān)于MFC的狀態(tài)更新處理機(jī)制,見(jiàn)后面4.4.4.4節(jié)的討論。
- 控制通知消息可以反射給子窗口處理。OnCommand判定當(dāng)前消息是WM_COMAND通知消息之后,首先它把消息反射給控制子窗口處理,如果子窗口處理了反射消息,OnCommand不會(huì)繼續(xù)調(diào)用OnCmdMsg讓父窗口對(duì)象來(lái)處理通知消息。
-
WM_NOTIFY消息及其處理:
(1)WM_NOTIFY消息
還有一種通知消息WM_NOTIFY,在Win32中用來(lái)傳遞信息復(fù)雜的通知消息。WM_NOTIFY消息怎么來(lái)傳遞復(fù)雜的信息呢?WM_NOTIFY的消息參數(shù)wParam包含了發(fā)送通知消息的控制窗口ID,另一個(gè)參數(shù)lParam包含了一個(gè)指針。該指針指向一個(gè)NMHDR結(jié)構(gòu),或者更大的結(jié)構(gòu),只要它的第一個(gè)結(jié)構(gòu)成員是NMHDR結(jié)構(gòu)。
NMHDR結(jié)構(gòu):
typedef struct tagNMHDR {
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR;
上述結(jié)構(gòu)有三個(gè)成員,分別是發(fā)送通知消息的控制窗口的句柄、ID和通知消息代碼。
舉一個(gè)更大、更復(fù)雜的結(jié)構(gòu)例子:列表控制窗發(fā)送LVN_KEYDOWN控制通知消息,則lParam包含了一個(gè)指向LV_KEYDOWN結(jié)構(gòu)的指針。其結(jié)構(gòu)如下:
typedef struct tagLV_KEYDOWN {
NMHDR hdr;
WORD wVKey;
UINT flags;
}LV_KEYDOWN;
它的第一個(gè)結(jié)構(gòu)成員hdr就是NMHDR類(lèi)型。其他成員包含了更多的信息:哪個(gè)鍵被按下,哪些輔助鍵(SHIFT、CTRL、ALT等)被按下。
(2)WM_NOTIFY消息的處理
在分析CWnd::OnWndMsg函數(shù)時(shí),曾指出當(dāng)消息是WM_NOTIFY時(shí),它把消息傳遞給OnNotify虛擬函數(shù)處理。這是一個(gè)虛擬函數(shù),類(lèi)似于OnCommand,CWnd和派生類(lèi)都可以覆蓋該函數(shù)。OnNotify的函數(shù)原型如下:
BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult)
參數(shù)1是發(fā)送通知消息的控制窗口ID,沒(méi)有被使用;參數(shù)2是一個(gè)指針;參數(shù)3指向一個(gè)long類(lèi)型的數(shù)據(jù),用來(lái)返回處理結(jié)果。
WM_NOTIFY消息的處理過(guò)程如下:
- 反射消息給控制子窗口處理。
- 如果子窗口不處理反射消息,則交給OnCmdMsg處理。給OnCmdMsg的四個(gè)參數(shù)分別如下:第一個(gè)是命令消息ID,第四個(gè)為空;第二個(gè)高階word是WM_NOTIFY,低階word是通知消息;第三個(gè)參數(shù)是指向AFX_NOTIFY結(jié)構(gòu)的指針。第二、三個(gè)參數(shù)有別于OnCommand送給OnCmdMsg的參數(shù)。
AFX_NOTIFY結(jié)構(gòu):
struct AFX_NOTIFY
{
LRESULT* pResult;
NMHDR* pNMHDR;
};
pNMHDR的值來(lái)源于參數(shù)2 lParam,該結(jié)構(gòu)的域pResult用來(lái)保存處理結(jié)果,域pNMHDR用來(lái)傳遞信息。
OnCmdMsg后續(xù)的處理和WM_COMMAND通知消息基本相同,只是在派發(fā)消息給消息處理函數(shù)時(shí),DispatchMsdMsg的第五個(gè)參數(shù)pExtra指向OnCmdMsg傳遞給它的AFX_NOTIFY類(lèi)型的參數(shù),而不是空指針。這樣,處理函數(shù)就得到了復(fù)雜的通知消息信息。
-
消息反射
(1)消息反射的概念
前面討論控制通知消息時(shí),曾經(jīng)多次提到了消息反射。MFC提供了兩種消息反射機(jī)制,一種用于OLE控件,一種用于Windows控制窗口。這里只討論后一種消息反射。
Windows控制常常發(fā)送通知消息給它們的父窗口,通??刂葡⒂筛复翱谔幚?。但是在MFC里頭,父窗口在收到這些消息后,或者自己處理,或者反射這些消息給控制窗口自己處理,或者兩者都進(jìn)行處理。如果程序員在父窗口類(lèi)覆蓋了通知消息的處理(假定不調(diào)用基類(lèi)的實(shí)現(xiàn)),消息將不會(huì)反射給控制子窗口。這種反射機(jī)制是MFC實(shí)現(xiàn)的,便于程序員創(chuàng)建可重用的控制窗口類(lèi)。
MFC的CWnd類(lèi)處理以下控制通知消息時(shí),必要或者可能的話(huà),把它們反射給子窗口處理:
WM_CTLCOLOR,
WM_VSCROLL,WM_HSCROLL,
WM_DRAWITEM,WM_MEASUREITEM,
WM_COMPAREITEM,WM_DELETEITEM,
WM_CHARTOITEM,WM_VKEYTOITEM,
WM_COMMAND、WM_NOTIFY。
例如,對(duì)WM_VSCROLL、WM_HSCROLL消息的處理,其消息處理函數(shù)如下:
void CWnd::OnHScroll(UINT, UINT, CScrollBar* pScrollBar)
{
//如果是一個(gè)滾動(dòng)條控制,首先反射消息給它處理
if (pScrollBar != NULL && pScrollBar->SendChildNotifyLastMsg())
return; //控制窗口成功處理了該消息
Default();
}
又如:在討論OnCommand和OnNofity函數(shù)處理通知消息時(shí),都曾經(jīng)指出,它們首先調(diào)用ReflectLastMsg把消息反射給控制窗口處理。
為了利用消息反射的功能,首先需要從適當(dāng)?shù)腗FC窗口派生出一個(gè)控制窗口類(lèi),然后使用ClassWizard給它添加消息映射條目,指定它處理感興趣的反射消息。下面,討論反射消息映射宏。
上述消息的反射消息映射宏的命名遵循以下格式:“ON”前綴+消息名+“REFLECT”后綴,例如:消息WM_VSCROLL的反射消息映射宏是ON_WM_VSCROLL_REFECT。但是通知消息WM_COMMAND和WM_NOTIFY是例外,分別為ON_CONTROL_REFLECT和ON_NOFITY_REFLECT。狀態(tài)更新通知消息的反射消息映射宏是ON_UPDATE_COMMAND_UI_REFLECT。
消息處理函數(shù)的名字和去掉“WM_”前綴的消息名相同 ,例如WM_HSCROLL反射消息處理函數(shù)是Hscroll。
消息處理函數(shù)的原型這里不一一列舉了。
這些消息映射宏和消息處理函數(shù)的原型可以借助于ClassWizard自動(dòng)地添加到程序中。ClassWizard添加消息處理函數(shù)時(shí),可以處理的反射消息前面有一個(gè)等號(hào),例如處理WM_HSCROLL的反射消息,選擇映射消息“=EN_HSC ROLL”。ClassWizard自動(dòng)的添加消息映射宏和處理函數(shù)到框架文件。
(2)消息反射的處理過(guò)程
如果不考慮有OLE控件的情況,消息反射的處理流程如下圖所示:
首先,調(diào)用CWnd的成員函數(shù)SendChildNotifyLastMsg,它從線(xiàn)程狀態(tài)得到本線(xiàn)程最近一次獲取的消息(關(guān)于線(xiàn)程狀態(tài),后面第9章會(huì)詳細(xì)介紹)和消息參數(shù),并且把這些參數(shù)傳遞給函數(shù)OnChildNotify。注意,當(dāng)前的CWnd對(duì)象就是MFC控制子窗口對(duì)象。
OnChlidNofify是CWnd定義的虛擬函數(shù),不考慮OLE控制的話(huà),它僅僅只調(diào)用ReflectChildNotify。OnChlidNofify可以被覆蓋,所以如果程序員希望處理某個(gè)控制的通知消息,除了采用消息映射的方法處理通知反射消息以外,還可以覆蓋OnChlidNotify虛擬函數(shù),如果成功地處理了通知消息,則返回TRUE。
ReflectChildNotify是CWnd的成員函數(shù),完成反射消息的派發(fā)。對(duì)于WM_COMMAND,它直接調(diào)用CWnd::OnCmdMsg派發(fā)反射消息WM_REFLECT_BASE+WM_COMMAND;對(duì)于WM_NOTIFY,它直接調(diào)用CWnd::OnCmdMsg派發(fā)反射消息WM_REFLECT_BASE+WM_NOFITY;對(duì)于其他消息,則直接調(diào)用CWnd::OnWndMsg(即CmdTarge::OnWndMsg)派發(fā)相應(yīng)的反射消息,例如WM_REFLECT_BASE+WM_HSCROLL。
注意:ReflectChildNotify直接調(diào)用了CWnd的OnCmdMsg或OnWndMsg,這樣反射消息被直接派發(fā)給控制子窗口,省卻了消息發(fā)送的過(guò)程。
接著,控制子窗口如果處理了當(dāng)前的反射消息,則返回反射消息被成員處理的信息。
(3)一個(gè)示例
如果要?jiǎng)?chuàng)建一個(gè)編輯框控制,要求它背景使用黃色,其他特性不變,則可以從CEdit派生一個(gè)類(lèi)CYellowEdit,處理通知消息WM_CTLCOLOR的反射消息。CYellowEdit有三個(gè)屬性,定義如下:
CYellowEdit::CYellowEdit()
{
m_clrText = RGB( 0, 0, 0 );
m_clrBkgnd = RGB( 255, 255, 0 );
m_brBkgnd.CreateSolidBrush( m_clrBkgnd );
}
使用ClassWizard添加反射消息處理函數(shù):
函數(shù)原型:
afx_msg void HScroll();
消息映射宏:
ON_WM_CTLCOLOR_REFLECT()
函數(shù)的框架
HBRUSH CYellowEdit::CtlColor(CDC* pDC, UINT nCtlColor)
{
// TODO:添加代碼改變?cè)O(shè)備描述表的屬性
// TODO: 如果不再調(diào)用父窗口的處理,則返回一個(gè)非空的刷子句柄
return NULL;
}
添加一些處理到函數(shù)CtlColor中,如下:
pDC->SetTextColor( m_clrText );//設(shè)置文本顏色
pDC->SetBkColor( m_clrBkgnd );//設(shè)置背景顏色
return m_brBkgnd; //返回背景刷
這樣,如果某個(gè)地方需要使用黃色背景的編輯框,則可以使用CYellowEdit控制。
-
對(duì)更新命令的接收和處理
用戶(hù)接口對(duì)象如菜單、工具條有多種狀態(tài),例如:禁止,可用,選中,未選中,等等。這些狀態(tài)隨著運(yùn)行條件的變化,由程序來(lái)進(jìn)行更新。雖然程序員可以自己來(lái)完成更新,但是MFC框架為自動(dòng)更新用戶(hù)接口對(duì)象提供了一個(gè)方便的接口,使用它對(duì)程序員來(lái)說(shuō)可能是一個(gè)好的選擇。
-
實(shí)現(xiàn)方法
每一個(gè)用戶(hù)接口對(duì)象,如菜單、工具條、控制窗口的子窗口,都由唯一的ID號(hào)標(biāo)識(shí),用戶(hù)和它們交互時(shí),產(chǎn)生相應(yīng)ID號(hào)的命令消息。在MFC里,一個(gè)用戶(hù)接口對(duì)象還可以響應(yīng)CN_UPDATE_COMMAND_UI通知消息。因此,對(duì)每個(gè)標(biāo)號(hào)ID的接口對(duì)象,可以有兩個(gè)處理函數(shù):一個(gè)消息處理函數(shù)用來(lái)處理該對(duì)象產(chǎn)生的命令消息ID,另一個(gè)狀態(tài)更新函數(shù)用來(lái)處理給該對(duì)象的CN_UPDATE_COMMAND_UID的通知消息。
使用ClassWizard可把狀態(tài)更新函數(shù)加入到某個(gè)消息處理類(lèi),其結(jié)果是:
在類(lèi)的定義中聲明一個(gè)狀態(tài)函數(shù);
在消息映射中使用ON_UPDATE_COMMAND_UI宏添加一個(gè)映射條目;
在類(lèi)的實(shí)現(xiàn)文件中實(shí)現(xiàn)狀態(tài)更新函數(shù)的定義。
ON_UPDATE_COMMAND_UI給指定ID的用戶(hù)對(duì)象指定狀態(tài)更新函數(shù),例如:
ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
映射標(biāo)識(shí)號(hào)ID為ID_EDIT_COPY菜單的通知消息CN_UPDATE_COMMAND_UI到函數(shù)OnUpdateEditCopy。用于給EDIT(編輯菜單)的菜單項(xiàng)ID_EDIT_COPY(復(fù)制)添加一個(gè)狀態(tài)處理函數(shù)OnUpdateEditCopy,通過(guò)處理通知消息CN_UPDATE_COMMAND_UI實(shí)現(xiàn)該菜單項(xiàng)的狀態(tài)更新。
狀態(tài)處理函數(shù)的原型如下:
afxmsg void ClassName::OnUpdateEditPaste(CCmdUI* pCmdUI)
CCmdUI對(duì)象由MFC自動(dòng)地構(gòu)造。在完善函數(shù)的實(shí)現(xiàn)時(shí),使用pCmdUI對(duì)象和CmdUI的成員函數(shù)實(shí)現(xiàn)菜單項(xiàng)ID_EDIT_COPY的狀態(tài)更新,讓它變灰或者變亮,也就是禁止或者允許用戶(hù)使用該菜單項(xiàng)。
-
狀態(tài)更新命令消息
要討論MFC的狀態(tài)更新處理,先得了解一條特殊的消息。MFC的消息映射機(jī)制除了處理各種Windows消息、控制通知消息、命令消息、反射消息外,還處理一種特別的“通知命令消息”,并通過(guò)它來(lái)更新菜單、工具欄(包括對(duì)話(huà)框工具欄)等命令目標(biāo)的狀態(tài)。
這種“通知命令消息”是MFC內(nèi)部定義的,消息ID是WM_COMMAND,通知代碼是CN_UPDATE_COMMAND_UI(0XFFFFFFFF)。
它不是一個(gè)真正意義上的通知消息,因?yàn)闆](méi)有控制窗口產(chǎn)生這樣的通知消息,而是MFC自己主動(dòng)產(chǎn)生,用于送給工具條窗口或者主邊框窗口,通知它們更新用戶(hù)接口對(duì)象的狀態(tài)。
它和標(biāo)準(zhǔn)WM_COMMAND命令消息也不相同,因?yàn)樗刑囟ǖ耐ㄖa,而命令消息通知代碼是0。
但是,從消息的處理角度,可以把它看作是一條通知消息。如果是工具條窗口接收該消息,則在發(fā)送機(jī)制上它和WM_COMMAND控制通知消息是相同的,相當(dāng)于讓工具條窗口處理一條通知消息。如果是邊框窗口接收該消息,則在消息的發(fā)送機(jī)制上它和WM_COMMAND命令消息是相同的,可以讓任意命令目標(biāo)處理該消息,也就是說(shuō)邊框窗口可以把該條通知消息發(fā)送給任意命令目標(biāo)處理。
從程序員的角度,可以把它看作一條“狀態(tài)更新命令消息”,像處理命令消息那樣處理該消息。每條命令消息都可以對(duì)應(yīng)有一條“狀態(tài)更新命令消息”。ClassWizard也支持讓任意消息目標(biāo)處理“狀態(tài)更新命令消息”(包括非窗口命令目標(biāo)),實(shí)現(xiàn)用戶(hù)接口狀態(tài)的更新。
在這條消息發(fā)送時(shí),通過(guò)OnCmdMsg的第三個(gè)參數(shù)pExtra傳遞一些信息,表示要更新的用戶(hù)接口對(duì)象。pExtra指向一個(gè)CCmdUI對(duì)象。這些信息將傳遞給狀態(tài)更新命令消息的處理函數(shù)。
下面討論用于更新用戶(hù)接口對(duì)象狀態(tài)的類(lèi)CCmdUI。
-
類(lèi)CCmdUI
CCmdUI不是從CObject派生,沒(méi)有基類(lèi)。
- 成員變量
m_nID 用戶(hù)接口對(duì)象的ID
m_nIndex 用戶(hù)接口對(duì)象的index
m_pMenu 指向CCmdUI對(duì)象表示的菜單
m_pSubMenu 指向CCmdUI對(duì)象表示的子菜單
m_pOther 指向其他發(fā)送通知消息的窗口對(duì)象
m_pParentMenu 指向CCmdUI對(duì)象表示的子菜單
- 成員函數(shù)
Enable(BOOL bOn = TRUE ) 禁止用戶(hù)接口對(duì)象或者使之可用
SetCheck( int nCheck = 1) 標(biāo)記用戶(hù)接口對(duì)象選中或未選中
SetRadio(BOOL bOn = TRUE)
SetText(LPCTSTR lpszText)
ContinueRouting()
還有一個(gè)MFC內(nèi)部使用的成員函數(shù):
DoUpdate(CCmdTarget* pTarget, BOOL bDisableIfNoHndler)
其中,參數(shù)1指向處理接收更新通知的命令目標(biāo),一般是邊框窗口;參數(shù)2指示如果沒(méi)有提供處理函數(shù)(例如某個(gè)菜單沒(méi)有對(duì)應(yīng)的命令處理函數(shù)),是否禁止用戶(hù)對(duì)象。
DoUpdate作以下事情:
首先,發(fā)送狀態(tài)更新命令消息給參數(shù)1表示的命令目標(biāo):調(diào)用pTarget->OnCmdMsg(m_nID, CN_UPDATE_COMMAND_UI, this, NULL)發(fā)送m_nID對(duì)象的通知消息CN_UPDATE_COMMAND_UI。OnCmdMsg的參數(shù)3取值this,包含了當(dāng)前要更新的用戶(hù)接口對(duì)象的信息。
然后,如果參數(shù)2為T(mén)RUE,調(diào)用pTarget->OnCmdMsg(m_nID, CN_COMMAND, this, &info)測(cè)試命令消息m_nID是否被處理。這時(shí),OnCmdMsg的第四個(gè)參數(shù)非空,表示僅僅是測(cè)試,不是真的要派發(fā)消息。如果沒(méi)有提供命令消息m_nID的處理函數(shù),則禁止用戶(hù)對(duì)象m_nID,否則使之可用。
從上面的討論可以知道:通過(guò)其結(jié)構(gòu),一個(gè)CCmdUI對(duì)象標(biāo)識(shí)它表示了哪一個(gè)用戶(hù)接口對(duì)象,如果是菜單接口對(duì)象,pMenu表示了要更新的菜單對(duì)象;如果是工具條,pOther表示了要更新的工具條窗口對(duì)象,nID表示了工具條按鈕ID。
所以,由參數(shù)上狀態(tài)更新消息的消息處理函數(shù)就知道要更新什么接口對(duì)象的狀態(tài)。例如,第1節(jié)的函數(shù)OnUpdateEditPaste,函數(shù)參數(shù)pCmdUI表示一個(gè)菜單對(duì)象,需要更新該菜單對(duì)象的狀態(tài)。
通過(guò)其成員函數(shù),一個(gè)CCmdUI可以更新、改變用戶(hù)接口對(duì)象的狀態(tài)。例如,CCmdUI可以管理菜單和對(duì)話(huà)框控制的狀態(tài),調(diào)用Enable禁止或者允許菜單或者控制子窗口,等等。
所以,函數(shù)OnUpdateEditPaste可以直接調(diào)用參數(shù)的成員函數(shù)(如pCmdUI->Enable)實(shí)現(xiàn)菜單對(duì)象的狀態(tài)更新。
由于接口對(duì)象的多樣性,其他接口對(duì)象將從CCmdUI派生出管理自己的類(lèi)來(lái),覆蓋基類(lèi)的有關(guān)成員函數(shù)如Enable等,提供對(duì)自身狀態(tài)更新的功能。例如管理狀態(tài)條和工具欄更新的CStatusCmdUI類(lèi)和CToolCmdUI類(lèi)。
-
自動(dòng)更新用戶(hù)接口對(duì)象狀態(tài)的機(jī)制
MFC提供了分別用于更新菜單和工具條的兩種途徑。
- 更新菜單狀態(tài)
當(dāng)用戶(hù)對(duì)菜單如File單擊鼠標(biāo)時(shí),就產(chǎn)生一條WM_INITMENUPOPUP消息,邊框窗口在菜單下拉之前響應(yīng)該消息,從而更新該菜單所有項(xiàng)的狀態(tài)。
在應(yīng)用程序開(kāi)始運(yùn)行時(shí),邊框也會(huì)收到WM_INITMENUPOPUP消息。
- 更新工具條等狀態(tài)
當(dāng)應(yīng)用程序進(jìn)入空閑處理狀態(tài)時(shí),將發(fā)送WM_IDLEUPDATECMDUI消息,導(dǎo)致所有的工具條用戶(hù)對(duì)象的狀態(tài)處理函數(shù)被調(diào)用,從而改變其狀態(tài)。WM_IDLEUPDATECMDUI是MFC自己定義和使用的消息。
在窗口初始化時(shí),工具條也會(huì)收到WM_IDLEUPDATECMDUI消息。
- 菜單狀態(tài)更新的實(shí)現(xiàn)
MFC讓邊框窗口來(lái)響應(yīng)WM_INITMENUPOPUP消息,消息處理函數(shù)是OnInitMenuPopup,其原型如下:
afx_msg void CFrameWnd::OnInitMenuPopup( CMenu* pPopupMenu,
UINT nIndex, BOOL bSysMenu );
第一個(gè)參數(shù)指向一個(gè)CMenu對(duì)象,是當(dāng)前按擊的菜單;第二個(gè)參數(shù)是菜單索引;第三個(gè)參數(shù)表示子菜單是否是系統(tǒng)控制菜單。
函數(shù)的處理:
如果是系統(tǒng)控制菜單,不作處理;否則,創(chuàng)建CCmdUI對(duì)象state,給它的各個(gè)成員如m_pMenu,m_pParentMenu,m_pOther等賦值。
對(duì)該菜單的各個(gè)菜單項(xiàng),調(diào)函數(shù)state.DoUpdate,用CCmdUI的DoUpdate來(lái)更新?tīng)顟B(tài)。DoUpdate的第一個(gè)參數(shù)是this,表示命令目標(biāo)是邊框窗口;在CFrameWnd的成員變量m_bAutoMenuEnable為T(mén)RUE時(shí)(表示如果菜單m_nID沒(méi)有對(duì)應(yīng)的消息處理函數(shù)或狀態(tài)更新函數(shù),則禁止它),把DoUpdate的第二個(gè)參數(shù)bDisableIfNoHndler置為T(mén)RUE。
順便指出,m_bAutoMenuEnable缺省時(shí)為T(mén)RUE,所以,應(yīng)用程序啟動(dòng)時(shí)菜單經(jīng)過(guò)初始化處理,沒(méi)有提供消息處理函數(shù)或狀態(tài)更新函數(shù)的菜單項(xiàng)被禁止。
- 工具條等狀態(tài)更新的實(shí)現(xiàn)
圖4-5表示了消息空閑時(shí)MFC更新用戶(hù)對(duì)象狀態(tài)的流程:
MFC提供的缺省空閑處理向頂層窗口(框架窗口)的所有子窗口發(fā)送消息WM_IDLEUPDATECMDUI;MFC的控制窗口(工具條、狀態(tài)欄等)實(shí)現(xiàn)了對(duì)該消息的處理,導(dǎo)致用戶(hù)對(duì)象狀態(tài)處理函數(shù)的調(diào)用。
雖然兩種途徑調(diào)用了同一狀態(tài)處理函數(shù),但是傳遞的 CCmdUI參數(shù)從內(nèi)部構(gòu)成上是不一樣的:第一種傳遞的CCmdUI對(duì)象表示了一菜單對(duì)象,(pMenu域被賦值);第二種傳遞了一個(gè)窗口對(duì)象(pOther域被賦值)。同樣的狀態(tài)改變動(dòng)作,如禁止、允許狀態(tài)的改變,前者調(diào)用了CMenu的成員函數(shù)EnableMenuItem,后者使用了CWnd的成員函數(shù)EnabelWindow。但是,這些不同由CCmdUI對(duì)象內(nèi)部區(qū)分、處理,對(duì)用戶(hù)是透明的:不論菜單還是對(duì)應(yīng)的工具條,用戶(hù)都用同一個(gè)狀態(tài)處理函數(shù)使用同樣的形式來(lái)處理。
這一節(jié)分析了用戶(hù)界面更新的原理和機(jī)制。在后面第13章討論工具條和狀態(tài)欄時(shí),將詳細(xì)的分析這種機(jī)制的具體實(shí)現(xiàn)。
-
消息的預(yù)處理
到現(xiàn)在為止,詳細(xì)的討論了MFC的消息映射機(jī)制。但是,為了提高效率和簡(jiǎn)化處理,MFC提供了一種消息預(yù)處理機(jī)制,如果一條消息在預(yù)處理時(shí)被過(guò)濾掉了(被處理),則不會(huì)被派發(fā)給目的窗口的窗口過(guò)程,更不會(huì)進(jìn)入消息循環(huán)了。
顯然,能夠進(jìn)行預(yù)處理的消息只可能是隊(duì)列消息,而且必須在消息派發(fā)之前進(jìn)行預(yù)處理。因此,MFC在實(shí)現(xiàn)消息循環(huán)時(shí),對(duì)于得到的每一條消息,首先送給目的窗口、其父窗口、其祖父窗口乃至最頂層父窗口,依次進(jìn)行預(yù)處理,如果沒(méi)有被處理,則進(jìn)行消息轉(zhuǎn)換和消息派發(fā),如果某個(gè)窗口實(shí)現(xiàn)了預(yù)處理,則終止。有關(guān)實(shí)現(xiàn)見(jiàn)后面關(guān)于CWinThread線(xiàn)程類(lèi)的章節(jié),CWinThread的Run函數(shù)和PreTranslateMessage函數(shù)以及CWnd的函數(shù)WalkPreTranslateTree實(shí)現(xiàn)了上述要求和功能。這里要討論的是MFC窗口類(lèi)如何進(jìn)行消息預(yù)處理。
CWnd提供了虛擬函數(shù)PreTranslateMessage來(lái)進(jìn)行消息預(yù)處理。CWnd的派生類(lèi)可以覆蓋該函數(shù),實(shí)現(xiàn)自己的預(yù)處理。下面,討論幾個(gè)典型的預(yù)處理。
首先,是CWnd的預(yù)處理:
預(yù)處理函數(shù)的原型為:
BOOL CWnd::PreTranslateMessage(MSG* pMsg)
CWnd類(lèi)主要是處理和過(guò)濾Tooltips消息。關(guān)于該函數(shù)的實(shí)現(xiàn)和Tooltips消息,見(jiàn)后面第13章關(guān)于工具欄的討論。
然后,是CFrameWnd的預(yù)處理:
CFrameWnd除了調(diào)用基類(lèi)CWnd的實(shí)現(xiàn)過(guò)濾Tooltips消息之外,還要判斷當(dāng)前消息是否是鍵盤(pán)快捷鍵被按下,如果是,則調(diào)用函數(shù)::TranslateAccelerator(m_hWnd, hAccel, pMsg)處理快捷鍵。
接著,是CMDIChildWnd的預(yù)處理:
CMDIChildWnd的預(yù)處理過(guò)程和CFrameWnd的一樣,但是不能依靠基類(lèi)CFrameWnd的實(shí)現(xiàn),必須覆蓋它。因?yàn)镸DI子窗口沒(méi)有菜單,所以它必須在MDI邊框窗口的上下文中來(lái)處理快捷鍵,它調(diào)用了函數(shù)::TranslateAccelerator(GetMDIFrame()->m_hWnd, hAccel, pMsg)。
討論了MDI子窗口的預(yù)處理后,還要討論MDI邊框窗口:
CMDIFrameWnd的實(shí)現(xiàn)除了CFrameWnd的實(shí)現(xiàn)的功能外,它還要處理MDI快捷鍵(標(biāo)準(zhǔn)MDI界面統(tǒng)一使用的系統(tǒng)快捷鍵)。
在后面,還會(huì)討論CDialog、CFormView、CToolBar等的消息預(yù)處理及其實(shí)現(xiàn)。
至于CWnd::WalkPreTranslateTree函數(shù),它從接受消息的窗口開(kāi)始,逐級(jí)向父窗回溯,逐一對(duì)各層窗口調(diào)用PreTranslateMessage函數(shù),直到消息被處理或者到最頂層窗口為止。
-
MFC消息映射的回顧
從處理命令消息的過(guò)程可以看出,Windows消息和控制消息的處理要比命令消息的處理簡(jiǎn)單,因?yàn)椴檎蚁⑻幚砗瘮?shù)時(shí),后者只要搜索當(dāng)前窗口對(duì)象(this所指)的類(lèi)或其基類(lèi)的消息映射入口表。但是,命令消息就要復(fù)雜多了,它沿一定的順序鏈查找鏈上的各個(gè)命令目標(biāo),每一個(gè)被查找的命令目標(biāo)都要搜索它的類(lèi)或基類(lèi)的消息映射入口表。
MFC通過(guò)消息映射的手段,以一種類(lèi)似C++虛擬函數(shù)的概念向程序員提供了一種處理消息的方式。但是,若使用C++虛擬函數(shù)實(shí)現(xiàn)眾多的消息,將導(dǎo)致虛擬函數(shù)表極其龐大;而使用消息映射,則僅僅感興趣的消息才加入映射表,這樣就要節(jié)省資源、提高效率。這套消息映射機(jī)制的基礎(chǔ)包括以下幾個(gè)方面:
- 消息映射入口表的實(shí)現(xiàn):采用了C++靜態(tài)成員和虛擬函數(shù)的方法來(lái)表示和得到一個(gè)消息映射類(lèi)(CCmdTarget或派生類(lèi))的映射表。
- 消息查找的實(shí)現(xiàn):從低層到高層搜索消息映射入口表,直至根類(lèi)CCmdTarget。
- 消息發(fā)送的實(shí)現(xiàn):主要以幾個(gè)虛擬函數(shù)為基礎(chǔ)來(lái)實(shí)現(xiàn)標(biāo)準(zhǔn)MFC消息發(fā)送路徑:OnComamnd、OnNotify、OnWndMsg和OnCmdMsg。、
OnWndMsg是CWnd類(lèi)或其派生類(lèi)的成員函數(shù),由窗口過(guò)程調(diào)用。它處理標(biāo)準(zhǔn)的Windows消息。
OnCommand是CWnd類(lèi)或其派生類(lèi)的成員函數(shù),由OnWndMsg調(diào)用來(lái)處理WM_COMMAND消息,實(shí)現(xiàn)命令消息或者控制通知消息的發(fā)送。如果派生類(lèi)覆蓋該函數(shù),則必須調(diào)用基類(lèi)的實(shí)現(xiàn),否則將不能自動(dòng)的處理命令消息映射,而且必須使用該函數(shù)接受的參數(shù)(不是程序員給定值)調(diào)用基類(lèi)的OnCommand。
OnNotify是CWnd類(lèi)或其派生類(lèi)的成員函數(shù),由OnWndMsg調(diào)用來(lái)處理WM_NOTIFY消息,實(shí)現(xiàn)控制通知消息的發(fā)送。
OnCmdMsg是CCmdTarget類(lèi)或其派生類(lèi)的成員函數(shù)。被OnCommand調(diào)用,用來(lái)實(shí)現(xiàn)命令消息發(fā)送和派發(fā)命令消息到命令消息處理函數(shù)。
自動(dòng)更新用戶(hù)對(duì)象狀態(tài)是通過(guò)MFC的命令消息發(fā)送機(jī)制實(shí)現(xiàn)的。
控制消息可以反射給控制窗口處理。
隊(duì)列消息在發(fā)送給窗口過(guò)程之前可以進(jìn)行消息預(yù)處理,如果消息被MFC窗口對(duì)象預(yù)處理了,則不會(huì)進(jìn)入消息發(fā)送過(guò)程。