• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            C++博客 聯(lián)系 聚合 管理  

            Blog Stats

            文章分類(lèi)(17)

            收藏夾(2)

            文章檔案(18)

            相冊(cè)

            Blogs

            citywanderer

            http://www.blog.edu.cn/user2/28873/archives/2005/252013.shtml
            http://www.vczx.com/tutorial/mfc/mfc4.php
            消息映射的實(shí)現(xiàn)

            1. Windows消息概述

              Windows應(yīng)用程序的輸入由Windows系統(tǒng)以消息的形式發(fā)送給應(yīng)用程序的窗口。這些窗口通過(guò)窗口過(guò)程來(lái)接收和處理消息,然后把控制返還給Windows。

              1. 消息的分類(lèi)

            1. 隊(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ì)列消息。

            2. 系統(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。

                1. 消息結(jié)構(gòu)和消息處理

            1. 消息的結(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)的位置。

            2. 應(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í)別和處理消息。

            3. 應(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)。

            4. 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)消息處理的。

              1. 消息映射的定義和實(shí)現(xiàn)

                1. 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í)候了。但是,要知道怎么處理消息,首先要知道如何映射消息。

                1. 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)題。

                2. 在聲明與實(shí)現(xiàn)的內(nèi)部

            1. 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的情形。

            2. 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)單,就只有一種定義。

            3. ON_COMMAND宏

            最后,看ON_COMMAND宏的內(nèi)容:

            #define ON_COMMAND(id, memberFxn) \

            {\

            WM_COMMAND,\

            CN_COMMAND,\

            (WORD)id,\

            (WORD)id,\

            AfxSig_vv,\

            (AFX_PMSG)memberFxn\

            };

                  1. 消息映射聲明的解釋

            在清楚了有關(guān)宏的定義之后,現(xiàn)在來(lái)分析它們的作用和功能。

            消息映射聲明的實(shí)質(zhì)是給所在類(lèi)添加幾個(gè)靜態(tài)成員變量和靜態(tài)或虛擬函數(shù),當(dāng)然它們是與消息映射相關(guān)的變量和函數(shù)。

            1. 成員變量

            有兩個(gè)成員變量被添加,第一個(gè)是_messageEntries,第二個(gè)是messageMap。

            • 第一個(gè)成員變量的聲明:

            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í)行它。

            • 第二個(gè)成員變量的聲明

            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ù)組的地址)。

            1. 成員函數(shù)

            • _GetBaseMessageMap()

            用來(lái)得到基類(lèi)消息映射的函數(shù)。

            • GetMessageMap()

            用來(lái)得到自身消息映射的函數(shù)。

                  1. 消息映射實(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)某條消息。

            1. 對(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)的消息映射條目的終止。

            2. 對(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ù)組的地址。

            3. 對(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)、用法和用途。

                1. 消息映射宏的種類(lèi)

            為了簡(jiǎn)化程序員的工作,MFC定義了一系列的消息映射宏和像AfxSig_vv這樣的枚舉變量,以及標(biāo)準(zhǔn)消息處理函數(shù),并且具體地實(shí)現(xiàn)這些函數(shù)。這里主要討論消息映射宏,常用的分為以下幾類(lèi)。

            1. 用于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。

            2. 用于命令消息的宏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è)大略的介紹。

              1. 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。

            1. 靜態(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ì)的描述。

            2. 虛擬函數(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)一步的討論。

              1. 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)題。

                1. 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ò)程。

                2. 對(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ì)處理。

                  1. 從窗口過(guò)程到消息映射

            首先,分析AfxWndProc窗口過(guò)程函數(shù)。

            • AfxWndProc的原型如下:

            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í)行。

            • AfxCallWndProc原型如下:

            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ù)。

            • OnWndMsg的原型如下:

            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í)具體分析。

                  1. 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ù)。

                  2. 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)處理。

                  3. 消息映射機(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é),討論命令消息的接受和處理。

                1. 對(duì)命令消息的接收和處理

                  1. 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ā)送路徑:

            1. 送給活動(dòng)(Active)視處理,調(diào)用活動(dòng)視的OnCmdMsg。由于當(dāng)前對(duì)象是MFC視對(duì)象,所以,OnCmdMsg將搜索CTview及其基類(lèi)的消息映射數(shù)組,試圖得到相應(yīng)的處理函數(shù)。

            2. 如果視對(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ù)。

            3. 如果文檔對(duì)象不處理,則它得到管理文檔的文檔模板對(duì)象,調(diào)用文檔模板的OnCmdMsg。由于當(dāng)前對(duì)象是MFC文檔模板對(duì)象,所以,OnCmdMsg將搜索文檔模板類(lèi)及其基類(lèi)的消息映射數(shù)組,試圖得到相應(yīng)的處理函數(shù)。

            4. 如果文檔模板不處理,則把沒(méi)有處理的信息逐級(jí)返回:文檔模板告訴文檔對(duì)象,文檔對(duì)象告訴視對(duì)象,視對(duì)象告訴邊框窗口對(duì)象。最后,邊框窗口得知,視、文檔、文檔模板都沒(méi)有處理消息。

            5. 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之后其他代碼不用改變。

              這一步是邊框窗口自己嘗試處理消息。

            6. 如果邊框窗口對(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ù)

            7. 如果應(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é)再次具體分析。

                  1. 命令消息的派發(fā)和消息的多次處理

            1. 命令消息的派發(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消息的處理是一樣的。

            2. 消息的多次處理

            如果消息處理函數(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é)。

                  1. 一些消息處理類(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

            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)。

            • CDocument的 OnCmdMsg

            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。

            • CDialog的OnCmdMsg

            BOOL CDialog::OnCmdMsg(UINT nID, int nCode, void* pExtra,

            AFX_CMDHANDLERINFO* pHandlerInfo)

            1. 調(diào)用CWnd::OnCmdMsg,讓對(duì)話(huà)框或其基類(lèi)處理消息。

            2. 如果還沒(méi)有處理,而且是控制消息或系統(tǒng)命令或非命令按鈕,則返回FALSE,不作進(jìn)一步處理。否則,調(diào)用父窗口的OnCmdmsg(GetParent()->OnCmdmsg)把消息送給父窗口處理。

            3. 如果仍然沒(méi)有處理,則調(diào)用當(dāng)前線(xiàn)程的OnCmdMsg(GetThread()->OnCmdMsg)把消息送給線(xiàn)程對(duì)象處理。

            4. 如果最后沒(méi)有處理,返回FALSE。

            • CMDIFrameWnd的OnCmdMsg

            對(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)

            1. 如果有激活的文檔邊框窗口,則調(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)行消息處理。

            2. 如果文檔邊框窗口沒(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)。

                  1. 一些消息處理類(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ù)。

            1. 對(duì)于MDI消息,如果主邊框窗口還不處理的話(huà),交給CMDIFrameWnd的DefWindowProc作缺省處理。

            2. 消息沒(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ā)送消息,急處理終止。

                1. 對(duì)控制通知消息的接收和處理

                  1. 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)處理通知消息。

                  1. 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ò)程如下:

            1. 反射消息給控制子窗口處理。

            2. 如果子窗口不處理反射消息,則交給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. 消息反射

            (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控制。

                1. 對(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è)好的選擇。

                  1. 實(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)。

                  2. 狀態(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。

                  3. 類(lèi)CCmdUI

            CCmdUI不是從CObject派生,沒(méi)有基類(lèi)。

            1. 成員變量

              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ì)象表示的子菜單

            2. 成員函數(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)。

                  1. 自動(dòng)更新用戶(hù)接口對(duì)象狀態(tài)的機(jī)制

            MFC提供了分別用于更新菜單和工具條的兩種途徑。

            1. 更新菜單狀態(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消息。

            2. 更新工具條等狀態(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消息。

            3. 菜單狀態(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)被禁止。

            4. 工具條等狀態(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)。

              1. 消息的預(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ù),直到消息被處理或者到最頂層窗口為止。

              2. 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è)方面:

            1. 消息映射入口表的實(shí)現(xiàn):采用了C++靜態(tài)成員和虛擬函數(shù)的方法來(lái)表示和得到一個(gè)消息映射類(lèi)(CCmdTarget或派生類(lèi))的映射表。

            2. 消息查找的實(shí)現(xiàn):從低層到高層搜索消息映射入口表,直至根類(lèi)CCmdTarget。

            3. 消息發(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ò)程。

            posted on 2006-11-01 10:03 citywanderer 閱讀(10336) 評(píng)論(5)  編輯 收藏 引用 所屬分類(lèi): C++

            評(píng)論

            # re: MFC消息映射的實(shí)現(xiàn)(轉(zhuǎn)) 2012-10-03 13:17 ismallboy
            好文章啊!找了很久終于找到了!但是這個(gè)東西還真是比較復(fù)雜,不是那么容易就能看懂的!  回復(fù)  更多評(píng)論
              

            # re: MFC消息映射的實(shí)現(xiàn)(轉(zhuǎn)) 2016-03-15 10:41 Jhon
            @ismallboy
            我看現(xiàn)在的消息映射宏定義只有一個(gè)版本?。??  回復(fù)  更多評(píng)論
              

            # re: MFC消息映射的實(shí)現(xiàn)(轉(zhuǎn))[未登錄](méi) 2016-03-15 10:55 ismallboy
            @Jhon
            什么意思?  回復(fù)  更多評(píng)論
              

            # re: MFC消息映射的實(shí)現(xiàn)(轉(zhuǎn)) 2016-03-25 13:56 Jhon
            @ismallboy
            沒(méi)有區(qū)分是否定義宏_AFXDLL而定義消息映射宏的兩個(gè)版本?  回復(fù)  更多評(píng)論
              

            # re: MFC消息映射的實(shí)現(xiàn)(轉(zhuǎn)) 2016-03-25 14:06 Jhon
            感覺(jué)有的東西跟現(xiàn)在的對(duì)不上,比如我怎么都找不到CCmdTarget::DispatchCmdMsg這個(gè)成員函數(shù)。  回復(fù)  更多評(píng)論
              

            狠狠色综合网站久久久久久久高清| 91精品国产91久久久久福利| 久久久久久久综合日本| 亚洲?V乱码久久精品蜜桃 | 亚洲国产精品无码久久九九| 亚洲欧美成人久久综合中文网| 狠狠色婷婷久久一区二区| 久久久久人妻一区二区三区vr| 国产精品成人99久久久久91gav | 波多野结衣久久| 97久久香蕉国产线看观看| 欧美伊人久久大香线蕉综合69| 天天爽天天狠久久久综合麻豆| 久久国产三级无码一区二区| 久久亚洲AV成人无码国产 | 久久久久亚洲AV片无码下载蜜桃| 99久久精品免费国产大片| 久久亚洲sm情趣捆绑调教 | 亚洲精品乱码久久久久66| 精品欧美一区二区三区久久久 | 色妞色综合久久夜夜| 东京热TOKYO综合久久精品| 无码国内精品久久综合88| 国产69精品久久久久99尤物| 亚洲国产欧洲综合997久久| 久久久亚洲精品蜜桃臀| 国产成人精品久久亚洲高清不卡 国产成人精品久久亚洲高清不卡 国产成人精品久久亚洲 | 久久免费香蕉视频| 国产精品丝袜久久久久久不卡| 精品免费久久久久久久| 亚洲AV无码久久| 人妻丰满AV无码久久不卡| 久久久精品国产免大香伊| 中文字幕久久亚洲一区| 伊人久久大香线蕉综合网站| 国内精品欧美久久精品| 久久久久久A亚洲欧洲AV冫| 久久精品免费大片国产大片| 精品人妻伦一二三区久久| 国产成人久久777777| 国产精品成人99久久久久91gav |