📄 mfc教程_ 消息映射的实现.htm
字号:
align=justify>CCmdTarget类是MFC处理命令消息的基础、核心。MFC为该类设计了许多成员函数和一些成员数据,基本上是为了解决消息映射问题的,而且,很大一部分是针对OLE设计的。在OLE应用中,CCmdTarget是MFC处理模块状态的重要环节,它起到了传递模块状态的作用:其构造函数获取当前模块状态,并保存在成员变量m_pModuleState里头。关于模块状态,在后面章节讲述。</P>
<P align=justify>CCmdTarget有两个与消息映射有密切关系的成员函数:DispatchCmdMsg和OnCmdMsg。</P>
<OL>
<P align=justify>
<LI><A name=_Toc445889015></A><A name=_Toc445782418></A>静态成员函数DispatchCmdMsg
<P></P>
<P
align=justify>CCmdTarget的静态成员函数DispatchCmdMsg,用来分发Windows消息。此函数是MFC内部使用的,其原型如下:</P>
<P align=justify>static BOOL DispatchCmdMsg(</P>
<P align=justify>CCmdTarget* pTarget, </P>
<P align=justify>UINT nID,</P>
<P align=justify>int nCode,</P>
<P align=justify>AFX_PMSG pfn,</P>
<P align=justify>void* pExtra,</P>
<P align=justify>UINT nSig,</P>
<P align=justify>AFX_CMDHANDLERINFO* pHandlerInfo)</P>
<P align=justify></P>
<P align=justify>关于此函数将在4.4.3.2章节命令消息的处理中作更详细的描述。</P>
<P align=justify></P>
<LI><A name=_Toc445889016></A><A name=_Toc445782419></A>虚拟函数OnCmdMsg
<P></P></LI></OL>
<P align=justify>CCmdTarget的虚拟函数OnCmdMsg,用来传递和发送消息、更新用户界面对象的状态,其原型如下:</P>
<P align=justify>OnCmdMsg(</P>
<DIR>
<P align=justify>UINT nID,</P>
<P align=justify>int nCode,</P>
<P align=justify>void* pExtra,</P>
<P align=justify>AFX_CMDHANDLERINFO* pHandlerInfo)</P></DIR>
<P
align=justify>框架的命令消息传递机制主要是通过该函数来实现的。其参数描述参见4.3.3.2章节DispacthCMdMessage的参数描述。</P>
<P align=justify>在本书中,命令目标指希望或者可能处理消息的对象;命令目标类指命令目标的类。</P>
<P
align=justify>CCmdTarget对OnCmdMsg的默认实现:在当前命令目标(this所指)的类和基类的消息映射数组里搜索指定命令消息的消息处理函数(标准Windows消息不会送到这里处理)。</P>
<P
align=justify>这里使用虚拟函数GetMessageMap得到命令目标类的消息映射入口数组_messageEntries,然后在数组里匹配指定的消息映射条目。匹配标准:命令消息ID相同,控制通知代码相同。因为GetMessageMap是虚拟函数,所以可以确认当前命令目标的确切类。</P>
<P align=justify>如果找到了一个匹配的消息映射条目,则使用DispachCmdMsg调用这个处理函数;</P>
<P
align=justify>如果没有找到,则使用_GetBaseMessageMap得到基类的消息映射数组,查找,直到找到或搜寻了所有的基类(到CCmdTarget)为止;</P>
<P align=justify>如果最后没有找到,则返回FASLE。</P>
<P align=justify></P>
<P
align=justify>每个从CCmdTarget派生的命令目标类都可以覆盖OnCmdMsg,利用它来确定是否可以处理某条命令,如果不能,就通过调用下一命令目标的OnCmdMsg,把该命令送给下一个命令目标处理。通常,派生类覆盖OnCmdMsg时,要调用基类的被覆盖的OnCmdMsg。</P>
<P
align=justify>在MFC框架中,一些MFC命令目标类覆盖了OnCmdMsg,如框架窗口类覆盖了该函数,实现了MFC的标准命令消息发送路径。具体实现见后续章节。</P>
<P
align=justify>必要的话,应用程序也可以覆盖OnCmdMsg,改变一个或多个类中的发送规定,实现与标准框架发送规定不同的发送路径。例如,在以下情况可以作这样的处理:在要打断发送顺序的类中把命令传给一个非MFC默认对象;在新的非默认对象中或在可能要传出命令的命令目标中。</P>
<P
align=justify>本节对CCmdTarget的两个成员函数作一些讨论,是为了对MFC的消息处理有一个大致印象。后面4.4.3.2节和4.4.3.3节将作进一步的讨论。</P>
<OL>
<OL>
<P align=justify>
<LI><A name=_Toc445889017></A><A name=_Toc445782420></A><A
name=_Toc452640906></A><A name=_Toc457298974></A><B>MFC窗口过程</B>
<P></P>
<P
align=justify>前文曾经提到,所有的消息都送给窗口过程处理,MFC的所有窗口都使用同一窗口过程,消息或者直接由窗口过程调用相应的消息处理函数处理,或者按MFC命令消息派发路径送给指定的命令目标处理。</P>
<P
align=justify>那么,MFC的窗口过程是什么?怎么处理标准Windows消息?怎么实现命令消息的派发?这些都将是下文要回答的问题。</P>
<OL>
<P align=justify>
<LI><A name=_Toc445889018></A><A name=_Toc445782421></A><A
name=_Toc452640907></A><A name=_Toc457298975></A><B>MFC窗口过程的指定</B>
<P></P>
<P
align=justify>从前面的讨论可知,每一个“窗口类”都有自己的窗口过程。正常情况下使用该“窗口类”创建的窗口都使用它的窗口过程。</P>
<P
align=justify>MFC的窗口对象在创建HWND窗口时,也使用了已经注册的“窗口类”,这些“窗口类”或者使用应用程序提供的窗口过程,或者使用Windows提供的窗口过程(例如Windows控制窗口、对话框等)。那么,为什么说MFC创建的所有HWND窗口使用同一个窗口过程呢?</P>
<P
align=justify>在MFC中,的确所有的窗口都使用同一个窗口过程:AfxWndProc或AfxWndProcBase(如果定义了_AFXDLL)。它们的原型如下:</P>
<P align=justify>LRESULT CALLBACK</P>
<P align=justify>AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM
lParam)</P>
<P align=justify></P>
<P align=justify>LRESULT CALLBACK</P>
<P align=justify>AfxWndProcBase(HWND hWnd, UINT nMsg, WPARAM wParam,
LPARAM lParam)</P>
<P align=justify>这两个函数的原型都如4.1.1节描述的窗口过程一样。</P>
<P align=justify>如果动态链接到MFC
DLL(定义了_AFXDLL),则AfxWndProcBase被用作窗口过程,否则AfxWndProc被用作窗口过程。AfxWndProcBase首先使用宏AFX_MANAGE_STATE设置正确的模块状态,然后调用AfxWndProc。</P>
<P align=justify></P>
<P align=justify>下面,假设不使用MFC DLL,讨论MFC如何使用AfxWndProc取代各个窗口的原窗口过程。</P>
<P
align=justify>窗口过程的取代发生在窗口创建的过程时,使用了子类化(Subclass)的方法。所以,从窗口的创建过程来考察取代过程。从前面可以知道,窗口创建最终是通过调用CWnd::CreateEx函数完成的,分析该函数的流程,如图4-1所示。</P>
<P align=justify><IMG height=250 src="MFC教程_ 消息映射的实现.files/image113.gif"
width=325></P>
<P
align=justify>图4-1中的CREATESTRUCT结构类型的变量cs包含了传递给窗口过程的初始化参数。CREATESTRUCT结构描述了创建窗口所需要的信息,定义如下:</P>
<P align=justify>typedef struct tagCREATESTRUCT {</P>
<P align=justify>LPVOID lpCreateParams; //用来创建窗口的数据</P>
<P align=justify>HANDLE hInstance; //创建窗口的实例</P>
<P align=justify>HMENU hMenu; //窗口菜单</P>
<P align=justify>HWND hwndParent; //父窗口</P>
<P align=justify>int cy; //高度</P>
<P align=justify>int cx; //宽度</P>
<P align=justify>int y; //原点Y坐标</P>
<P align=justify>int x;//原点X坐标</P>
<P align=justify>LONG style; //窗口风格</P>
<P align=justify>LPCSTR lpszName; //窗口名</P>
<P align=justify>LPCSTR lpszClass; //窗口类</P>
<P align=justify>DWORD dwExStyle; //窗口扩展风格</P>
<P align=justify>} CREATESTRUCT;</P>
<P
align=justify>cs表示的创建参数可以在创建窗口之前被程序员修改,程序员可以覆盖当前窗口类的虚拟成员函数PreCreateWindow,通过该函数来修改cs的style域,改变窗口风格。这里cs的主要作用是保存创建窗口的各种信息,::CreateWindowEx函数使用cs的各个域作为参数来创建窗口,关于该函数见2.2.2节。</P>
<P
align=justify>在创建窗口之前,创建了一个WH_CBT类型的钩子(Hook)。这样,创建窗口时所有的消息都会被钩子过程函数_AfxCbtFilterHook截获。</P>
<P
align=justify>AfxCbtFilterHook函数首先检查是不是希望处理的Hook──HCBT_CREATEWND。如果是,则先把MFC窗口对象(该对象必须已经创建了)和刚刚创建的Windows窗口对象捆绑在一起,建立它们之间的映射(见后面模块-线程状态);然后,调用::SetWindowLong设置窗口过程为AfxWndProc,并保存原窗口过程在窗口类成员变量m_pfnSuper中,这样形成一个窗口过程链。需要的时候,原窗口过程地址可以通过窗口类成员函数GetSuperWndProcAddr得到。</P>
<P
align=justify>这样,AfxWndProc就成为CWnd或其派生类的窗口过程。不论队列消息,还是非队列消息,都送到AfxWndProc窗口过程来处理(如果使用MFC
DLL,则AfxWndProcBase被调用,然后是AfxWndProc)。经过消息分发之后没有被处理的消息,将送给原窗口过程处理。</P>
<P
align=justify>最后,有一点可能需要解释:为什么不直接指定窗口过程为AfxWndProc,而要这么大费周折呢?这是因为原窗口过程(“窗口类”指定的窗口过程)常常是必要的,是不可缺少的。</P>
<P
align=justify>接下来,讨论AfxWndProc窗口过程如何使用消息映射数据实现消息映射。Windows消息和命令消息的处理不一样,前者没有消息分发的过程。</P>
<P align=justify></P>
<LI><A name=_Toc445889019></A><A name=_Toc445782422></A><A
name=_Toc452640908></A><A name=_Toc457298976></A><B>对Windows消息的接收和处理</B>
<P></P>
<P
align=justify>Windows消息送给AfxWndProc窗口过程之后,AfxWndProc得到HWND窗口对应的MFC窗口对象,然后,搜索该MFC窗口对象和其基类的消息映射数组,判定它们是否处理当前消息,如果是则调用对应的消息处理函数,否则,进行缺省处理。</P>
<P
align=justify>下面,以一个应用程序的视窗口创建时,对WM_CREATE消息的处理为例,详细地讨论Windows消息的分发过程。</P>
<P
align=justify>用第一章的例子,类CTview要处理WM_CREATE消息,使用ClassWizard加入消息处理函数CTview::OnCreate。下面,看这个函数怎么被调用:</P>
<P
align=justify>视窗口最终调用::CreateEx函数来创建。由Windows系统发送WM_CREATE消息给视的窗口过程AfxWndProc,参数1是创建的视窗口的句柄,参数2是消息ID(WM_CREATE),参数3、4是消息参数。图4-2描述了其余的处理过程。图中函数的类属限制并非源码中所具有的,而是根据处理过程得出的判断。例如,“CWnd::WindowProc”表示CWnd类的虚拟函数WindowProc被调用,并不一定当前对象是CWnd类的实例,事实上,它是CWnd派生类CTview类的实例;而“CTview::OnCreate”表示CTview的消息处理函数OnCreate被调用。下面描述每一步的详细处理。</P>
<P align=justify><IMG height=239 src="" width=277></P>
<OL>
<P align=justify>
<LI><A name=_Toc445889020></A><A name=_Toc445782423></A><A
name=_Toc457298977></A><B>从窗口过程到消息映射</B>
<P></P></LI></OL></LI></OL></LI></OL></OL>
<P align=justify>首先,分析AfxWndProc窗口过程函数。</P>
<UL>
<P align=justify>
<LI>AfxWndProc的原型如下:
<P></P></LI></UL>
<P align=justify>LRESULT AfxWndProc(HWND hWnd, </P>
<DIR>
<P align=justify>UINT nMsg, WPARAM wParam, LPARAM lParam)</P></DIR>
<P
align=justify>如果收到的消息nMsg不是WM_QUERYAFXWNDPROC(该消息被MFC内部用来确认窗口过程是否使用AfxWndProc),则从hWnd得到对应的MFC
Windows对象(该对象必须已存在,是永久性<Permanent>对象)指针pWnd。pWnd所指的MFC窗口对象将负责完成消息的处理。这里,pWnd所指示的对象是MFC视窗口对象,即CTview对象。</P>
<P align=justify>然后,把pWnd和AfxWndProc接受的四个参数传递给函数AfxCallWndProc执行。</P>
<P align=justify></P>
<UL>
<P align=justify>
<LI>AfxCallWndProc原型如下:
<P></P></LI></UL>
<P align=justify>LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, </P>
<DIR>
<P align=justify>UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0)</P></DIR>
<P
align=justify>MFC使用AfxCallWndProc函数把消息送给CWnd类或其派生类的对象。该函数主要是把消息和消息参数(nMsg、wParam、lParam)传递给MFC窗口对象的成员函数WindowProc(pWnd->WindowProc)作进一步处理。如果是WM_INITDIALOG消息,则在调用WindowProc前后要作一些处理。</P>
<P align=justify></P>
<P align=justify>WindowProc的函数原型如下:</P>
<P align=justify>LRESULT CWnd::WindowProc(UINT message, </P>
<DIR>
<P align=justify>WPARAM wParam, LPARAM lParam)</P></DIR>
<P
align=justify>这是一个虚拟函数,程序员可以在CWnd的派生类中覆盖它,改变MFC分发消息的方式。例如,MFC的CControlBar就覆盖了WindowProc,对某些消息作了自己的特别处理,其他消息处理由基类的WindowProc函数完成。</P>
<P align=justify>但是在当前例子中,当前对象的类CTview没有覆盖该函数,所以CWnd的WindowProc被调用。</P>
<P
align=justify>这个函数把下一步的工作交给OnWndMsg函数来处理。如果OnWndMsg没有处理,则交给DefWindowProc来处理。</P>
<P align=justify>OnWndMsg和DefWindowProc都是CWnd类的虚拟函数。</P>
<P></P>
<UL>
<P align=justify>
<LI>OnWndMsg的原型如下:
<P></P></LI></UL>
<P align=justify>BOOL CWnd::OnWndMsg( UINT message, </P>
<DIR>
<P align=justify>WPARAM wParam, LPARAM lParam,RESULT*pResult );</P></DIR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -