【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

楼主: abc

MFC 教程

[复制链接]
 楼主| 发表于 2006-12-8 10:08:47 | 显示全部楼层
< align=justify>(3)一个示例</P>
< align=justify>如果要创建一个编辑框控制,要求它背景使用黄色,其他特性不变,则可以从CEdit派生一个类CYellowEdit,处理通知消息WM_CTLCOLOR的反射消息。CYellowEdit有三个属性,定义如下:</P>
< align=justify>CYellowEdit::CYellowEdit()</P>
< align=justify>{</P>
< align=justify>m_clrText = RGB( 0, 0, 0 );</P>
< align=justify>m_clrBkgnd = RGB( 255, 255, 0 );</P>
< align=justify>m_brBkgnd.CreateSolidBrush( m_clrBkgnd );</P>
< align=justify>}</P>
< align=justify>使用ClassWizard添加反射消息处理函数:</P>
< align=justify>函数原型:</P>
<P align=justify>afx_msg void HScroll();</P>
<P align=justify>消息映射宏:</P>
<P align=justify>ON_WM_CTLCOLOR_REFLECT()</P>
<P align=justify>函数的框架</P>
<P align=justify>HBRUSH CYellowEdit::CtlColor(CDC* pDC, UINT nCtlColor) </P>
<P align=justify>{</P>
<P align=justify>// TODO:添加代码改变设备描述表的属性</P>
<P align=justify>// TODO: 如果不再调用父窗口的处理,则返回一个非空的刷子句柄</P>
<P align=justify>return NULL;</P>
<P align=justify>}</P>
<P align=justify>添加一些处理到函数CtlColor中,如下:</P>
<P align=justify>pDC-&gt;SetTextColor( m_clrText );//设置文本颜色</P>
<P align=justify>pDC-&gt;SetBkColor( m_clrBkgnd );//设置背景颜色</P>
<P align=justify>return m_brBkgnd; //返回背景刷</P>
<P align=justify>这样,如果某个地方需要使用黄色背景的编辑框,则可以使用CYellowEdit控制。</P>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>对更新命令的接收和处理</B>
<p>
<P align=justify>用户接口对象如菜单、工具条有多种状态,例如:禁止,可用,选中,未选中,等等。这些状态随着运行条件的变化,由程序来进行更新。虽然程序员可以自己来完成更新,但是MFC框架为自动更新用户接口对象提供了一个方便的接口,使用它对程序员来说可能是一个好的选择。</P>
<OL>
<P align=justify>
<LI><B>实现方法</B>
<p>
<P align=justify>每一个用户接口对象,如菜单、工具条、控制窗口的子窗口,都由唯一的ID号标识,用户和它们交互时,产生相应ID号的命令消息。在MFC里,一个用户接口对象还可以响应CN_UPDATE_COMMAND_UI通知消息。因此,对每个标号ID的接口对象,可以有两个处理函数:一个消息处理函数用来处理该对象产生的命令消息ID,另一个状态更新函数用来处理给该对象的CN_UPDATE_COMMAND_UID的通知消息。</P>
<P align=justify>使用ClassWizard可把状态更新函数加入到某个消息处理类,其结果是:</P>
<P align=justify>在类的定义中声明一个状态函数;</P>
<P align=justify>在消息映射中使用ON_UPDATE_COMMAND_UI宏添加一个映射条目;</P>
<P align=justify>在类的实现文件中实现状态更新函数的定义。</P>
<P align=justify></P>
<P align=justify>ON_UPDATE_COMMAND_UI给指定ID的用户对象指定状态更新函数,例如:</P>
<P align=justify>ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)</P>
<P align=justify>映射标识号ID为ID_EDIT_COPY菜单的通知消息CN_UPDATE_COMMAND_UI到函数OnUpdateEditCopy。用于给EDIT(编辑菜单)的菜单项ID_EDIT_COPY(复制)添加一个状态处理函数OnUpdateEditCopy,通过处理通知消息CN_UPDATE_COMMAND_UI实现该菜单项的状态更新。</P>
<P align=justify>状态处理函数的原型如下:</P>
<P align=justify>afxmsg void ClassName::OnUpdateEditPaste(CCmdUI* pCmdUI)</P>
<P align=justify>CCmdUI对象由MFC自动地构造。在完善函数的实现时,使用pCmdUI对象和CmdUI的成员函数实现菜单项ID_EDIT_COPY的状态更新,让它变灰或者变亮,也就是禁止或者允许用户使用该菜单项。</P>
<P align=justify></P>
<LI><B>状态更新命令消息</B>
<p>
<P align=justify>要讨论MFC的状态更新处理,先得了解一条特殊的消息。MFC的消息映射机制除了处理各种Windows消息、控制通知消息、命令消息、反射消息外,还处理一种特别的“通知命令消息”,并通过它来更新菜单、工具栏(包括对话框工具栏)等命令目标的状态。</P>
<P align=justify>这种“通知命令消息”是MFC内部定义的,消息ID是WM_COMMAND,通知代码是CN_UPDATE_COMMAND_UI(0XFFFFFFFF)。</P>
<P align=justify>它不是一个真正意义上的通知消息,因为没有控制窗口产生这样的通知消息,而是MFC自己主动产生,用于送给工具条窗口或者主边框窗口,通知它们更新用户接口对象的状态。</P>
<P align=justify>它和标准WM_COMMAND命令消息也不相同,因为它有特定的通知代码,而命令消息通知代码是0。</P>
<P align=justify>但是,从消息的处理角度,可以把它看作是一条通知消息。如果是工具条窗口接收该消息,则在发送机制上它和WM_COMMAND控制通知消息是相同的,相当于让工具条窗口处理一条通知消息。如果是边框窗口接收该消息,则在消息的发送机制上它和WM_COMMAND命令消息是相同的,可以让任意命令目标处理该消息,也就是说边框窗口可以把该条通知消息发送给任意命令目标处理。</P>
<P align=justify>从程序员的角度,可以把它看作一条“状态更新命令消息”,像处理命令消息那样处理该消息。每条命令消息都可以对应有一条“状态更新命令消息”。ClassWizard也支持让任意消息目标处理“状态更新命令消息”(包括非窗口命令目标),实现用户接口状态的更新。</P>
<P align=justify>在这条消息发送时,通过OnCmdMsg的第三个参数pExtra传递一些信息,表示要更新的用户接口对象。pExtra指向一个CCmdUI对象。这些信息将传递给状态更新命令消息的处理函数。</P>
<P align=justify>下面讨论用于更新用户接口对象状态的类CCmdUI。</P>
<P align=justify></P>
<LI><B>类CCmdUI</B>
<p></LI></OL></LI></OL></OL></OL>
<P align=justify>CCmdUI不是从CObject派生,没有基类。</P>
<OL>
<P align=justify>
<LI>成员变量
<p>
<P align=justify>m_nID 用户接口对象的ID</P>
<P align=justify>m_nIndex 用户接口对象的index</P>
<P align=justify>m_pMenu 指向CCmdUI对象表示的菜单</P>
<P align=justify>m_pSubMenu 指向CCmdUI对象表示的子菜单</P>
<P align=justify>m_pOther 指向其他发送通知消息的窗口对象</P>
<P align=justify>m_pParentMenu 指向CCmdUI对象表示的子菜单</P>
<P align=justify></P>
<LI>成员函数
<p></LI></OL>
<P align=justify>Enable(BOOL bOn = TRUE ) 禁止用户接口对象或者使之可用</P>
<P align=justify>SetCheck( int nCheck = 1) 标记用户接口对象选中或未选中</P>
<P align=justify>SetRadio(BOOL bOn = TRUE)</P>
<P align=justify>SetText(LPCTSTR lpszText)</P>
<P align=justify>ContinueRouting()</P>
<P align=justify>还有一个MFC内部使用的成员函数:</P>
<P align=justify>DoUpdate(CCmdTarget* pTarget, BOOL bDisableIfNoHndler)</P>
<P align=justify>其中,参数1指向处理接收更新通知的命令目标,一般是边框窗口;参数2指示如果没有提供处理函数(例如某个菜单没有对应的命令处理函数),是否禁止用户对象。</P>
<P align=justify>DoUpdate作以下事情:</P>
<P align=justify>首先,发送状态更新命令消息给参数1表示的命令目标:调用pTarget-&gt;OnCmdMsg(m_nID, CN_UPDATE_COMMAND_UI, this, NULL)发送m_nID对象的通知消息CN_UPDATE_COMMAND_UI。OnCmdMsg的参数3取值this,包含了当前要更新的用户接口对象的信息。</P>
<P align=justify>然后,如果参数2为TRUE,调用pTarget-&gt;OnCmdMsg(m_nID, CN_COMMAND, this, &amp;info)测试命令消息m_nID是否被处理。这时,OnCmdMsg的第四个参数非空,表示仅仅是测试,不是真的要派发消息。如果没有提供命令消息m_nID的处理函数,则禁止用户对象m_nID,否则使之可用。</P>
<P align=justify>从上面的讨论可以知道:通过其结构,一个CCmdUI对象标识它表示了哪一个用户接口对象,如果是菜单接口对象,pMenu表示了要更新的菜单对象;如果是工具条,pOther表示了要更新的工具条窗口对象,nID表示了工具条按钮ID。</P>
<P align=justify>所以,由参数上状态更新消息的消息处理函数就知道要更新什么接口对象的状态。例如,第1节的函数OnUpdateEditPaste,函数参数pCmdUI表示一个菜单对象,需要更新该菜单对象的状态。</P>
<P align=justify>通过其成员函数,一个CCmdUI可以更新、改变用户接口对象的状态。例如,CCmdUI可以管理菜单和对话框控制的状态,调用Enable禁止或者允许菜单或者控制子窗口,等等。</P>
<P align=justify>所以,函数OnUpdateEditPaste可以直接调用参数的成员函数(如pCmdUI-&gt;Enable)实现菜单对象的状态更新。</P>
<P align=justify>由于接口对象的多样性,其他接口对象将从CCmdUI派生出管理自己的类来,覆盖基类的有关成员函数如Enable等,提供对自身状态更新的功能。例如管理状态条和工具栏更新的CStatusCmdUI类和CToolCmdUI类。</P>
<OL>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>自动更新用户接口对象状态的机制</B>
<p></LI></OL></OL></OL></OL>
<P align=justify>MFC提供了分别用于更新菜单和工具条的两种途径。</P>
<OL>
<P align=justify>
<LI>更新菜单状态
<p>
<P align=justify>当用户对菜单如File单击鼠标时,就产生一条WM_INITMENUPOPUP消息,边框窗口在菜单下拉之前响应该消息,从而更新该菜单所有项的状态。</P>
<P align=justify>在应用程序开始运行时,边框也会收到WM_INITMENUPOPUP消息。</P>
<P align=justify></P>
<LI>更新工具条等状态
<p>
<P align=justify>当应用程序进入空闲处理状态时,将发送WM_IDLEUPDATECMDUI消息,导致所有的工具条用户对象的状态处理函数被调用,从而改变其状态。WM_IDLEUPDATECMDUI是MFC自己定义和使用的消息。</P>
<P align=justify>在窗口初始化时,工具条也会收到WM_IDLEUPDATECMDUI消息。</P>
<P align=justify></P>
<LI>菜单状态更新的实现
<p>
<P align=justify>MFC让边框窗口来响应WM_INITMENUPOPUP消息,消息处理函数是OnInitMenuPopup,其原型如下:</P>
<P align=justify>afx_msg void CFrameWnd::OnInitMenuPopup( CMenu* pPopupMenu,</P>
<P align=justify>UINT nIndex, BOOL bSysMenu );</P>
<P align=justify>第一个参数指向一个CMenu对象,是当前按击的菜单;第二个参数是菜单索引;第三个参数表示子菜单是否是系统控制菜单。</P>
<P align=justify>函数的处理:</P>
<P align=justify>如果是系统控制菜单,不作处理;否则,创建CCmdUI对象state,给它的各个成员如m_pMenu,m_pParentMenu,m_pOther等赋值。</P>
<P align=justify>对该菜单的各个菜单项,调函数state.DoUpdate,用CCmdUI的DoUpdate来更新状态。DoUpdate的第一个参数是this,表示命令目标是边框窗口;在CFrameWnd的成员变量m_bAutoMenuEnable为TRUE时(表示如果菜单m_nID没有对应的消息处理函数或状态更新函数,则禁止它),把DoUpdate的第二个参数bDisableIfNoHndler置为TRUE。</P>
<P align=justify>顺便指出,m_bAutoMenuEnable缺省时为TRUE,所以,应用程序启动时菜单经过初始化处理,没有提供消息处理函数或状态更新函数的菜单项被禁止。</P>
<P align=justify></P>
<LI>工具条等状态更新的实现
<p></LI></OL>
<P align=justify>图4-5表示了消息空闲时MFC更新用户对象状态的流程:</P>
<P align=justify>MFC提供的缺省空闲处理向顶层窗口(框架窗口)的所有子窗口发送消息WM_IDLEUPDATECMDUI;MFC的控制窗口(工具条、状态栏等)实现了对该消息的处理,导致用户对象状态处理函数的调用。</P>
<P align=justify></P>
<P align=justify>虽然两种途径调用了同一状态处理函数,但是传递的 CCmdUI参数从内部构成上是不一样的:第一种传递的CCmdUI对象表示了一菜单对象,(pMenu域被赋值);第二种传递了一个窗口对象(pOther域被赋值)。同样的状态改变动作,如禁止、允许状态的改变,前者调用了CMenu的成员函数EnableMenuItem,后者使用了CWnd的成员函数EnabelWindow。但是,这些不同由CCmdUI对象内部区分、处理,对用户是透明的:不论菜单还是对应的工具条,用户都用同一个状态处理函数使用同样的形式来处理。</P><IMG src="http://www.vczx.com/tutorial/mfc/image117.gif" align=left>
<P align=justify></P>
<P align=justify> </P>
<P align=justify>这一节分析了用户界面更新的原理和机制。在后面第13章讨论工具条和状态栏时,将详细的分析这种机制的具体实现。</P>
<OL>
<OL>
<P align=justify>
<LI><B>消息的预处理</B>
<p>
<P align=justify>到现在为止,详细的讨论了MFC的消息映射机制。但是,为了提高效率和简化处理,MFC提供了一种消息预处理机制,如果一条消息在预处理时被过滤掉了(被处理),则不会被派发给目的窗口的窗口过程,更不会进入消息循环了。</P>
<P align=justify>显然,能够进行预处理的消息只可能是队列消息,而且必须在消息派发之前进行预处理。因此,MFC在实现消息循环时,对于得到的每一条消息,首先送给目的窗口、其父窗口、其祖父窗口乃至最顶层父窗口,依次进行预处理,如果没有被处理,则进行消息转换和消息派发,如果某个窗口实现了预处理,则终止。有关实现见后面关于CWinThread线程类的章节,CWinThread的Run函数和PreTranslateMessage函数以及CWnd的函数WalkPreTranslateTree实现了上述要求和功能。这里要讨论的是MFC窗口类如何进行消息预处理。</P>
<P align=justify>CWnd提供了虚拟函数PreTranslateMessage来进行消息预处理。CWnd的派生类可以覆盖该函数,实现自己的预处理。下面,讨论几个典型的预处理。</P>
<P align=justify>首先,是CWnd的预处理:</P>
<P align=justify>预处理函数的原型为:</P>
<P align=justify>BOOL CWnd:reTranslateMessage(MSG* pMsg)</P>
<P align=justify>CWnd类主要是处理和过滤Tooltips消息。关于该函数的实现和Tooltips消息,见后面第13章关于工具栏的讨论。</P>
<P align=justify></P>
<P align=justify>然后,是CFrameWnd的预处理:</P>
<P align=justify>CFrameWnd除了调用基类CWnd的实现过滤Tooltips消息之外,还要判断当前消息是否是键盘快捷键被按下,如果是,则调用函数::TranslateAccelerator(m_hWnd, hAccel, pMsg)处理快捷键。</P>
<P align=justify></P>
<P align=justify>接着,是CMDIChildWnd的预处理:</P>
<P align=justify>CMDIChildWnd的预处理过程和CFrameWnd的一样,但是不能依靠基类CFrameWnd的实现,必须覆盖它。因为MDI子窗口没有菜单,所以它必须在MDI边框窗口的上下文中来处理快捷键,它调用了函数::TranslateAccelerator(GetMDIFrame()-&gt;m_hWnd, hAccel, pMsg)。</P>
<P align=justify></P>
<P align=justify>讨论了MDI子窗口的预处理后,还要讨论MDI边框窗口:</P>
<P align=justify>CMDIFrameWnd的实现除了CFrameWnd的实现的功能外,它还要处理MDI快捷键(标准MDI界面统一使用的系统快捷键)。</P>
<P align=justify></P>
<P align=justify>在后面,还会讨论CDialog、CFormView、CToolBar等的消息预处理及其实现。</P>
<P align=justify>至于CWnd::WalkPreTranslateTree函数,它从接受消息的窗口开始,逐级向父窗回溯,逐一对各层窗口调用PreTranslateMessage函数,直到消息被处理或者到最顶层窗口为止。</P>
<P align=justify></P></LI></OL></OL>
 楼主| 发表于 2006-12-8 10:09:19 | 显示全部楼层
<OL>
<LI>
<><B>MFC消息映射的回顾</B> </P>
<p></LI></OL>
< align=justify>从处理命令消息的过程可以看出,Windows消息和控制消息的处理要比命令消息的处理简单,因为查找消息处理函数时,后者只要搜索当前窗口对象(this所指)的类或其基类的消息映射入口表。但是,命令消息就要复杂多了,它沿一定的顺序链查找链上的各个命令目标,每一个被查找的命令目标都要搜索它的类或基类的消息映射入口表。</P>
< align=justify>MFC通过消息映射的手段,以一种类似C++虚拟函数的概念向程序员提供了一种处理消息的方式。但是,若使用C++虚拟函数实现众多的消息,将导致虚拟函数表极其庞大;而使用消息映射,则仅仅感兴趣的消息才加入映射表,这样就要节省资源、提高效率。这套消息映射机制的基础包括以下几个方面:</P>
<OL>
< align=justify>
<LI>消息映射入口表的实现:采用了C++静态成员和虚拟函数的方法来表示和得到一个消息映射类(CCmdTarget或派生类)的映射表。
<p>
< align=justify></P>
<LI>消息查找的实现:从低层到高层搜索消息映射入口表,直至根类CCmdTarget。
<p>
< align=justify></P>
<LI>消息发送的实现:主要以几个虚拟函数为基础来实现标准MFC消息发送路径:OnComamnd、OnNotify、OnWndMsg和OnCmdMsg。、
<p></LI></OL>
< align=justify></P>
< align=justify>OnWndMsg是CWnd类或其派生类的成员函数,由窗口过程调用。它处理标准的Windows消息。</P>
< align=justify>OnCommand是CWnd类或其派生类的成员函数,由OnWndMsg调用来处理WM_COMMAND消息,实现命令消息或者控制通知消息的发送。如果派生类覆盖该函数,则必须调用基类的实现,否则将不能自动的处理命令消息映射,而且必须使用该函数接受的参数(不是程序员给定值)调用基类的OnCommand。</P>
< align=justify>OnNotify是CWnd类或其派生类的成员函数,由OnWndMsg调用来处理WM_NOTIFY消息,实现控制通知消息的发送。</P>
<P align=justify>OnCmdMsg是CCmdTarget类或其派生类的成员函数。被OnCommand调用,用来实现命令消息发送和派发命令消息到命令消息处理函数。</P>
<P align=justify></P>
<P align=justify>自动更新用户对象状态是通过MFC的命令消息发送机制实现的。</P>
<P align=justify>控制消息可以反射给控制窗口处理。</P>
<P align=justify>队列消息在发送给窗口过程之前可以进行消息预处理,如果消息被MFC窗口对象预处理了,则不会进入消息发送过程。</P>
<P align=justify><STRONG>MFC对象的创建</STRONG> </P>
<p>
<P align=justify>前面几章介绍了MFC的核心概念和思想,即介绍了MFC对Windows对象的封装方法和特点;MFC对象的动态创建、序列化;MFC消息映射机制。</P>
<P align=justify>现在,考查MFC的应用程序结构体系,即以文档-视为核心的编程模式。学习本章,应该弄清楚以下问题:</P>
<P align=justify>MFC中诸多MFC对象的关系:应用程序对象,文档对象,边框窗口对象,文档边框窗口对象,视对象,文档模板对象等。</P>
<P align=justify>MFC对象的创建和销毁:由什么对象创建或销毁什么对象,何时创建,何时销毁?</P>
<P align=justify>MFC提供了那些接口来支持其编程模式?</P>
<OL>
<P align=justify>
<LI><B>MFC对象的关系</B>
<p>
<OL>
<P align=justify>
<LI><B>创建关系</B>
<p><B><IMG src="http://www.vczx.com/tutorial/mfc/image118.gif" align=left> </B>
<P align=justify>这里讨论应用程序、文档模板、边框窗口、视、文档等的创建关系。图5-1大略地表示了创建顺序,但表5-1更直接地显示了创建与被创建的关系。</P>
<P align=justify> </P>
<P align=center>表5-1 MFC对象的创建关系</P>
<TABLE cellSpacing=1 cellPadding=7 width=497 border=1>

<TR>
<TD vAlign=top width="47%">
<P align=justify>创建者 </P></TD>
<TD vAlign=top width="53%">
<P align=justify>被创建的对象 </P></TD></TR>
<TR>
<TD vAlign=top width="47%">
<P align=justify>应用程序对象 </P></TD>
<TD vAlign=top width="53%">
<P align=justify>文档模板 </P></TD></TR>
<TR>
<TD vAlign=top width="47%">
<P align=justify>文档模板 </P></TD>
<TD vAlign=top width="53%">
<P align=justify>文档 </P></TD></TR>
<TR>
<TD vAlign=top width="47%">
<P align=justify>文档模板 </P></TD>
<TD vAlign=top width="53%">
<P align=justify>边框窗口 </P></TD></TR>
<TR>
<TD vAlign=top width="47%">
<P align=justify>边框窗口 </P></TD>
<TD vAlign=top width="53%">
<P align=justify>视 </P></TD></TR></TABLE></LI></OL></LI></OL>
 楼主| 发表于 2006-12-8 10:09:50 | 显示全部楼层
<OL>
<LI>
<><B>交互作用关系</B> </P>
<p></LI>
<LI>
<DIV align=justify>应用程序对象有一个文档模板列表,存放一个或多个文档模板对象;文档模板对象有一个打开文档列表,存放一个或多个已经打开的文档对象;文档对象有一个视列表,存放显示该文档数据的一个或多个视对象;还有一个指针指向创建该文档的文档模板对象;视有一个指向其关联文档的指针,视是一个子窗口,其父窗口是边框窗口(或者文档边框窗口);文档边框窗口有一个指向其当前活动视的指针;文档边框窗口是边框窗口的子窗口。</DIV></LI>
<LI>
<DIV align=justify>Windows 管理所有已经打开的窗口,把消息或事件发送给目标窗口。通常,命令消息发送给主边框窗口。</DIV></LI>
<LI><IMG src="http://www.vczx.com/tutorial/mfc/image119.gif" align=left> </LI>
<LI>
<DIV align=justify>图5-2大略地表示了上述关系:</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI>
<DIV align=justify>MFC提供了一些函数来维护这些关系。</DIV></LI>
<LI>
<DIV align=justify>表5-2列出了从一个对象得到相关对象的方法。</DIV></LI>
<LI>
<DIV align=center>表5-2 从一个对象得到另一个对象的方法</DIV></LI></OL>
<DIV>
<TABLE cellSpacing=1 cellPadding=7 width=497 border=1>

<TR>
<TD vAlign=top width="29%">
<OL>
<LI>
<DIV align=center>本对象 </DIV></LI></OL></TD>
<TD vAlign=top width="27%">
< align=center>要得到的对象 </P></TD>
<TD vAlign=top width="44%">
< align=justify>使用的成员函数 </P></TD></TR>
<TR>
<TD vAlign=top width="29%" rowSpan=2>
< align=justify>CDocument对象 </P></TD>
<TD vAlign=top width="27%">
< align=justify>视列表 </P></TD>
<TD vAlign=top width="44%">
< align=justify>GetFirstViewPosition</P>
< align=justify>GetNextView </P></TD></TR>
<TR>
<TD vAlign=top width="27%">
< align=justify>文档模板 </P></TD>
<TD vAlign=top width="44%">
< align=justify>GetDocTemplate </P></TD></TR>
<TR>
<TD vAlign=top width="29%" rowSpan=2>
< align=justify>CView对象 </P></TD>
<TD vAlign=top width="27%">
<P align=justify>文档对象 </P></TD>
<TD vAlign=top width="44%">
<P align=justify>GetDocument </P></TD></TR>
<TR>
<TD vAlign=top width="27%">
<P align=justify>边框窗口 </P></TD>
<TD vAlign=top width="44%">
<P align=justify>GetParentFrame </P></TD></TR>
<TR>
<TD vAlign=top width="29%" rowSpan=2>
<P align=justify>CMDIChildWnd或</P>
<P align=justify>CFrameWnd对象 </P></TD>
<TD vAlign=top width="27%">
<P align=justify>活动视 </P></TD>
<TD vAlign=top width="44%">
<P align=justify>GetActiveView </P></TD></TR>
<TR>
<TD vAlign=top width="27%">
<P align=justify>活动视的文档 </P></TD>
<TD vAlign=top width="44%">
<P align=justify>GetActiveDocument </P></TD></TR>
<TR>
<TD vAlign=top width="29%">
<P align=justify>CMDIFrameWnd对象 </P></TD>
<TD vAlign=top width="27%">
<P align=justify>活动文档边框窗口 </P></TD>
<TD vAlign=top width="44%">
<P align=justify>MDIGetActive </P></TD></TR></TABLE></DIV>
<OL>
<LI>
<DIV align=center> </DIV></LI>
<LI>
<DIV align=center>表5-3 从一个对象通知另一个对象的方法:</DIV></LI></OL>
<DIV>
<TABLE cellSpacing=1 cellPadding=7 width=497 border=1>

<TR>
<TD vAlign=top width="31%">
<OL>
<LI>
<DIV align=justify>本对象 </DIV></LI></OL></TD>
<TD vAlign=top width="30%">
<P align=justify>要通知的对象/动作 </P></TD>
<TD vAlign=top width="39%">
<P align=justify>使用的成员函数 </P></TD></TR>
<TR>
<TD vAlign=top width="31%">
<P align=justify>CView对象 </P></TD>
<TD vAlign=top width="30%">
<P align=justify>通知文档更新所有视 </P></TD>
<TD vAlign=top width="39%">
<P align=justify>CDocument::UpdateAllViews </P></TD></TR>
<TR>
<TD vAlign=top width="31%">
<P align=justify>CDocument对象 </P></TD>
<TD vAlign=top width="30%">
<P align=justify>更新一个视 </P></TD>
<TD vAlign=top width="39%">
<P align=justify>CView::OnUpdate </P></TD></TR>
<TR>
<TD vAlign=top width="31%" rowSpan=2>
<P align=justify>CFrameWnd或</P>
<P align=justify>CMDIFrameWnd对象 </P></TD>
<TD vAlign=top width="30%">
<P align=justify>通知一个视为活动视 </P></TD>
<TD vAlign=top width="39%">
<P align=justify>CView::OnActivateView </P></TD></TR>
<TR>
<TD vAlign=top width="30%">
<P align=justify>设置一个视为活动视 </P></TD>
<TD vAlign=top width="39%">
<P align=justify>SetActivateView </P></TD></TR></TABLE></DIV>
<P align=justify></P>
<OL>
<LI>
<DIV align=justify>可以通过表5-2得到相关对象,再调用表5-3中相应的函数。例如:视在接受了新数据或者数据被修改之后,使用表5-2中的函数GetDocument得到关联文档对象,然后调用表5-3中的文档函数UpdateAllViews更新其他和文档对象关联的视。</DIV></LI>
<LI>
<DIV align=justify>在表5-2和表5-3中,CView对象指CView或派生类的实例;成员函数列中如果没有指定类属,就是第一列对象的类的成员函数。</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI><B>MFC提供的接口</B> </LI>
<LI></LI>
<LI>
<DIV align=justify>MFC编程就是把一些应用程序特有的东西填入MFC框架。MFC提供了两种填入的方法:一种就是使用前一章论述的消息映射,消息映射给应用程序的各种对象处理各种消息的机会;另一种就是使用虚拟函数,MFC在实现许多功能或者处理消息、事件的过程中,调用了虚拟函数来完成一些任务,这样就给了派生类覆盖这些虚拟函数实现特定处理的机会。</DIV></LI>
<LI>
<DIV align=justify>下面两节将列出两类接口,有两个目的:一是为了让读者获得整体印象,二是后文将涉及到或者讨论其中的许多函数时,不显得突兀。</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI><B>虚拟函数接口</B>
<p>
<P align=justify>几乎每一个MFC类都定义和使用了虚拟成员函数,程序员可以在派生类中覆盖它们。一般,MFC提供了这些函数的缺省实现,所以覆盖函数应该调用基类的实现。这里给出一个MFC常用虚拟函数的总览表(见表5-4),更详细的信息或它们的缺省实现动作参见MFC文档。由于基类的虚拟函数被派生类继承,所以在派生类中不作重复说明。</P>
<P align=justify>覆盖基类的虚拟函数可以通过ClassWizard进行,不过,并非所有的函数都可以这样,有的必须手工加入函数声明和实现。</P>
<P align=center>表5-4 常见MFC类的虚拟函数接口</P>
<TABLE cellSpacing=1 cellPadding=7 width=497 border=1>

<TR>
<TD vAlign=top width="23%">
<P align=justify>类 </P></TD>
<TD vAlign=top width="34%">
<P align=justify>虚拟函数 </P></TD>
<TD vAlign=top width="44%">
<P align=justify>覆盖的目的和功能 </P></TD></TR>
<TR>
<TD vAlign=top width="23%" rowSpan=2>
<P align=justify>CCmdTarget </P></TD>
<TD vAlign=top width="34%">
<P align=justify>OnCmdMsg </P></TD>
<TD vAlign=top width="44%">
<P align=justify>发送、派发命令消息 </P></TD></TR>
<TR>
<TD vAlign=top width="34%">
<P align=justify>OnFinalRelease </P></TD>
<TD vAlign=top width="44%">
<P align=justify>OLE用途,引用为0时作清理工作 </P></TD></TR>
<TR>
<TD vAlign=top width="23%" rowSpan=8>
<P align=justify>CWinThread </P></TD>
<TD vAlign=top width="34%">
<P align=justify>ExitInstance </P></TD>
<TD vAlign=top width="44%">
<P align=justify>在线程退出时作清理工作 </P></TD></TR>
<TR>
<TD vAlign=top width="34%">
<P align=justify>InitInstance </P></TD>
<TD vAlign=top width="44%">
<P align=justify>在线程开始时作初始化 </P></TD></TR>
<TR>
<TD vAlign=top width="34%">
<P align=justify>OnIdle </P></TD>
<TD vAlign=top width="44%">
<P align=justify>执行thread-specific idle-time处理 </P></TD></TR>
<TR>
<TD vAlign=top width="34%">
<P align=justify>PreTranslateMessage </P></TD>
<TD vAlign=top width="44%">
<P align=justify>在消息送给Windows函数TranslateMessage and DispatchMessage.之前进行消息过滤 </P></TD></TR>
<TR>
<TD vAlign=top width="34%">
<P align=justify>IsIdleMessage </P></TD>
<TD vAlign=top width="44%">
<P align=justify>检查是否是某个特别的消息 </P></TD></TR>
<TR>
<TD vAlign=top width="34%">
<P align=justify>ProcessWndProcException </P></TD>
<TD vAlign=top width="44%">
<P align=justify>截获线程消息/命令处理中的例外 </P></TD></TR>
<TR>
<TD vAlign=top width="34%">
<P align=justify>ProcessMessageFilter </P></TD>
<TD vAlign=top width="44%">
<P align=justify>线程消息过滤 </P></TD></TR>
<TR>
<TD vAlign=top width="34%">
<P align=justify>Run </P></TD>
<TD vAlign=top width="44%">
<P align=justify>实现线程特定的消息循环 </P></TD></TR>
<TR>
<TD vAlign=top width="23%" rowSpan=2>
<P align=justify>CWinApp </P></TD>
<TD vAlign=top width="34%">
<P align=justify>HideApplication </P></TD>
<TD vAlign=top width="44%">
<P align=justify>关闭所有的窗口之前隐藏应用程序 </P></TD></TR>
<TR>
<TD vAlign=top width="34%">
<P align=justify>CloseAllDocument </P></TD>
<TD vAlign=top width="44%">
<P align=justify>退出程序之前关闭所有文档 </P></TD></TR>
<TR>
<TD vAlign=top colSpan=3>
<P align=justify>转下页 </P></TD></TR></TABLE>
<P align=justify></P>
<P align=justify> </P>
<TABLE cellSpacing=1 cellPadding=7 width=497 border=1>

<TR>
<TD vAlign=top colSpan=3>
<P align=justify>续表 </P></TD></TR>
<TR>
<TD vAlign=top width="24%" rowSpan=5> </TD>
<TD vAlign=top width="31%">
<P align=justify>SaveModifiedDocument </P></TD>
<TD vAlign=top width="45%">
<P align=justify>框架窗口关闭时用来保存文档 </P></TD></TR>
<TR>
<TD vAlign=top width="31%">
<P align=justify>DoMessageBox </P></TD>
<TD vAlign=top width="45%">
<P align=justify>实现客户化的messagebox </P></TD></TR>
<TR>
<TD vAlign=top width="31%">
<P align=justify>DoWaitCursor </P></TD>
<TD vAlign=top width="45%">
<P align=justify>关闭或打开等待光标 </P></TD></TR>
<TR>
<TD vAlign=top width="31%">
<P align=justify>OnDDeCommand </P></TD>
<TD vAlign=top width="45%">
<P align=justify>响应DDE命令 </P></TD></TR>
<TR>
<TD vAlign=top width="31%">
<P align=justify>WinHelp </P></TD>
<TD vAlign=top width="45%">
<P align=justify>调用WinHelp函数 </P></TD></TR>
<TR>
<TD vAlign=top width="24%" rowSpan=6>
<P align=justify>CWnd </P></TD>
<TD vAlign=top width="31%">
<P>WindowProc </P></TD>
<TD vAlign=top width="45%">
<P align=justify>提供一个窗口过程 </P></TD></TR>
<TR>
<TD vAlign=top width="31%">
<P>DefWindowProc </P></TD>
<TD vAlign=top width="45%">
<P align=justify>为应用程序不处理的消息提供缺省处理 </P></TD></TR>
<TR>
<TD vAlign=top width="31%">
<P>PostNcDestroy </P></TD>
<TD vAlign=top width="45%">
<P align=justify>在窗口销毁之后被消息处理函数OnNcDestroy调用 </P></TD></TR>
<TR>
<TD vAlign=top width="31%">
<P>OnNotify </P></TD>
<TD vAlign=top width="45%">
<P align=justify>处理通知消息WM_NOTIFY </P></TD></TR>
<TR>
<TD vAlign=top width="31%">
<P>OnChildNotify </P></TD>
<TD vAlign=top width="45%">
<P align=justify>父窗口调用它给控制子窗口一个机会来处理通知反射消息 </P></TD></TR>
<TR>
<TD vAlign=top width="31%">
<P align=justify>DoDataExchange </P></TD>
<TD vAlign=top width="45%">
<P align=justify>Updata调用它来进行对话框数据交换和验证 </P></TD></TR>
<TR>
<TD vAlign=top width="24%" rowSpan=4>
<P align=justify>CFrameWnd </P></TD>
<TD vAlign=top width="31%">
<P align=justify>GetMessageBar </P></TD>
<TD vAlign=top width="45%">
<P align=justify>返回一个指向框架窗口的状态条的指针 </P></TD></TR>
<TR>
<TD vAlign=top width="31%">
<P>OnCreateClient </P></TD>
<TD vAlign=top width="45%">
<P align=justify>创建框架的客户窗口 </P></TD></TR>
<TR>
<TD vAlign=top width="31%">
<P>OnSetPreviewMode </P></TD>
<TD vAlign=top width="45%">
<P align=justify>设置程序的主框架窗口进入或退出打印预览模式 </P></TD></TR>
<TR>
<TD vAlign=top width="31%">
<P align=justify>NegotiateBorderSpace </P></TD>
<TD vAlign=top width="45%">
<P align=justify>协调边框窗口的边框空间的大小(OLE用途) </P></TD></TR>
<TR>
<TD vAlign=top width="24%">
<P align=justify>CMDIFrameWnd </P></TD>
<TD vAlign=top width="31%">
<P>CreateClient </P></TD>
<TD vAlign=top width="45%">
<P align=justify>创建CMDIFrameWnd的MDICLIENT窗,被CWnd的消息处理函数OnCreate调用. </P></TD></TR>
<TR>
<TD vAlign=top colSpan=3>
<P>转下页 </P></TD></TR></TABLE>
<P align=justify></P></LI></OL>
 楼主| 发表于 2006-12-8 10:11:59 | 显示全部楼层
< align=justify> </P>
<TABLE cellSpacing=1 cellPadding=7 width=451 border=1>

<TR>
<TD vAlign=top colSpan=3>
< align=justify>续表 </P></TD></TR>
<TR>
<TD vAlign=top width="26%"> </TD>
<TD vAlign=top width="32%">
< align=justify>GetWindowMenuPopup </P></TD>
<TD vAlign=top width="43%">
< align=justify>返回窗口的弹出式菜单 </P></TD></TR>
<TR>
<TD vAlign=top width="26%" rowSpan=4>
< align=justify>CDialog </P></TD>
<TD vAlign=top width="32%">
< align=justify>OnInitDialog </P></TD>
<TD vAlign=top width="43%">
< align=justify>对话框窗口的初始化 </P></TD></TR>
<TR>
<TD vAlign=top width="32%">
< align=justify>OnSetFont </P></TD>
<TD vAlign=top width="43%">
< align=justify>设置对话框控制的文本字体 </P></TD></TR>
<TR>
<TD vAlign=top width="32%">
< align=justify>OnOK </P></TD>
<TD vAlign=top width="43%">
<P align=justify>模式对话框的OK按钮按下后进行的处理 </P></TD></TR>
<TR>
<TD vAlign=top width="32%">
<P align=justify>OnCancel </P></TD>
<TD vAlign=top width="43%">
<P align=justify>模式对话框的CANCEL按钮按下后进行的处理 </P></TD></TR>
<TR>
<TD vAlign=top width="26%" rowSpan=11>
<P align=justify>CView </P></TD>
<TD vAlign=top width="32%">
<P align=justify>IsSelected </P></TD>
<TD vAlign=top width="43%">
<P align=justify>测试是否有一个文档被选择(OLE支持) </P></TD></TR>
<TR>
<TD vAlign=top width="32%">
<P align=justify>OnActivateView </P></TD>
<TD vAlign=top width="43%">
<P align=justify>视窗口激活时调用 </P></TD></TR>
<TR>
<TD vAlign=top width="32%">
<P align=justify>OnActivateFrame </P></TD>
<TD vAlign=top width="43%">
<P align=justify>当包含视窗口的框架窗口变成活动或非活动窗口时调用 </P></TD></TR>
<TR>
<TD vAlign=top width="32%">
<P align=justify>OnBeginPrinting </P></TD>
<TD vAlign=top width="43%">
<P align=justify>打印工作开始时调用,用来分配GDI资源 </P></TD></TR>
<TR>
<TD vAlign=top width="32%">
<P align=justify>OnDraw </P></TD>
<TD vAlign=top width="43%">
<P align=justify>用来屏幕显示、打印、打印预览文档内容 </P></TD></TR>
<TR>
<TD vAlign=top width="32%">
<P align=justify>OnEndPrinting </P></TD>
<TD vAlign=top width="43%">
<P align=justify>打印工作结束时调用,释放GDI资源 </P></TD></TR>
<TR>
<TD vAlign=top width="32%">
<P align=justify>OnEndPrintPreview </P></TD>
<TD vAlign=top width="43%">
<P align=justify>退出打印预览模式时调用 </P></TD></TR>
<TR>
<TD vAlign=top width="32%">
<P align=justify>OnPrepareDC </P></TD>
<TD vAlign=top width="43%">
<P align=justify>OnDraw或OnPrint之前调用,用来准备设备描述表 </P></TD></TR>
<TR>
<TD vAlign=top width="32%">
<P align=justify>OnPreparePrinting </P></TD>
<TD vAlign=top width="43%">
<P align=justify>文档打印或者打印预览前调用,可用来初始化打印对话框 </P></TD></TR>
<TR>
<TD vAlign=top width="32%">
<P align=justify>OnPrint </P></TD>
<TD vAlign=top width="43%">
<P align=justify>用来打印或打印预览文档 </P></TD></TR>
<TR>
<TD vAlign=top width="32%">
<P align=justify>OnUpdate </P></TD>
<TD vAlign=top width="43%">
<P align=justify>用来通知一个视的关联文档内容已经变化 </P></TD></TR>
<TR>
<TD vAlign=top width="26%">
<P align=justify>CDocTemplate </P></TD>
<TD vAlign=top width="32%">
<P align=justify>MatchDocType </P></TD>
<TD vAlign=top width="43%">
<P align=justify>确定文档类型和文档模板匹配时的可信程度 </P></TD></TR>
<TR>
<TD vAlign=top colSpan=3>
<P align=justify>转下页 </P></TD></TR></TABLE>
<P align=justify></P>
<P align=justify> </P>
<TABLE cellSpacing=1 cellPadding=7 width=451 border=1>

<TR>
<TD vAlign=top colSpan=3>
<P align=justify>续表 </P></TD></TR>
<TR>
<TD vAlign=top width="20%" rowSpan=7> </TD>
<TD vAlign=top width="37%">
<P align=justify>CreateNewDocument </P></TD>
<TD vAlign=top width="43%">
<P align=justify>创建一个新的文档 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>CreateNewFrame </P></TD>
<TD vAlign=top width="43%">
<P align=justify>创建一个包含文档和视的框架窗口 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>InitialUpdateFrame </P></TD>
<TD vAlign=top width="43%">
<P align=justify>初始化框架窗口,必要时使它可见 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>SaveAllModified </P></TD>
<TD vAlign=top width="43%">
<P align=justify>保存所有和模板相关的而且修改了的文档 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>CloseAllDocuments </P></TD>
<TD vAlign=top width="43%">
<P align=justify>关闭所有和模板相关的文档 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>OpenDocumentFile </P></TD>
<TD vAlign=top width="43%">
<P align=justify>打开指定路径的文件 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>SetDefaultTitle </P></TD>
<TD vAlign=top width="43%">
<P align=justify>设置文档窗口缺省显示的标题 </P></TD></TR>
<TR>
<TD vAlign=top width="20%" rowSpan=12>
<P align=justify>CDocument </P></TD>
<TD vAlign=top width="37%">
<P align=justify>CanCloseFrame </P></TD>
<TD vAlign=top width="43%">
<P align=justify>在关闭显示该文档的边框窗口之前调用 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>DeleteContents </P></TD>
<TD vAlign=top width="43%">
<P align=justify>用来清除文档的内容 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>OnChangedViewList </P></TD>
<TD vAlign=top width="43%">
<P align=justify>在与文档关联的视图被移走或新加入时调用 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>OnCloseDocument </P></TD>
<TD vAlign=top width="43%">
<P align=justify>用来关闭文档 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>OnNewDocument </P></TD>
<TD vAlign=top width="43%">
<P align=justify>用来创建新文档 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>OnOpenDocument </P></TD>
<TD vAlign=top width="43%">
<P align=justify>用来打开文档 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>OnSaveDocument </P></TD>
<TD vAlign=top width="43%">
<P align=justify>以来保存文档 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>ReportSaveLoadException </P></TD>
<TD vAlign=top width="43%">
<P align=justify>处理打开、保存文档操作失败时的例外 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>GetFile </P></TD>
<TD vAlign=top width="43%">
<P align=justify>返回一个指向Cfile对象的指针 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>ReleaseFile </P></TD>
<TD vAlign=top width="43%">
<P align=justify>释放一个文件以便其他应用程序可以使用 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>SaveModified </P></TD>
<TD vAlign=top width="43%">
<P align=justify>用来询问用户文档是否需要保存 </P></TD></TR>
<TR>
<TD vAlign=top width="37%">
<P align=justify>PreCloseFrame </P></TD>
<TD vAlign=top width="43%">
<P align=justify>在框架窗口关闭之前调用 </P></TD></TR></TABLE>
 楼主| 发表于 2006-12-8 10:12:15 | 显示全部楼层
<OL>
<LI>
<><B>消息映射方法和标准命令消息</B> </P>
<p></LI>
<LI>
<DIV align=justify>窗口对象可以响应以“WM_”为前缀的标准Windows消息,消息处理函数名称以“ON”为前缀。不同类型的Windows窗口处理的Windows消息是有所不同的,因此,不同类型的MFC窗口实现的消息处理函数也有所不同。例如,多文档边框窗口能处理WM_MDIACTIVATE消息,其他类型窗口就不能。程序员从一定的MFC窗口派生自己的窗口类,对感兴趣的消息,覆盖基类的消息处理函数,实现自己的消息处理函数。</DIV></LI>
<LI>
<DIV align=justify>所有的命令目标(CCmdTarger或导出类对象)可以响应命令消息,程序员可以指定应用程序对象、框架窗口对象、视对象或文档对象等来处理某条命令消息。一般地,尽量由与命令消息关系密切的对象来处理,例如隐藏/显示工具栏由框架窗口处理,打开文件由应用程序对象处理,数据变化的操作由文档对象处理。</DIV></LI>
<LI>
<DIV align=justify>对话框的控制子窗口可以响应各类通知消息。</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI>
<DIV align=justify>对于命令消息,MFC实现了一系列标准命令消息处理函数。标准命令ID在afxres.h中定义。表5-5列出了MFC标准命令的实现,从ID或者函数名可以大致地看出该函数的目的、功用,具体的实现有的后续章节会讲解,详细参见MFC技术文档。</DIV></LI>
<LI>
<DIV align=justify>程序员可以自己来处理这些标准消息,也可以通过不同的类或从不同的类导出自己的类来处理这些消息,不过最好遵循MFC的缺省实现。比如处理ID_FILE_NEW命令,最好由CWinApp的派生类处理。</DIV></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI>
<DIV align=center>表5-5 标准命令消息处理函数</DIV></LI></OL>
<TABLE cellSpacing=1 cellPadding=7 width=504 border=1>

<TR>
<TD vAlign=top width="37%" height=21>
<OL>
<LI>
<DIV align=justify>ID </DIV></LI></OL></TD>
<TD vAlign=top width="32%" height=21>
< align=justify>函数 </P></TD>
<TD vAlign=top width="31%" height=21>
< align=justify>实现函数的类 </P></TD></TR>
<TR>
<TD vAlign=top width="37%" height=20>
< align=justify>ID_FILE_NEW </P></TD>
<TD vAlign=top width="32%" height=20>
< align=justify>OnFileNew </P></TD>
<TD vAlign=top width="31%" height=20>
< align=justify>CWinApp </P></TD></TR>
<TR>
<TD vAlign=top width="37%" height=20>
< align=justify>ID_FILE_OPEN </P></TD>
<TD vAlign=top width="32%" height=20>
< align=justify>OnFileOpen </P></TD>
<TD vAlign=top width="31%" height=20>
< align=justify>CWinApp </P></TD></TR>
<TR>
<TD vAlign=top width="37%" height=20>
< align=justify>ID_FILE_CLOSE </P></TD>
<TD vAlign=top width="32%" height=20>
<P align=justify>OnFileClose </P></TD>
<TD vAlign=top width="31%" height=20>
<P align=justify>CDocument </P></TD></TR>
<TR>
<TD vAlign=top width="37%" height=20>
<P align=justify>ID_FILE_SAVE </P></TD>
<TD vAlign=top width="32%" height=20>
<P align=justify>OnFileSave </P></TD>
<TD vAlign=top width="31%" height=20>
<P align=justify>CDocument </P></TD></TR>
<TR>
<TD vAlign=top width="37%" height=20>
<P align=justify>ID_FILE_SAVE_AS </P></TD>
<TD vAlign=top width="32%" height=20>
<P align=justify>OnFileSaveAs </P></TD>
<TD vAlign=top width="31%" height=20>
<P align=justify>CDocument </P></TD></TR>
<TR>
<TD vAlign=top width="37%" height=20>
<P align=justify>ID_FILE_SAVE_COPY_AS </P></TD>
<TD vAlign=top width="32%" height=20>
<P align=justify>OnFileSaveCopyAs </P></TD>
<TD vAlign=top width="31%" height=20>
<P align=justify>COleServerDoc </P></TD></TR>
<TR>
<TD vAlign=top width="37%" height=20>
<P align=justify>ID_FILE_UPDATE </P></TD>
<TD vAlign=top width="32%" height=20>
<P align=justify>OnUpdateDocument </P></TD>
<TD vAlign=top width="31%" height=20>
<P align=justify>COleServerDoc </P></TD></TR>
<TR>
<TD vAlign=top width="37%" height=20>
<P align=justify>ID_FILE_PAGE_SETUP </P></TD>
<TD vAlign=top width="32%" height=20>
<P align=justify>OnFilePrintSetup </P></TD>
<TD vAlign=top width="31%" height=20>
<P align=justify>CWinApp </P></TD></TR>
<TR>
<TD vAlign=top colSpan=3 height=20>
<P align=justify>转下页 </P></TD></TR></TABLE>
<P align=justify></P>
<OL>
<LI>
<DIV align=justify> </DIV></LI></OL>
<TABLE cellSpacing=1 cellPadding=7 width=497 border=1>

<TR>
<TD vAlign=top colSpan=3 height=20>
<OL>
<LI>
<DIV align=justify>续表 </DIV></LI></OL></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_FILE_PRINT </P></TD>
<TD vAlign=top width="33%" height=20>
<P align=justify>OnFilePrint </P></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CView </P></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_FILE_PRINT_PREVIEW </P></TD>
<TD vAlign=top width="33%" height=20>
<P align=justify>OnFilePrintPreview </P></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CView </P></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_FILE_MRU_FILE1...FILE16 </P></TD>
<TD vAlign=top width="33%" height=20>
<P align=justify>OnUpdateRecentFileMenu </P></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CWinApp </P></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_EDIT_CLEAR </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CView没有实现, </P></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_EDIT_CLEAR_ALL </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>但是,如果有实现 </P></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_EDIT_COPY </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>函数,就是派生类 </P></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_EDIT_CUT </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CEditView的 </P></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_EDIT_FIND </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>实现函数 </P></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_EDIT_PASTE_LINK </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<p></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_EDIT_PASTE_SPECIAL </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<p></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_EDIT_REPEAT </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<p></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_EDIT_REPLACE </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<p></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_EDIT_SELET_ALL </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<p></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_EDIT_UNDO </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<p></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_WINDOW_NEW </P></TD>
<TD vAlign=top width="33%" height=20>
<P align=justify>OnWindowNew </P></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CMDIFrameWnd </P></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_WINDOW_ARRANGE </P></TD>
<TD vAlign=top width="33%" height=20>
<P align=justify>OnMDIWindowCmd </P></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CMDIFrameWnd </P></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_WINDOW_CASCADE </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<p></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_WINDOW_TILE_HORZ </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<p></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_WINDOW_TILE_VERT </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<p></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_WINDOW_SPLIT </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CSplitterWnd </P></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_APP_ABOUT </P></TD>
<TD vAlign=top width="33%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<p></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_APP_EXIT </P></TD>
<TD vAlign=top width="33%" height=20>
<P align=justify>OnAppExit </P></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CWinApp </P></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_HELP_INDEX </P></TD>
<TD vAlign=top width="33%" height=20>
<P align=justify>OnHelpIndex </P></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CWinApp </P></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_HELP_USING </P></TD>
<TD vAlign=top width="33%" height=20>
<P align=justify>OnHelpUsing </P></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CWinApp </P></TD></TR>
<TR>
<TD vAlign=top width="38%" height=20>
<P align=justify>ID_CONTEXT_HELP </P></TD>
<TD vAlign=top width="33%" height=20>
<P align=justify>OnContextHelp </P></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CWinApp </P></TD></TR>
<TR>
<TD vAlign=top colSpan=3 height=20>
<P align=justify>转下页 </P></TD></TR></TABLE>
<P align=justify></P>
<OL>
<LI>
<DIV align=justify> </DIV></LI></OL>
<TABLE cellSpacing=1 cellPadding=7 width=497 border=1>

<TR>
<TD vAlign=top colSpan=3 height=20>
<OL>
<LI>
<DIV align=justify>续表 </DIV></LI></OL></TD></TR>
<TR>
<TD vAlign=top width="39%" height=20>
<P align=justify>ID_HELP </P></TD>
<TD vAlign=top width="31%" height=20>
<P align=justify>OnHelp </P></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CWinApp </P></TD></TR>
<TR>
<TD vAlign=top width="39%" height=20>
<P align=justify>ID_DEFAULT_HELP </P></TD>
<TD vAlign=top width="31%" height=20>
<P align=justify>OnHelpIndex </P></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CWinApp </P></TD></TR>
<TR>
<TD vAlign=top width="39%" height=20>
<P align=justify>ID_NEXT_PANE </P></TD>
<TD vAlign=top width="31%" height=20>
<P align=justify>OnNextPaneCmd </P></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CSplitterWnd </P></TD></TR>
<TR>
<TD vAlign=top width="39%" height=20>
<P align=justify>ID_PREV_PANE </P></TD>
<TD vAlign=top width="31%" height=20>
<P align=justify>OnNextPaneCmd </P></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CSplitterWnd </P></TD></TR>
<TR>
<TD vAlign=top width="39%" height=20>
<P align=justify>ID_OLE_INSERT_NEW </P></TD>
<TD vAlign=top width="31%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<p></TD></TR>
<TR>
<TD vAlign=top width="39%" height=20>
<P align=justify>ID_OLE_EDIT_LINKS </P></TD>
<TD vAlign=top width="31%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<p></TD></TR>
<TR>
<TD vAlign=top width="39%" height=20>
<P align=justify>ID_OLE_VERB_FIRST...LAST </P></TD>
<TD vAlign=top width="31%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<p></TD></TR>
<TR>
<TD vAlign=top width="39%" height=20>
<P align=justify>ID_VIEW_TOOLBAR </P></TD>
<TD vAlign=top width="31%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CFrameWnd </P></TD></TR>
<TR>
<TD vAlign=top width="39%" height=20>
<P align=justify>ID_VIEW_STATUS_BAR </P></TD>
<TD vAlign=top width="31%" height=20>
<p></TD>
<TD vAlign=top width="30%" height=20>
<P align=justify>CFrameWnd </P></TD></TR>
<TR>
<TD vAlign=top width="39%" height=20>
<P align=justify>ID_INDICATOR_CAPS</P>
<P align=justify>ID_INDICATOR_NUM</P>
<P align=justify>ID_INDICATOR_SCRL</P>
<P align=justify>ID_INDICATOR_KANA </P></TD>
<TD vAlign=top width="31%" height=20>
<P align=justify>OnUpdateKeyIndica</P></TD></TR></TABLE>
 楼主| 发表于 2006-12-8 10:13:28 | 显示全部楼层
<STRONG>MFC对象的创建过程</STRONG>
<>
< align=justify>应用程序使用MFC的接口是把一些自己的特殊处理填入MFC框架,这些处理或者在应用程序启动和初始化的时候被调用,或者在程序启动之后和用户交互的过程中被调用,或者在程序退出和作清理工作的时候被调用。这三个阶段中,和用户交互阶段是各个程序自己的事情,自然都不一样,但是程序的启动和退出两个阶段是MFC框架所实现的,是MFC框架的一部分,各个程序都遵循同样的步骤和规则。显然,清楚MFC框架对这两个阶段的处理是很有必要的,它可以帮助深入理解MFC框架,更好地使用MFC框架,更有效地实现应用程序特定的处理。</P>
< align=justify>MFC程序启动和初始化过程就是创建MFC对象和Windows对象、建立各种对象之间的关系、把窗口显示在屏幕上的过程,退出过程就是关闭窗口、销毁所创建的Windows对象和MFC对象的过程。所以,下面要讨论几种常用MFC对象的结构,它们是构成一个文档-视模式应用程序的重要部件。</P>
<OL>
< align=justify>
<LI><B>应用程序中典型对象的结构</B>
<>
< align=justify>本节将主要分析应用程序对象、文档对象、文档模板等的数据结构。通过考察类的结构,特别是成员变量结构,弄清它的功能、目的以及和其他类的关系;另外,在后续有关分析中必定会提到这些成员变量,这里先作个说明,到时也不会显得突兀。</P>
< align=justify>下面几节以表格的形式来描述各个类的成员变量。表格中,第一列打钩的表示是MFC类库文档有说明的;没打钩的在文档中没有说明,如果是public,则可以直接访问,但随着MFC版本的变化,以后MFC可能不支持这些成员;第二列是访问属性;第三列是成员变量名称;第四列是成员变量的数据类型;第五列是对成员变量的功能、用途的简要描述。</P>
<OL>
< align=justify>
<LI><B>应用程序类的成员变量</B>
<>
< align=justify>应用程序对象的数据成员表由两部分组成,第一部分是CWinThread的成员变量,如表5-6所示,CWinApp继承了CWinThread的数据成员。第二部分是CWinApp自己定义的成员变量,如表5-7所示。</P>
<P align=center>表5-6 CwinThread的成员变量</P>
<TABLE cellSpacing=1 cellPadding=7 width=497 border=1>

<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>访问限制 </P></TD>
<TD vAlign=top width="25%">
<P align=justify>变量名称 </P></TD>
<TD vAlign=top width="17%">
<P align=justify>类型 </P></TD>
<TD vAlign=top width="39%">
<P align=justify>解释 </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="25%">
<P align=justify>m_bAutoDelete </P></TD>
<TD vAlign=top width="17%">
<P align=justify>BOOL </P></TD>
<TD vAlign=top width="39%">
<P align=justify>指定线程结束时是否销毁线程对象本身 </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="25%">
<P align=justify>m_hThread </P></TD>
<TD vAlign=top width="17%">
<P align=justify>HANDLE </P></TD>
<TD vAlign=top width="39%">
<P align=justify>当前线程的句柄 </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="25%">
<P align=justify>m_nThreadID </P></TD>
<TD vAlign=top width="17%">
<P align=justify>UINT </P></TD>
<TD vAlign=top width="39%">
<P align=justify>当前线程的ID </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="25%">
<P align=justify>m_pMainWnd </P></TD>
<TD vAlign=top width="17%">
<P align=justify>CWnd* </P></TD>
<TD vAlign=top width="39%">
<P align=justify>指向应用程序主窗口的指针 </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="25%">
<P align=justify>m_pActiveWnd </P></TD>
<TD vAlign=top width="17%">
<P align=justify>CWnd* </P></TD>
<TD vAlign=top width="39%">
<P align=justify>当OLE SERVER就地激活时指向客户程序主窗口的指针 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="25%">
<P align=justify>m_msgCur </P></TD>
<TD vAlign=top width="17%">
<P align=justify>MSG </P></TD>
<TD vAlign=top width="39%">
<P align=justify>当前消息(MSG结构) </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="25%">
<P align=justify>m_pThreadParams </P></TD>
<TD vAlign=top width="17%">
<P align=justify>LPVOID </P></TD>
<TD vAlign=top width="39%">
<P align=justify>传递给线程开始函数的参数 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="25%">
<P align=justify>m_pfnThreadProc </P></TD>
<TD vAlign=top width="17%">
<P align=justify>函数指针1 </P></TD>
<TD vAlign=top width="39%">
<P align=justify>线程开始函数,AFX_THREADPROC类型 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="25%">
<P align=justify>m_lpfnOleTermOrFreeLib </P></TD>
<TD vAlign=top width="17%">
<P align=justify>函数指针2 </P></TD>
<TD vAlign=top width="39%">
<P align=justify>OLE用途,void (AFXAPI * fn)(BOOL,BOOL) </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="25%">
<P align=justify>m_pMessageFilter </P></TD>
<TD vAlign=top width="17%">
<P align=justify>指针 </P></TD>
<TD vAlign=top width="39%">
<P align=justify>OLE消息过滤,指向COleMessageFilter对象 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="25%">
<P align=justify>m_ptCursorLast </P></TD>
<TD vAlign=top width="17%">
<P align=justify>CPoint </P></TD>
<TD vAlign=top width="39%">
<P align=justify>最新鼠标位置 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="25%">
<P align=justify>m_nMsgLast </P></TD>
<TD vAlign=top width="17%">
<P align=justify>UINT </P></TD>
<TD vAlign=top width="39%">
<P align=justify>消息队列中最新接收到的消息 </P></TD></TR></TABLE>
<P align=center> </P>
<P align=center>表5-7 CWinApp的成员变量</P>
<P align=justify></P>
<TABLE cellSpacing=1 cellPadding=7 width=497 border=1>

<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>访问限制 </P></TD>
<TD vAlign=top width="23%">
<P align=justify>变量名称 </P></TD>
<TD vAlign=top width="20%">
<P align=justify>类型 </P></TD>
<TD vAlign=top width="39%">
<P align=justify>解释 </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_pszAppName </P></TD>
<TD vAlign=top width="20%">
<P align=justify>LPCTSTR </P></TD>
<TD vAlign=top width="39%">
<P align=justify>应用程序名称 </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_hInstance </P></TD>
<TD vAlign=top width="20%">
<P align=justify>HINSTANCE </P></TD>
<TD vAlign=top width="39%">
<P align=justify>标志应用程序当前实例句柄 </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_hPrevInstance </P></TD>
<TD vAlign=top width="20%">
<P align=justify>HINSTANCE </P></TD>
<TD vAlign=top width="39%">
<P align=justify>32位程序设为空 </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_lpCmdLine </P></TD>
<TD vAlign=top width="20%">
<P align=justify>LPTSTR </P></TD>
<TD vAlign=top width="39%">
<P align=justify>指向应用程序的命令行字符串 </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_nCmdShow </P></TD>
<TD vAlign=top width="20%">
<P align=justify>int </P></TD>
<TD vAlign=top width="39%">
<P align=justify>指定窗口开始的显示方式 </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_bHelpMode </P></TD>
<TD vAlign=top width="20%">
<P align=justify>BOOL </P></TD>
<TD vAlign=top width="39%">
<P align=justify>标识用户是否在上下文帮助模式 </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_pszExeName </P></TD>
<TD vAlign=top width="20%">
<P align=justify>LPCTSTR </P></TD>
<TD vAlign=top width="39%">
<P align=justify>应用程序的模块名 </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_pszHelpFilePath </P></TD>
<TD vAlign=top width="20%">
<P align=justify>LPCTSTR </P></TD>
<TD vAlign=top width="39%">
<P align=justify>应用程序的帮助文件名,缺省时同模块名 </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_pszProfileName </P></TD>
<TD vAlign=top width="20%">
<P align=justify>LPCTSTR </P></TD>
<TD vAlign=top width="39%">
<P align=justify>应用程序的INI文件名,缺省时同应用程序名 </P></TD></TR>
<TR>
<TD vAlign=top width="4%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_pszRegistryKey </P></TD>
<TD vAlign=top width="20%">
<P align=justify>LPCTSTR </P></TD>
<TD vAlign=top width="39%">
<P align=justify>Register入口,如果不指定,使用INI文件。 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_pDocManager; </P></TD>
<TD vAlign=top width="20%">
<P align=justify>CDocManager * </P></TD>
<TD vAlign=top width="39%">
<P align=justify>指向一个文档模板管理器 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_hDevMode </P></TD>
<TD vAlign=top width="20%">
<P align=justify>HGLOBAL </P></TD>
<TD vAlign=top width="39%">
<P align=justify>打印设备模式 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_hDevNames </P></TD>
<TD vAlign=top width="20%">
<P align=justify>HGLOBAL </P></TD>
<TD vAlign=top width="39%">
<P align=justify>打印设备名称 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_dwPromptContext </P></TD>
<TD vAlign=top width="20%">
<P align=justify>DWORD </P></TD>
<TD vAlign=top width="39%">
<P align=justify>被MESSAGE BOX覆盖的帮助上下文 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_nWaitCursorCount </P></TD>
<TD vAlign=top width="20%">
<P align=justify>int </P></TD>
<TD vAlign=top width="39%">
<P align=justify>等待光标计数 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_hcurWaitCursorRestore </P></TD>
<TD vAlign=top width="20%">
<P align=justify>HCURSOR </P></TD>
<TD vAlign=top width="39%">
<P align=justify>保存的光标,在等待光标之后恢复 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_pRecentFileList </P></TD>
<TD vAlign=top width="20%">
<P align=justify>指针 </P></TD>
<TD vAlign=top width="39%">
<P align=justify>指向CRecentFileList对象,最近打开的文件列表 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_atomApp </P></TD>
<TD vAlign=top width="20%">
<P align=justify>ATOM </P></TD>
<TD vAlign=top width="39%">
<P align=justify>DDE用途 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_atomSystemTopic </P></TD>
<TD vAlign=top width="20%">
<P align=justify>m_atomApp </P></TD>
<TD vAlign=top width="39%">
<P align=justify>DDE用途 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_nNumPreviewPages </P></TD>
<TD vAlign=top width="20%">
<P align=justify>UINT </P></TD>
<TD vAlign=top width="39%">
<P align=justify>缺省被打印的页面 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_nSafetyPoolSize </P></TD>
<TD vAlign=top width="20%">
<P align=justify>size_t </P></TD>
<TD vAlign=top width="39%">
<P align=justify>理想尺寸 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="23%">
<P align=justify>m_lpfnDaoTerm </P></TD>
<TD vAlign=top width="20%">
<P align=justify>函数指针 </P></TD>
<TD vAlign=top width="39%">
<P align=justify>DAO初始化设置时使用 </P></TD></TR></TABLE>
<P align=justify></P></LI></OL></LI></OL>
 楼主| 发表于 2006-12-8 10:14:05 | 显示全部楼层
<LI>
<OL>
<LI><B>CDocument的成员变量</B> </LI>
<LI></LI>
<LI>
<DIV align=center>表5-8 文档对象的属性。</DIV></LI></OL>
<TABLE cellSpacing=1 cellPadding=7 width=497 border=1>

<TR>
<TD vAlign=top width="4%">
<OL>
<LI> </LI></OL></TD>
<TD vAlign=top width="14%">
< align=justify>访问限制 </P></TD>
<TD vAlign=top width="24%">
< align=justify>变量名称 </P></TD>
<TD vAlign=top width="23%">
< align=justify>类型 </P></TD>
<TD vAlign=top width="35%">
< align=justify>解释 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
< align=justify>protected </P></TD>
<TD vAlign=top width="24%">
< align=justify>m_strTitle </P></TD>
<TD vAlign=top width="23%">
< align=justify>CString </P></TD>
<TD vAlign=top width="35%">
< align=justify>文档标题 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
< align=justify>protected </P></TD>
<TD vAlign=top width="24%">
< align=justify>m_strPathName </P></TD>
<TD vAlign=top width="23%">
<P align=justify>CString </P></TD>
<TD vAlign=top width="35%">
<P align=justify>文档路径 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="24%">
<P align=justify>m_pDocTemplate </P></TD>
<TD vAlign=top width="23%">
<P align=justify>CDocTemplate* </P></TD>
<TD vAlign=top width="35%">
<P align=justify>指向文档模板的指针 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="24%">
<P align=justify>m_viewList </P></TD>
<TD vAlign=top width="23%">
<P align=justify>CPtrList </P></TD>
<TD vAlign=top width="35%">
<P align=justify>关联的视窗口列表 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="24%">
<P align=justify>m_bModified </P></TD>
<TD vAlign=top width="23%">
<P align=justify>BOOL </P></TD>
<TD vAlign=top width="35%">
<P align=justify>文档是否有变化、需要存盘 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="24%">
<P align=justify>m_bAutoDelete </P></TD>
<TD vAlign=top width="23%">
<P align=justify>BOOL </P></TD>
<TD vAlign=top width="35%">
<P align=justify>关联视都关闭时是否删除文档对象 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="24%">
<P align=justify>m_bEmbedded </P></TD>
<TD vAlign=top width="23%">
<P align=justify>BOOL </P></TD>
<TD vAlign=top width="35%">
<P align=justify>文档是否由OLE创建 </P></TD></TR></TABLE></LI>
<OL>
<LI>
<P><B>文档模板的属性</B> </P>
<p></LI>
<LI>
<DIV align=justify>表5-9列出了文档模板的成员变量,5-10列出了单文档模板的成员变量,5-11列出了多文档模板的成员变量。单、多文档模板继承了文档模板的成员变量。</DIV></LI>
<LI>
<DIV align=center>表5-9 文档模板的数据成员</DIV></LI></OL>
<TABLE cellSpacing=1 cellPadding=7 width=497 border=1>

<TR>
<TD vAlign=top width="4%">
<OL>
<LI> </LI></OL></TD>
<TD vAlign=top width="14%">
<P align=justify>访问限制 </P></TD>
<TD vAlign=top width="28%">
<P align=justify>变量名称 </P></TD>
<TD vAlign=top width="15%">
<P align=justify>类型 </P></TD>
<TD vAlign=top width="38%">
<P align=justify>解释 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_bAutoDelete </P></TD>
<TD vAlign=top width="15%">
<P align=justify>BOOL </P></TD>
<TD vAlign=top width="38%"> </TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_pAttachedFactory </P></TD>
<TD vAlign=top width="15%">
<P align=justify>CObject * </P></TD>
<TD vAlign=top width="38%"> </TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_hMenuInPlace </P></TD>
<TD vAlign=top width="15%">
<P align=justify>HMENU </P></TD>
<TD vAlign=top width="38%">
<P align=justify>就地激活时,OLE客户程序的菜单 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_hAccelInPlace </P></TD>
<TD vAlign=top width="15%">
<P align=justify>HACCEL </P></TD>
<TD vAlign=top width="38%">
<P align=justify>就地激活时,OLE客户程序的快捷键 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_hMenuEmbedding </P></TD>
<TD vAlign=top width="15%">
<P align=justify>HMENU </P></TD>
<TD vAlign=top width="38%"> </TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_hAccelEmbedding </P></TD>
<TD vAlign=top width="15%">
<P align=justify>HACCEL </P></TD>
<TD vAlign=top width="38%"> </TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_hMenuInPlaceServer </P></TD>
<TD vAlign=top width="15%">
<P align=justify>HMENU </P></TD>
<TD vAlign=top width="38%"> </TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_hAccelInPlaceServer </P></TD>
<TD vAlign=top width="15%">
<P align=justify>HACCEL </P></TD>
<TD vAlign=top width="38%"> </TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_nIDResource </P></TD>
<TD vAlign=top width="15%">
<P align=justify>UINT </P></TD>
<TD vAlign=top width="38%">
<P align=justify>框架、菜单、快捷键等的资源ID </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_nIDServerResource </P></TD>
<TD vAlign=top width="15%">
<P align=justify>UINT </P></TD>
<TD vAlign=top width="38%"> </TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_nIDEmbeddingResource </P></TD>
<TD vAlign=top width="15%">
<P align=justify>UINT </P></TD>
<TD vAlign=top width="38%"> </TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_nIDContainerResource </P></TD>
<TD vAlign=top width="15%">
<P align=justify>UINT </P></TD>
<TD vAlign=top width="38%"> </TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_pDocClass </P></TD>
<TD vAlign=top width="15%">
<P align=justify>CRuntimeClass* </P></TD>
<TD vAlign=top width="38%">
<P align=justify>指向文档类的动态创建信息 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_pFrameClass </P></TD>
<TD vAlign=top width="15%">
<P align=justify>CRuntimeClass* </P></TD>
<TD vAlign=top width="38%">
<P align=justify>指向框架类的动态创建信息 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_pViewClass </P></TD>
<TD vAlign=top width="15%">
<P align=justify>CRuntimeClass* </P></TD>
<TD vAlign=top width="38%">
<P align=justify>指向视类的动态创建信息,由字符串m_nIDResource描述 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_pOleFrameClass </P></TD>
<TD vAlign=top width="15%">
<P align=justify>CRuntimeClass* </P></TD>
<TD vAlign=top width="38%">
<P align=justify>指向OLD框架类的动态创建信息 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_pOleViewClass </P></TD>
<TD vAlign=top width="15%">
<P align=justify>CRuntimeClass* </P></TD>
<TD vAlign=top width="38%"> </TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>public </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_strDocStrings </P></TD>
<TD vAlign=top width="15%">
<P align=justify>CString </P></TD>
<TD vAlign=top width="38%">
<P align=justify>描述该文档类型的字符串 </P></TD></TR></TABLE>
<OL>
<LI>
<DIV align=center>表5-10 单文档模板的成员变量</DIV></LI></OL>
<TABLE cellSpacing=1 cellPadding=7 width=497 border=1>

<TR>
<TD vAlign=top width="4%">
<OL>
<LI> </LI></OL></TD>
<TD vAlign=top width="14%">
<P align=justify>访问限制 </P></TD>
<TD vAlign=top width="19%">
<P align=justify>变量名称 </P></TD>
<TD vAlign=top width="17%">
<P align=justify>类型 </P></TD>
<TD vAlign=top width="45%">
<P align=justify>解释 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="19%">
<P align=justify>m_pOnlyDoc </P></TD>
<TD vAlign=top width="17%">
<P align=justify>CDocment* </P></TD>
<TD vAlign=top width="45%">
<P align=justify>指向唯一的文档对象 </P></TD></TR></TABLE>
 楼主| 发表于 2006-12-8 10:14:27 | 显示全部楼层
<OL>
<LI>
<DIV align=center>表5-11 单文档模板的成员变量</DIV></LI></OL>
<TABLE cellSpacing=1 cellPadding=7 width=497 border=1>

<TR>
<TD vAlign=top width="4%">
<OL>
<LI> </LI></OL></TD>
<TD vAlign=top width="14%">
< align=justify>访问限制 </P></TD>
<TD vAlign=top width="28%">
< align=justify>变量名称 </P></TD>
<TD vAlign=top width="15%">
< align=justify>类型 </P></TD>
<TD vAlign=top width="38%">
< align=justify>解释 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
< align=justify>public </P></TD>
<TD vAlign=top width="28%">
< align=justify>m_hMenuShared </P></TD>
<TD vAlign=top width="15%">
< align=justify>HMENU </P></TD>
<TD vAlign=top width="38%">
< align=justify>该模板的MDI子窗口的菜单 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
< align=justify>public </P></TD>
<TD vAlign=top width="28%">
< align=justify>m_hAccelTable </P></TD>
<TD vAlign=top width="15%">
<P align=justify>HACCEL </P></TD>
<TD vAlign=top width="38%">
<P align=justify>该模板的MDI子窗口的快捷键 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_docList </P></TD>
<TD vAlign=top width="15%">
<P align=justify>CPtrList </P></TD>
<TD vAlign=top width="38%">
<P align=justify>该模板的文档列表 </P></TD></TR>
<TR>
<TD vAlign=top width="4%"> </TD>
<TD vAlign=top width="14%">
<P align=justify>protected </P></TD>
<TD vAlign=top width="28%">
<P align=justify>m_nUntitledCount </P></TD>
<TD vAlign=top width="15%">
<P align=justify>UINT </P></TD>
<TD vAlign=top width="38%">
<P align=justify>用来生成文件名的数字,如”untitled0”的0。 </P></TD></TR></TABLE>
<P align=justify></P>
<OL>
<LI><B>WinMain入口函数</B> </LI>
<LI></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI><B>WinMain流程</B>
<p>
<P align=justify>现在讨论MFC应用程序如何启动。</P>
<P align=justify>WinMain函数是MFC提供的应用程序入口。进入WinMain前,全局应用程序对象已经生成。WinMain流程如图5-3所示。图中,灰色框是对被调用的虚拟函数的注释,程序员可以或必须覆盖它以实现MFC要求的或用户希望的功能;大括号所包含的图示是相应函数流程的细化,有应用程序对象App的初始化、Run函数的实现、PumpMessage的流程,等等。</P>
<P align=justify><IMG src="http://www.vczx.com/tutorial/mfc/image120.gif"></P>
<P align=justify>从图中可以看出:</P>
<P align=justify>(1)一些虚拟函数被调用的时机</P>
<P align=justify>对应用程序类(线程类)的InitIntance、ExitInstance、Run、ProcessMessageFilter、OnIdle、PreTranslateMessage来说,InitInstance在应用程序初始化时调用,ExitInstance在程序退出时调用,Run在程序初始化之后调用导致程序进入消息循环,ProcessMessageFilter、OnIdle、PreTranslateMessage在消息循环时被调用,分别用来过滤消息、进行Idle处理、让窗口预处理消息。</P>
<P align=justify>(2)应用程序对象的角色</P>
<P align=justify>首先,应用程序对象的成员函数InitInstance被WinMain调用。对程序员来说,它就是程序的入口点(真正的入口点是WinMain,但MFC向程序员隐藏了WinMain的存在)。由于MFC没有提供InitInstance的缺省实现,用户必须自己实现它。稍后将讨论该函数的实现。</P>
<P align=justify>其次,通过应用程序对象的Run函数,程序进入消息循环。实际上,消息循环的实现是通过CWinThread::Run来实现的,图中所示的是CWinThread::Run的实现,因为CWinApp没有覆盖Run的实现,程序员的应用程序类一般也不用覆盖该函数。</P>
<P align=justify>(3)Run所实现的消息循环</P>
<P align=justify>它调用PumpMessage来实现消息循环,如果没消息,则进行空闲(Idle)处理。如果是WM_QUIT消息,则调用ExitInstance后退出消息循环。</P>
<P align=justify>(4)CWinThread:umpMessage</P>
<P align=justify>该函数在MFC函数文档里没有描述,但是MFC建议用户使用。它实现获取消息,转换(Translate)消息,发送消息的消息循环。在转换消息之前,调用虚拟函数PreTranslateMessage对消息进行预处理,该函数得到消息目的窗口对象之后,使用CWnd的WalkPreTranslateTree让目的窗口及其所有父窗口得到一个预处理当前消息的机会。关于消息预处理,见消息映射的有关章节。如果是WM_QUIT消息,PumpMessage返回FALSE;否则返回TRUE。</P>
<P align=justify></P></LI></OL>
 楼主| 发表于 2006-12-8 10:14:49 | 显示全部楼层
<OL>
<LI>
<><B>MFC空闲处理</B> </P>
<p></LI></OL>
< align=justify>MFC实现了一个Idle处理机制,就是在没有消息可以处理时,进行Idle处理。Idle处理的一个应用是更新用户接口对象的状态。更新用户接口状态的内容见消息映射的章节。</P>
<OL>
< align=justify>
<LI>空闲处理由函数OnIdle完成,其原型为BOOL OnIdle(int)。参数的含义是当前空闲处理周期已经完成了多少次OnIdle调用,每个空闲处理周期的第一次调用,该参数设为0,每调用一次加1;返回值表示当前空闲处理周期是否继续调用OnIdle。
<p>
< align=justify></P>
<LI>MFC的缺省实现里,CWinThread::OnIdle完成了工具栏等的状态更新。如果覆盖OnIdle,需要调用基类的实现。
<p>
< align=justify></P>
<LI>在处理完一个消息或进入消息循环时,如果消息队列中没有消息要处理,则MFC开始一个新的空闲处理周期;
<p>
< align=justify></P>
<LI>当OnIdle返回FASLE,或者消息队列中有消息要处理时,当前的空闲处理周期结束。
<p></LI></OL>
<DIR>
< align=justify>从图5-3中Run的流程上可以清楚的看到MFC空闲处理的情况。</P>
< align=justify></P></DIR>
< align=justify>本节描述了应用程序从InitInstance开始初始化、从Run进入消息循环的过程,下面将就SDI应用程序的例子描述该过程中创建各个所需MFC对象的流程。</P>
<OL>
<OL>
<OL>
< align=justify>
<LI><B>SDI应用程序的对象创建</B>
<p>
<P align=justify>如前一节所述,程序从InitInstance开始。在SDI应用程序的InitInstance里,至少有以下语句:</P>
<P align=justify>//第一部分,创建文档模板对象并把它添加到应用程序的模板链表</P>
<P align=justify>CSingleDocTemplate* pDocTemplate;</P>
<P align=justify>pDocTemplate = new CSingleDocTemplate(</P>
<P align=justify>IDR_MAINFRAME,</P>
<P align=justify>RUNTIME_CLASS(CTDoc),</P>
<P align=justify>RUNTIME_CLASS(CMainFrame), // main SDI frame window</P>
<P align=justify>RUNTIME_CLASS(CTView));</P>
<P align=justify>AddDocTemplate(pDocTemplate);</P>
<P align=justify></P>
<P align=justify>//第二部分,动态创建文档、视、边框窗口等MFC对象和对应的Windows对象</P>
<P align=justify>//Parse command line for standard shell commands, DDE, file open</P>
<P align=justify>CCommandLineInfo cmdInfo;</P>
<P align=justify>ParseCommandLine(cmdInfo);</P>
<P align=justify>// Dispatch commands specified on the command line</P>
<P align=justify>if (!ProcessShellCommand(cmdInfo))</P>
<P align=justify>return FALSE;</P>
<P align=justify></P>
<P align=justify>//第三部分,返回TRUE,WinMain下一步调用Run开始消息循环,</P>
<P align=justify>//否则,终止程序</P>
<P align=justify>return TRUE;</P>
<P align=justify></P>
<P align=justify>对于第二部分,又可以分解成许多步骤。</P>
<P align=justify>下面将解释每一步。</P>
<OL>
<P align=justify>
<LI><B>文档模板的创建</B>
<p>
<P align=justify>第一步是创建文档模板。</P>
<P align=justify>文档模板的作用是动态创建其他MFC对象,它保存了要动态创建类的动态创建信息和该文档类型的资源ID。这些信息保存在文档模板的成员变量里:m_nIDResource(资源ID)、m_pDocClass(文档类动态创建信息)、m_pFrameClass(边框窗口类动态创建信息)、m_pViewClass(视类动态创建信息)。</P>
<P align=justify>资源ID包括菜单、像标、快捷键、字符串资源的ID,它们都使用同一个ID值,如IDR_MAINFRAME。其中,字符串资源描述了文档类型,由七个被“\n”分隔的子字符串组成,各个子串可以通过CDocTemplate的成员函数GetDocString(CString&amp; rString, enum DocStringIndex index)来获取。DocStringIndex是CDocTemplate类定义的枚举变量以区分七个子串,描述如下(英文是枚举变量名称)。</P>
<P align=justify><B>WindowTitle </B>应用程序窗口的标题。仅仅对SDI程序指定。</P>
<P align=justify><B>DocName </B>用来构造缺省文档名的字符串。当用File菜单的菜单项new创建新文档时,缺省文档名由该字符串加一个数字构成。如果空,使用“unitled”。</P>
<P align=justify><B>FileNewName</B> 文档类型的名称,在打开File New对话框时显示。</P>
<P align=justify><B>FilterName </B>匹配过滤字符串,在File Open对话框用来过滤要显示的文件。如果不指定,File Open对话框的文件类型(file style)不可访问。</P>
<P align=justify><B>FilterExt </B>该类型文档的扩展名。如果不指定,则不可访问对话框的文件类型(File Style)。</P>
<P align=justify><B>RegFileTypeId </B>文档类型在Windows 注册库中的存储标识。</P>
<P align=justify><B>RegFileTypeName </B>文档类型在Windows 注册库中的类型名称。</P>
<P align=justify></P>
<P align=justify>文档模板被应用程序对象创建和管理。应用程序类CWinApp有一个CDocManager类型的成员变量m_pDocManager,通过该变量来管理应用程序的文档模板列表,把一些相关的操作委派给CDocManager对象处理。</P>
<P align=justify>CDocManager使用CPtrList类型的m_templateList变量来存储文档模板,并提供了操作文档模板列表的系列函数。</P>
<P align=justify></P>
<P align=justify>从语句pDocTemplate = new CSingleDocTemplate(…)可以看出应用程序对象创建模板时传递一个资源ID和三个类的动态创建信息给它:</P>
<P align=justify>IDR_MAINFRAME,资源ID</P>
<P align=justify>RUNTIME_CLASS(CTDoc),文档类动态创建信息</P>
<P align=justify>RUNTIME_CLASS(CMainFrame),边框窗口类动态创建信息</P>
<P align=justify>RUNTIME_CLASS(CTView),视类动态创建信息</P>
<P align=justify>文档模板对象接收这些信息并把它们保存到对应的成员变量里头。然后AddDocTemplate实际调用m_pDocManager-&gt;AddDocTemplate,把创建的模板对象加入到文档模板管理器的模板列表中,也就是应用程序对象的文档模板列表中。</P>
<P align=justify></P>
<LI><B>文件的创建或者打开</B>
<p></LI></OL></LI></OL></OL></OL>
<P align=justify>第二步是创建或者打开文件。</P>
<P align=justify>对于SDI程序,MFC对象的动态创建过程是在创建或者打开文件中发生的。但是为什么没有看到文件操作相关的语句呢?</P>
<OL>
<P align=justify>
<LI>CCommandLineInfo
<p>
<P align=justify>首先,需要弄清楚类CcommandLineInfo,它是用来处理命令行信息的类,CWinApp:areCommandLine调用CCommandLineInfo的成员函数ParseParm分析启动程序时的参数,把分析结果保存在CCommandLineInfo对象的成员变量里。CCommandLineInfo的定义如下:</P>
<P align=justify>class CCommandLineInfo : public CObject</P>
<P align=justify>{</P>
<P align=justify>BOOL m_bShowSplash;</P>
<P align=justify>BOOL m_bRunEmbedded;</P>
<P align=justify>BOOL m_bRunAutomated;</P>
<P align=justify></P>
<P align=justify>enum { FileNew, FileOpen, FilePrint, FilePrintTo, FileDDE,</P>
<P align=justify>AppUnregister, FileNothing = -1 } m_nShellCommand;</P>
<P align=justify>// not valid for FileNew</P>
<P align=justify>CString m_strFileName;</P>
<P align=justify>// valid only for FilePrintTo</P>
<P align=justify>CString m_strPrinterName;</P>
<P align=justify>CString m_strDriverName;</P>
<P align=justify>CString m_strPortName;</P>
<P align=justify>};</P>
<P align=justify>由上述定义可以看出,分析结果分几类:是否OLE激活;应该执行什么动作(FileNew、FileOpen等);传递的参数(打开或打印的文件名,打印设备、端口等)。</P>
<P align=justify>当命令行空时,执行FileNew命令。原因在于CCommandLineInfo的缺省构造函数:</P>
<P align=justify>CCommandLineInfo::CCommandLineInfo()</P>
<P align=justify>{</P>
<P align=justify>m_bShowSplash = TRUE;</P>
<P align=justify>m_bRunEmbedded = FALSE;</P>
<P align=justify>m_bRunAutomated = FALSE;</P>
<P align=justify>m_nShellCommand = FileNew;//指定了SHELL命令操作</P>
<P align=justify>}</P>
<P align=justify>缺省构造把应该执行的动作指定为FileNew。</P>
<P align=justify></P>
<LI>处理命令行命令
<p>
<P align=justify>其次,分析 CWinApp:rocessShellCommand(CCommandLineInfo&amp; rCmdInfo)的流程,它处理命令行的命令,流程如图5-3所示。</P><IMG src="http://www.vczx.com/tutorial/mfc/image121.gif" align=left>
<P align=justify>图5-4第三层表示根据命令类型进一步调用的函数,都是CWinApp或者派生类的成员函数。对于FILEDDE类型没有进一步的调用。</P>
<P align=justify>命令类型是FILENEW时,调用的函数就是标准命令ID_FILE_NEW对应的处理函数OnFileNew;命令类型是FILEOPEN时调用的函数是OpenDocumentFile,标准命令ID_FILE_OPEN的处理函数OnFileOpen的工作实际上就是由OpenDocumentFile完成的。函数FileNew、OpenDocumentFile导致了窗口、文档的创建。</P>
<P align=justify></P>
<LI>OnFileNew
<p>
<P align=justify>接着,分析 CWinApp::OnFileNew流程,如图5-5所示。</P><IMG src="http://www.vczx.com/tutorial/mfc/image122.gif" align=left>
<P align=justify>图5-5的说明:</P>
<P align=justify>应用程序对象得到文档模板管理器指针,调用文档模板管理器的成员函数OnFileNew(m_pDocManager-&gt;OnFileNew());模板管理器获取文档模板对象指针,调用文档模板对象的OpenDocumentFile 函数(pTemplate-&gt;OpenDocumentFile(NULL))。如果模板管理器发现有多个文档模板,就弹出一个对话框让用户选择文档模板。这里和后面的图解中类似于CWinApp::、CDocManager::、CDocTemplate::等的函数类属限制并不表示直接源码中有这样的限制,而是通过指针或者指针的动态约束可以认定调用了某个类的成员函数,其正确性仅仅限于本书图解的MFC的缺省实现。</P>
<P align=justify>如图5-5所示,程序员可以覆盖有关虚拟函数或命令处理函数:如果程序员在自己的应用程序类中覆盖了OnFileNew,则可以实现完全不同的处理流程;一般情况下,不会从文档模板类派生新类,如果派生的话,可以覆盖CDocTemplate的虚拟函数。</P>
<P align=justify></P></LI></OL>
 楼主| 发表于 2006-12-8 10:34:47 | 显示全部楼层
<OL>
<LI>
<><B>控制条的窗口创建</B> </P>
<p></LI></OL>
< align=justify>CControlBar派生类实现了自己的窗口创建函数Create,CControlBar的PreCreateWindow被派生类的Create函数直接或者间接地调用。以CToolBar为例讨论窗口创建函数和创建过程。</P>
<OL>
< align=justify>
<LI>CToolBar的窗口创建函数Create
<p>
< align=justify>Create函数实现如下:</P>
< align=justify>BOOL CToolBar::Create(CWnd* pParentWnd, DWORD dwStyle, UINT nID)</P>
< align=justify>{</P>
< align=justify>ASSERT_VALID(pParentWnd); // must have a parent</P>
< align=justify>ASSERT (!((dwStyle &amp; CBRS_SIZE_FIXED) &amp;&amp;</P>
< align=justify>(dwStyle &amp; CBRS_SIZE_DYNAMIC)));</P>
< align=justify></P>
<P align=justify>// 保存dwStyle指定的CBRS_风格</P>
<P align=justify>m_dwStyle = dwStyle;</P>
<P align=justify>if (nID == AFX_IDW_TOOLBAR)</P>
<P align=justify>m_dwStyle |= CBRS_HIDE_INPLACE;</P>
<P align=justify></P>
<P align=justify>//去掉参数dwStyle包含的CBRS_风格</P>
<P align=justify>dwStyle &amp;= ~CBRS_ALL;</P>
<P align=justify>//设置窗口风格</P>
<P align=justify>dwStyle |= </P>
<P align=justify>CCS_NOPARENTALIGN|CCS_NOMOVEY|CCS_NODIVIDER|CCS_NORESIZE;</P>
<P align=justify></P>
<P align=justify>//初始化通用控制,可以导致InitCommonControl的调用</P>
<P align=justify>VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG));</P>
<P align=justify></P>
<P align=justify>//创建窗口,将调用PreCreateWindow,OnCreate, OnNcCreate等</P>
<P align=justify>CRect rect; rect.SetRectEmpty();</P>
<P align=justify>if (!CWnd::Create(TOOLBARCLASSNAME, NULL, dwStyle, </P>
<P align=justify>rect, pParentWnd, nID))</P>
<P align=justify>return FALSE;</P>
<P align=justify></P>
<P align=justify>// Note: Parent must resize itself for control bar to be resized</P>
<P align=justify></P>
<P align=justify>return TRUE;</P>
<P align=justify>}</P>
<P align=justify>其中:</P>
<P align=justify>Create函数的参数1表示工具条的父窗口。参数2指定窗口风格和CBRS_风格,缺省值为 WS_CHILD | WS_VISIBLE | CBRS_TOP,其中WS_CHILD和WS_VISIBLE是窗口风格,CBRS_TOP是CBRS_风格。参数3指定工具条ID,缺省值为AFX_IDW_TOOLBAR(0X0E800或者59392)。如果还有多个工具栏要显示,在创建它们时则必须给每个工具栏指明ID。</P>
<P align=justify>首先,Create函数把参数2(dwStyle)指定的窗口风格和CBRS_风格分离出来,窗口风格保留在dwStyle中,CBRS_风格保存到成员变量m_dwStyle中。CToolBar:reCreateWindow将进一步修改这些风格。</P>
<P align=justify>接着,Create函数调用了函数AfxDeferRegisterClass。它如果没有注册TOOLBARCLASSNAME表示的“窗口类”,就注册该类;否则,返回TRUE,表示已经注册。TOOLBARCLASSNAME表示的字符串是“ToolbarWindow32”,即“窗口类”名称。</P>
<P align=justify>然后,调用CWnd::Create(7个参数)使用“ToolbarWindow32”“窗口类”创建工具栏。</P>
<P align=justify>Create在创建窗口的过程中,用MFC的标准窗口过程取代原来的窗口过程,如同CFormView和CDialog窗口创建时窗口过程被取代一样,并发送WM_CREATE和WM_NCCREATE消息。</P>
<P align=justify>至于添加向工具栏添加按钮,则由函数LoadToolBar完成。在分析LoadToolBar函数之前,先讨论OnCreate、OnNcCreate等函数。</P>
<P align=justify></P>
<LI>处理WM_CREATE消息
<p>
<P align=justify>CControlBar提供了消息处理函数OnCreate来处理WM_CREATE消息。</P>
<P align=justify>int CControlBar::OnCreate(LPCREATESTRUCT lpcs)</P>
<P align=justify>{</P>
<P align=justify>//调用基类的实现</P>
<P align=justify>if (CWnd::OnCreate(lpcs) == -1)</P>
<P align=justify>return -1;</P>
<P align=justify>//针对工具栏,是否有Tooltip特性</P>
<P align=justify>if (m_dwStyle &amp; CBRS_TOOLTIPS)</P>
<P align=justify>EnableToolTips();</P>
<P align=justify>//得到父窗口,并添加自身到其控制条列表中</P>
<P align=justify>CFrameWnd *pFrameWnd = (CFrameWnd*)GetParent();</P>
<P align=justify>if (pFrameWnd-&gt;IsFrameWnd())</P>
<P align=justify>{</P>
<P align=justify>m_pDockSite = pFrameWnd;</P>
<P align=justify>m_pDockSite-&gt;AddControlBar(this);</P>
<P align=justify>}</P>
<P align=justify>return 0;</P>
<P align=justify>}</P>
<P align=justify>如果需要支持Tooltips,则OnCreate调用EnableTooltips。</P>
<P align=justify>m_pDockSite是CControlBar的和泊位相关的成员变量,这里把它初始化为拥有工具栏的父边框窗口,该边框窗口把控制条加入其控制条列表m_listControlBars中。</P>
<P align=justify>在处理WM_CREATE之前,派生类先处理消息WM_NCCREAE。例如,CToolBar覆盖了OnNcCreate函数。</P>
<P align=justify></P>
<LI>处理WM_NCCREATE消息
<p>
<P align=justify>CToolBar对WM_NCCREATE消息的处理如下:</P>
<P align=justify>BOOL CToolBar::OnNcCreate(LPCREATESTRUCT lpCreateStruct)</P>
<P align=justify>{</P>
<P align=justify>if (!CControlBar::OnNcCreate(lpCreateStruct))</P>
<P align=justify>return FALSE;</P>
<P align=justify>// if the owner was set before the toolbar was created, set it now</P>
<P align=justify>if (m_hWndOwner != NULL)</P>
<P align=justify>DefWindowProc(TB_SETPARENT, (WPARAM)m_hWndOwner, 0);</P>
<P align=justify></P>
<P align=justify>DefWindowProc(TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);</P>
<P align=justify>return TRUE;</P>
<P align=justify>}</P>
<P align=justify>CToolBar覆盖CcontrolBar的该函数用来设置工具条的所属窗口和描述工具条按钮结构的大小,这两个动作都是通过给工具条窗口发送消息来实现的。因为这些消息被送给控制窗口类的窗口过程(Windows提供的)来处理,所以直接调用DefWindowProc,省却了消息发送的过程。</P>
<P align=justify>在控制窗口创建之后,对于工具条来说,下一步就是向工具栏添加按钮。</P>
<P align=justify></P>
<LI>向工具栏添加按钮
<p>
<P align=justify>通过函数LoadToolBar完成向工具栏添加按钮的任务,其实现如下:</P>
<P align=justify>BOOL CToolBar:oadToolBar(LPCTSTR lpszResourceName)</P>
<P align=justify>{</P>
<P align=justify>ASSERT_VALID(this);</P>
<P align=justify>ASSERT(lpszResourceName != NULL);</P>
<P align=justify></P>
<P align=justify>//查找并确认按钮位图、字符串等资源的位置</P>
<P align=justify>HINSTANCE hInst = AfxFindResourceHandle(lpszResourceName, RT_TOOLBAR);</P>
<P align=justify>HRSRC hRsrc = ::FindResource(hInst, lpszResourceName, RT_TOOLBAR);</P>
<P align=justify>if (hRsrc == NULL)</P>
<P align=justify>return FALSE;</P>
<P align=justify></P>
<P align=justify>//锁定资源</P>
<P align=justify>HGLOBAL hGlobal = LoadResource(hInst, hRsrc);</P>
<P align=justify>if (hGlobal == NULL)</P>
<P align=justify>return FALSE;</P>
<P align=justify></P>
<P align=justify>CToolBarData* pData = (CToolBarData*)LockResource(hGlobal);</P>
<P align=justify>if (pData == NULL)</P>
<P align=justify>return FALSE;</P>
<P align=justify>ASSERT(pData-&gt;wVersion == 1);</P>
<P align=justify></P>
<P align=justify>//复制与各个位图对应的命令ID到数组pItem</P>
<P align=justify>UINT* pItems = new UINT[pData-&gt;wItemCount];</P>
<P align=justify>for (int i = 0; i &lt; pData-&gt;wItemCount; i++)</P>
<P align=justify>pItems = pData-&gt;items();</P>
<P align=justify>//添加按钮到工具栏,指定各个按钮对应的ID</P>
<P align=justify>BOOL bResult = SetButtons(pItems, pData-&gt;wItemCount);</P>
<P align=justify>delete[] pItems;</P>
<P align=justify></P>
<P align=justify>//设置按钮的位图</P>
<P align=justify>if (bResult)</P>
<P align=justify>{</P>
<P align=justify>// set new sizes of the buttons</P>
<P align=justify>CSize sizeimage(pData-&gt;wWidth, pData-&gt;wHeight);</P>
<P align=justify>CSize sizeButton(pData-&gt;wWidth + 7, pData-&gt;wHeight + 7);</P>
<P align=justify>SetSizes(sizeButton, sizeimage);</P>
<P align=justify></P>
<P align=justify>// load bitmap now that sizes are known by the toolbar control</P>
<P align=justify>bResult = LoadBitmap(lpszResourceName);</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>UnlockResource(hGlobal);</P>
<P align=justify>FreeResource(hGlobal);</P>
<P align=justify></P>
<P align=justify>return bResult;</P>
<P align=justify>}</P>
<P align=justify>LoadToolBar函数的参数指定了资源。ToolBar资源的类型是RT_TOOLBAR,ToolBar位图资源的类型是RT_BITMAP。</P>
<P align=justify>在RT_TOOLBAR类型的资源读入内存之后,可以用CToolBarData结构描述。一个这样的结构包括了ToolBar资源的如下信息:</P>
<P align=justify>工具条位图的版本,宽度,高度,个数,各个位图对应的命令ID。</P>
<P align=justify>然后,LoadToolBar把这些命令ID被复制到数组pItem中;根据位图宽度、高度形成按钮尺寸sizeButton和位图尺寸sizeimage。</P>
<P align=justify>接着,调用SetBottons添加按钮到工具栏,把各个按钮和命令ID对应起来;调用SetSizes设置按钮和位图的尺寸大小;调用LoadBitmap添加或者取代工具条的位图列表。这些动作都是调用工具栏“窗口类”的窗口过程完成的。例如,SetButtons的实现:</P>
<P align=justify>BOOL CToolBar::SetButtons(const UINT* lpIDArray, int nIDCount)</P>
<P align=justify>{</P>
<P align=justify>ASSERT_VALID(this);</P>
<P align=justify>ASSERT(nIDCount &gt;= 1); // must be at least one of them</P>
<P align=justify>ASSERT(lpIDArray == NULL ||</P>
<P align=justify>AfxIsValidAddress(lpIDArray, sizeof(UINT) * nIDCount, FALSE));</P>
<P align=justify></P>
<P align=justify>//首先,删除工具条中现有的按钮</P>
<P align=justify>int nCount = (int)DefWindowProc(TB_BUTTONCOUNT, 0, 0);</P>
<P align=justify>while (nCount--)</P>
<P align=justify>VERIFY(DefWindowProc(TB_DELETEBUTTON, 0, 0));</P>
<P align=justify></P>
<P align=justify>if (lpIDArray != NULL)//命令ID数组非空</P>
<P align=justify>{</P>
<P align=justify>//添加新按钮</P>
<P align=justify>TBBUTTON button; memset(&amp;button, 0, sizeof(TBBUTTON));</P>
<P align=justify>int iimage = 0;</P>
<P align=justify>for (int i = 0; i &lt; nIDCount; i++)</P>
<P align=justify>{</P>
<P align=justify>button.fsState = TBSTATE_ENABLED;</P>
<P align=justify>if ((button.idCommand = *lpIDArray++) == 0)</P>
<P align=justify>{</P>
<P align=justify>//按钮之间分隔</P>
<P align=justify>button.fsStyle = TBSTYLE_SEP;</P>
<P align=justify>//按钮之间隔8个像素</P>
<P align=justify>button.iBitmap = 8;</P>
<P align=justify>}</P>
<P align=justify>else</P>
<P align=justify>{</P>
<P align=justify>//有位图和命令ID的按钮</P>
<P align=justify>button.fsStyle = TBSTYLE_BUTTON;</P>
<P align=justify>button.iBitmap = iimage++;//设置位图索引</P>
<P align=justify>}</P>
<P align=justify>//添加按钮</P>
<P align=justify>if (!DefWindowProc(TB_ADDBUTTONS, 1, (LPARAM)&amp;button))</P>
<P align=justify>return FALSE;</P>
<P align=justify>}</P>
<P align=justify>}</P>
<P align=justify>else//命令ID数组空,添加空按钮</P>
<P align=justify>{</P>
<P align=justify>TBBUTTON button; memset(&amp;button, 0, sizeof(TBBUTTON));</P>
<P align=justify>button.fsState = TBSTATE_ENABLED;</P>
<P align=justify>for (int i = 0; i &lt; nIDCount; i++)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(button.fsStyle == TBSTYLE_BUTTON);</P>
<P align=justify>if (!DefWindowProc(TB_ADDBUTTONS, 1, (LPARAM)&amp;button))</P>
<P align=justify>return FALSE;</P>
<P align=justify>}</P>
<P align=justify>}</P>
<P align=justify>//记录按钮个数到成员变量m_nCount中</P>
<P align=justify>m_nCount = (int)DefWindowProc(TB_BUTTONCOUNT, 0, 0);</P>
<P align=justify></P>
<P align=justify>//稍后放置按钮</P>
<P align=justify>m_bDelayedButtonLayout = TRUE;</P>
<P align=justify></P>
<P align=justify>return TRUE;</P>
<P align=justify>}</P>
<P align=justify>函数的参数1是一个数组,数组的各个元素就是命令ID;参数2是按钮的个数。首先,SetButtons删除工具条原来的按钮;然后,添加新的按钮,若命令ID数组非空,则把每一个按钮和命令ID对应并分配位图索引,否则设置空按钮并返回FALSE;最后,记录按钮个数。</P>
<P align=justify>从SetButtons的实现可以看出,对工具条的所有操作都是通过工具条“窗口类”的窗口过程完成的,SetSizes、LoadBitmap也是如此,这里不作讨论。</P>
<P align=justify></P></LI></OL>
 楼主| 发表于 2006-12-8 10:35:07 | 显示全部楼层
<OL>
<LI>
<>状态栏和对话框工具栏的创建 </P>
<p></LI></OL>
< align=justify>至此,分析了MFC创建工具条窗口的过程。对于状态栏和对话框工具栏有类似的步骤,但也有不同之处。</P>
< align=justify>CStatusBar的Create使用“msctls_statusbar32”“窗口类”创建状态栏,窗口ID为AFX_IDW_STATUS_BAR(0XE801),然后通过成员函数SetIndictors给状态栏分格,类似于给工具条添加按钮的过程,它实际上是通过状态栏“窗口类”的窗口过程完成的。</P>
< align=justify>CDialogBar的Create使用CreateDlg创建对话框工具栏,类似于CFormView的过程。在工具栏窗口创建之后,要添加到父窗口的工具栏列表中,这通过CControlBar::OnCreate完成。这样创建的结果导致窗口过程使用MFC的统一的窗口过程,相应“窗口类”的窗口过程也将在缺省处理中被调用,这一点如同CFormView和CDialog中所描述的。在初始化对话框的时候完成了各个控制按钮的添加。</P>
< align=justify>CStatusBar和CdialogBar都没有处理消息WM_NCCREATE。</P>
< align=justify>关于CStautsBar和CDialogBar创建过程的具体实现,这里不作详细讨论了。</P>
<OL>
<OL>
<OL>
< align=justify>
<LI><B>控制条的销毁</B>
<p>
< align=justify>描述了控制条的创建,顺便考察其销毁的设计。</P>
< align=justify>工具条、状态栏等这些控制窗口都要使用DestroyWindow来销毁,所有有关操作集中由CControlBar处理。CControlBar覆盖了虚拟函数DestroyWindow、PostNcDestroy和消息处理函数OnDestroy。</P>
< align=justify>当然,各个派生类的虚拟析构函数被实现。如果成员变量m_bAutoDelete为TRUE,则动态创建的MFC窗口将自动销毁。</P>
<P align=justify></P>
<LI><B>处理控制条的位置</B>
<p>
<OL>
<P align=justify>
<LI><B>计算控制条位置的过程和算法</B>
<p>
<P align=justify>工具条等控制条是作为一个子窗口在父边框窗口内显示的。为了处理控制条的布置(Layout),首先需要计算出控制条的尺寸大小,这个工作被委派给工具条等控制窗口自己来完成。为此,CControlBar提供了两个函数来达到这个目的:CalcFixLayout,CalcDynamicLayout。这两个函数都是虚拟函数。各个派生类都覆盖了这两个或者其中一个函数,用来计算自身的尺寸大小。这些计算比较琐碎,在此不作详细讨论。其次,在父窗口位置或者大小变化时,控制条的大小和位置要作相应的调整。</P>
<P align=justify>下面,描述MFC确定或者更新工具条、状态栏等位置的步骤:</P>
<P align=justify>(1)边框窗口在必要的时候调用虚拟函数RecalcLayout来重新放置它的控制条和客户窗口,例如在创建窗口时、响应消息WM_SIZE时(见5.3.3.5节)边框窗口的初始化)。</P>
<P align=justify>(2)CFrameWnd::RecalcLayout调用CWnd的成员函数RepositionBars完成控制条窗口的重新放置。</P>
<P align=justify>(3)CWnd::RepositionBars作如下的处理:</P>
<P align=justify>RepositionBars首先给各个控制子窗口发送(Send)MFC内部使用的消息WM_SIZEPARENT,把窗口客户区矩形指针传递给它们,给它们一个机会来确认自己的尺寸。</P>
<P align=justify>然后,各个控制子窗口用OnSizeParent响应WM_SIZEPARENT消息;ControlBar实现了消息处理函数OnSizeParent,它调用CalcDynamicLayout等函数确定本窗口的大小,并从客户区矩形中减去自己的尺寸。</P>
<P align=justify>在所有的控制子窗口处理了OnSizeParent消息之后,RepositonBars利用返回的信息调用函数CalcWindowRect计算客户区窗口(MDI客户窗口、View等)的大小。</P>
<P align=justify>最后,调用::EndDeferWindowPos或者::SetWindowPos放置所有的窗口(控制子窗口和客户窗口)。</P>
<P align=justify>在窗口被放置的时候,发送消息WM_WINDOWPOSCHANGING和WM_WINDOWPOSCHANGED。MFC的实现中,控制窗口响应了前一个消息,消息处理函数是OnWindowPosChanging。CControlBar、CToolBar和CStatusBar等实现了消息处理函数OnWindowPosChanging。</P>
<P align=justify></P>
<P align=justify>上述处理过程所涉及的这些函数中,RecalcLayout是CFrameWnd定义的虚拟函数;RepostionBars是CWnd的成员函数;CalcaWindowRect是CWnd的虚拟函数;OnSizeParent是CControlBar定义的消息处理函数;OnWindowPosChanging是CToolbar、CStatusBar、CDockBar等CControlBar派生类定义的消息处理函数。</P>
<P align=justify>下面,对其中两个函数RecalcLayout和RepositionBars作一些分析。</P>
<P align=justify></P>
<LI><B>CFrameWnd的虚拟函数RecalcLayout</B>
<p>
<P align=justify>RecalcLayout的实现如下:</P>
<P align=justify>void CFrameWnd::RecalcLayout(BOOL bNotify)</P>
<P align=justify>{</P>
<P align=justify>//RecalcLayout是否正在被调用</P>
<P align=justify>if (m_bInRecalcLayout)</P>
<P align=justify>return;</P>
<P align=justify></P>
<P align=justify>m_bInRecalcLayout = TRUE;</P>
<P align=justify>// clear idle flags for recalc layout if called elsewhere</P>
<P align=justify>if (m_nIdleFlags &amp; idleNotify)</P>
<P align=justify>bNotify = TRUE;</P>
<P align=justify>m_nIdleFlags &amp;= ~(idleLayout|idleNotify);</P>
<P align=justify></P>
<P align=justify>//与OLE相关的处理</P>
<P align=justify>#ifndef _AFX_NO_OLE_SUPPORT</P>
<P align=justify>// call the layout hook -- OLE support uses this hook</P>
<P align=justify>if (bNotify &amp;&amp; m_pNotifyHook != NULL)</P>
<P align=justify>m_pNotifyHook-&gt;OnRecalcLayout();</P>
<P align=justify>#endif</P>
<P align=justify></P>
<P align=justify>//是否包含浮动(floating)控制条的边框窗口(CMiniFrameWnd类)</P>
<P align=justify>if (GetStyle() &amp; FWS_SNAPTOBARS)</P>
<P align=justify>{</P>
<P align=justify>//计算控制条和边框窗口的位置、尺寸并设置它们的位置</P>
<P align=justify>CRect rect(0, 0, 32767, 32767);</P>
<P align=justify>RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposQuery,</P>
<P align=justify>&amp;rect, &amp;rect, FALSE);</P>
<P align=justify>RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposExtra,</P>
<P align=justify>&amp;m_rectBorder, &amp;rect, TRUE);</P>
<P align=justify>CalcWindowRect(&amp;rect);</P>
<P align=justify>SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(),</P>
<P align=justify>SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER);</P>
<P align=justify>}</P>
<P align=justify>else</P>
<P align=justify>//是普通边框窗口,则设置其所有子窗口的位置、尺寸</P>
<P align=justify>RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST,</P>
<P align=justify>reposExtra, &amp;m_rectBorder);</P>
<P align=justify></P>
<P align=justify>//本函数处理完毕</P>
<P align=justify>m_bInRecalcLayout = FALSE;</P>
<P align=justify>}</P>
<P align=justify>该函数主要的目的是调用RepositionBars函数,它分两种情况来调用RepositionBars函数。一种情况是当前边框窗口为浮动控制条的包容窗口(微型边框窗口)时;另一种情况是当前边框窗口为普通边框窗口时。</P>
<P align=justify></P>
<LI><B>CWnd的成员函数RepositionBars</B>
<p></LI></OL>
<P align=justify>RepositionBars的实现如下:</P>
<P align=justify>void CWnd::RepositionBars(UINT nIDFirst, UINT nIDLast, UINT nIDLeftOver,</P>
<P align=justify>UINT nFlags, LPRECT lpRectParam, LPCRECT lpRectClient, BOOL bStretch)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(nFlags == 0 || nFlags == reposQuery || nFlags == reposExtra);</P>
<P align=justify></P>
<P align=justify>AFX_SIZEPARENTPARAMS layout;</P>
<P align=justify>HWND hWndLeftOver = NULL;</P>
<P align=justify></P>
<P align=justify>layout.bStretch = bStretch;</P>
<P align=justify>layout.sizeTotal.cx = layout.sizeTotal.cy = 0;</P>
<P align=justify>if (lpRectClient != NULL)</P>
<P align=justify>layout.rect = *lpRectClient; //从参数6得到客户区</P>
<P align=justify>else</P>
<P align=justify>//参数lpRectClient空,得到客户区域</P>
<P align=justify>GetClientRect(&amp;layout.rect);</P>
<P align=justify></P>
<P align=justify>if (nFlags != reposQuery)</P>
<P align=justify>//准备放置各个子窗口(layout)</P>
<P align=justify>layout.hDWP = ::BeginDeferWindowPos(8); // reasonable guess</P>
<P align=justify>else</P>
<P align=justify>layout.hDWP = NULL; // not actually doing layout</P>
<P align=justify></P>
<P align=justify>//按一定顺序给各个控制条发送父窗口resize的消息;</P>
<P align=justify>//各个控制条窗口收到消息后,从客户区中扣除自己使用的区域;</P>
<P align=justify>//并且必要的话每个控制窗口调用:eferWindowPos</P>
<P align=justify>//剩下的区域留给nIDLeftOver子窗口</P>
<P align=justify>for (HWND hWndChild = ::GetTopWindow(m_hWnd); hWndChild != NULL;</P>
<P align=justify>hWndChild = ::GetNextWindow(hWndChild, GW_HWNDNEXT))</P>
<P align=justify>{</P>
<P align=justify>UINT nIDC = _AfxGetDlgCtrlID(hWndChild);</P>
<P align=justify>CWnd* pWnd = CWnd::FromHandlePermanent(hWndChild);</P>
<P align=justify>//如果是指定的nIDLeftOver子窗口,则保存其窗口句柄;</P>
<P align=justify>//否则,是控制条窗口,给它们发送WM_SIZEPARENT消息</P>
<P align=justify>if (nIDC == nIDLeftOver)</P>
<P align=justify>hWndLeftOver = hWndChild;</P>
<P align=justify>else if (nIDC &gt;= nIDFirst &amp;&amp; nIDC &lt;= nIDLast &amp;&amp; pWnd != NULL)</P>
<P align=justify>//如果layout-&gt;hDWP非空, OnSizeParent则将执行窗口布置的操作</P>
<P align=justify>::SendMessage(hWndChild, WM_SIZEPARENT, 0, (LPARAM)&amp;layout);</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>//如果是reposQuery,则得到客户区矩形,返回</P>
<P align=justify>if (nFlags == reposQuery)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(lpRectParam != NULL);</P>
<P align=justify>if (bStretch)</P>
<P align=justify>::CopyRect(lpRectParam, &amp;layout.rect);</P>
<P align=justify>else</P>
<P align=justify>{</P>
<P align=justify>lpRectParam-&gt;left = lpRectParam-&gt;top = 0;</P>
<P align=justify>lpRectParam-&gt;right = layout.sizeTotal.cx;</P>
<P align=justify>lpRectParam-&gt;bottom = layout.sizeTotal.cy;</P>
<P align=justify>}</P>
<P align=justify>return;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>//其他情况下(reposDefault、reposExtra),则需要执行Layout操作</P>
<P align=justify></P>
<P align=justify>//处理hWndLeftOver(nIDLeftOver子窗口)</P>
<P align=justify>if (nIDLeftOver != 0 &amp;&amp; hWndLeftOver != NULL)</P>
<P align=justify>{</P>
<P align=justify>CWnd* pLeftOver = CWnd::FromHandle(hWndLeftOver);</P>
<P align=justify>// allow extra space as specified by lpRectBorder</P>
<P align=justify>if (nFlags == reposExtra)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(lpRectParam != NULL);</P>
<P align=justify>layout.rect.left += lpRectParam-&gt;left;</P>
<P align=justify>layout.rect.top += lpRectParam-&gt;top;</P>
<P align=justify>layout.rect.right -= lpRectParam-&gt;right;</P>
<P align=justify>layout.rect.bottom -= lpRectParam-&gt;bottom;</P>
<P align=justify>}</P>
<P align=justify>//基于layout.rect表示的客户尺寸计算出窗口尺寸</P>
<P align=justify>pLeftOver-&gt;CalcWindowRect(&amp;layout.rect);</P>
<P align=justify>//导致函数:eferWindowPos的调用</P>
<P align=justify>AfxRepositionWindow(&amp;layout, hWndLeftOver, &amp;layout.rect);</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>//给所有的窗口设置尺寸、位置(size and layout)</P>
<P align=justify>if (layout.hDWP == NULL || !::EndDeferWindowPos(layout.hDWP))</P>
<P align=justify>TRACE0("Warning: DeferWindowPos failed - low system resources.\n");</P>
<P align=justify>}</P>
<P align=justify>RepositionBars用来改变客户窗口中控制条的尺寸大小或者位置,其中:</P>
<P align=justify>参数1和参数2定义了需要重新放置的子窗口ID的范围,一般是0到0xFFFF。</P>
<P align=justify>参数3指定了一个子窗口ID,它拥有客户窗口剩下的空间,一般是AFX_IDW_PANE_FIRST,表示视的窗口ID。</P>
<P align=justify>参数4指定了操作类型,缺省是CWnd::ReposDefault,表示执行窗口放置操作,参数5不会用到;若取值CWnd::ReposQuery,则表示尝试进行窗口放置(Layout) ,但最后不执行这个操作,只是把参数5初始化成客户区的尺寸大小;若取值CWnd::ReposExtra,则把参数5的值加到参数2表示的子窗口的客户区域,并执行窗口放置操作。</P>
<P align=justify>参数6表示传递给函数的可用窗口客户区的尺寸,如果空则使用窗口客户区尺寸。</P>
<P align=justify>如果执行layout操作的话,该函数的核心处理就是:</P>
<P align=justify>首先,调用::BeginDeferWindowPos初始化一个Windows内部的多窗口位置结构(Multiple-window - position structure)hDWP;</P>
<P align=justify>然后,让各个子窗口逐个调用:eferWindowPos,更新hDWP。在调用:eferWindowPos之前,要作一个确定子窗口大小的工作。这些工作通过给各个控制子窗口发送消息WM_SIZEPARENT来完成。</P>
<P align=justify>控制子窗口通过函数OnSizeParent响应WM_SIZEPARENT消息,先确定自己的尺寸,然后,如果需要进行窗口布置(WM_SIZEPARENT消息参数lParam包含了一个非空的HDWP结构(lpLayout-&gt;hDWP)),则OnSizeParent将调用AfxRepositionWindow函数计算本控制窗口的位置,结果保存到hDWP中。</P>
<P align=justify>在所有的控制窗口尺寸确定之后,剩下的留给窗口hWndLeftOver(如果存在的话)。确定了hWndLeftOver的大小之后,调用AfxRepositionWindow函数计算其位置,结果保存到hDWP中。</P>
<P align=justify>上面提到的函数AfxRepositionWindow间接调用了:eferWindowPos。</P>
<P align=justify>最后,::EndDeferWindowPos,使用hDWP安排所有子窗口的位置和大小。</P>
<P align=justify></P>
<P align=justify>至于其他函数,如OnSizeparent、OnWindowPosChanging、CalcWindowRect,这里不作进一步的分析。</P>
<P align=justify></P></LI></OL></OL></OL>
 楼主| 发表于 2006-12-8 10:35:31 | 显示全部楼层
<OL>
<LI><B>工具条、状态栏和边框窗口的接口</B> </LI>
<LI></LI>
<LI>
<DIV align=justify> </DIV></LI>
<LI><B>应用程序在状态栏中显示信息</B>
<p></LI></OL>
< align=justify>MFC内部通过给边框窗口发送消息WM_SETMESSAGESTRING、WM_POPMESSAGESTRING的方式在状态栏中显示信息。这两个消息在afxpriv.h里头定义。</P>
< align=justify>WM_SETMESSAGESTRING消息表示在状态栏中显示和某个ID对应的字符串信息或者指定的字符串信息,消息参数wParam指定了字符串资源ID,消息参数lParam指定了字符串指针,两个消息参数只有一个有用。一般,一个命令ID对应了一个字符串ID,对应的字符串是命令ID的说明。</P>
< align=justify>消息WM_POPMESSAGESTRING用来重新设置状态栏。</P>
< align=justify>这两个消息对应的消息处理函数分别是OnSetMessageString和OnPopMessageString,OnSetMessageString和OnPopMessageString分别实现如下:</P>
<OL>
< align=justify>
<LI>OnSetMessageString
<p>
< align=justify>LRESULT CFrameWnd::OnSetMessageString(WPARAM wParam, LPARAM lParam)</P>
< align=justify>{</P>
< align=justify>//最近一次被显示的消息字符串IDS(一个消息对应的字符串)</P>
< align=justify>UINT nIDLast = m_nIDLastMessage;</P>
< align=justify>m_nFlags &amp;= ~WF_NOPOPMSG;</P>
<P align=justify></P>
<P align=justify>//得到状态栏</P>
<P align=justify>CWnd* pMessageBar = GetMessageBar();</P>
<P align=justify>if (pMessageBar != NULL)</P>
<P align=justify>{</P>
<P align=justify>LPCTSTR lpsz = NULL;</P>
<P align=justify>CString strMessage;</P>
<P align=justify></P>
<P align=justify>//设置状态栏文本</P>
<P align=justify>if (lParam != 0) //指向一个字符串</P>
<P align=justify>{</P>
<P align=justify>ASSERT(wParam == 0); // can't have both an ID and a string</P>
<P align=justify>lpsz = (LPCTSTR)lParam; // set an explicit string</P>
<P align=justify>}</P>
<P align=justify>else if (wParam != 0)//一个字符串资源IDS</P>
<P align=justify>{</P>
<P align=justify>//打印预览时映射SC_CLOSE成AFX_IDS_PREVIEW_CLOSE;</P>
<P align=justify>if (wParam == AFX_IDS_SCCLOSE &amp;&amp; m_lpfnCloseProc != NULL)</P>
<P align=justify>wParam = AFX_IDS_PREVIEW_CLOSE;</P>
<P align=justify></P>
<P align=justify>//得到资源ID所标识的字符串</P>
<P align=justify>GetMessageString(wParam, strMessage);</P>
<P align=justify>lpsz = strMessage;</P>
<P align=justify>}</P>
<P align=justify>//在状态栏中显示文本</P>
<P align=justify>pMessageBar-&gt;SetWindowText(lpsz);</P>
<P align=justify></P>
<P align=justify>// 根据最近一次选择的消息更新状态条所属窗口的有关记录</P>
<P align=justify>CFrameWnd* pFrameWnd = pMessageBar-&gt;GetParentFrame();</P>
<P align=justify>if (pFrameWnd != NULL)</P>
<P align=justify>{</P>
<P align=justify>//记录最近一次显示的消息字符串</P>
<P align=justify>pFrameWnd-&gt;m_nIDLastMessage = (UINT)wParam;</P>
<P align=justify>//记录最近一次Tracking的命令ID和字符串IDS</P>
<P align=justify>pFrameWnd-&gt;m_nIDTracking = (UINT)wParam;</P>
<P align=justify>}</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>m_nIDLastMessage = (UINT)wParam; // new ID (or 0)</P>
<P align=justify>m_nIDTracking = (UINT)wParam; // so F1 on toolbar buttons work</P>
<P align=justify>return nIDLast;</P>
<P align=justify>}</P>
<P align=justify>OnSetMessageString函数直接或者从ID从字符串资源中得到字符串指针。如果是从ID得到字符串指针,则函数GetMessageString被调用。</P>
<P align=justify>和命令ID对应的字符串由两部分组成,前一部分用于在状态栏显示,后一部分用于Tooltip显示,分隔符号是“\n”。例如,字符串ID_APP_EXIT(对应“退出”菜单、按钮)是“Exit Application\nExit”,当鼠标落在“退出”按钮上时,状态栏显示“Exit Application”,Tooltip显示“Exit”。根据这种格式,GetMessageString分离出第一部分的文本信息。至于第二部分的用途将在讨论Tooltip的章节将用到。</P>
<P align=justify>得到了字符串之后,OnSetMessageString调用状态栏的SetWindowText函数。SetWindowText导致消息WM_SETTEXT消息发送给状态栏,状态栏的消息处理函数OnSetText被调用,实际上等于调用了SetPaneText(0, lpsz),即在状态栏的第0格中显示字符串lpsz的信息。对于工具栏来说,SetWindowText可以认为是SetPaneText(0, lpsz)的简化版本。</P>
<P align=justify>顺便指出,pMessageBar-&gt;GetParentFrame()返回主边框窗口,即使pMessageBar指向漂浮的工具条。关于泊位和漂浮,见后面13.2.5节的描述。</P>
<P align=justify>关于OnSetText,其实现如下:</P>
<P align=justify>LRESULT CStatusBar::OnSetText(WPARAM, LPARAM lParam)</P>
<P align=justify>{</P>
<P align=justify>ASSERT_VALID(this);</P>
<P align=justify>ASSERT(::IsWindow(m_hWnd));</P>
<P align=justify></P>
<P align=justify>int nIndex = CommandToIndex(0); //返回0</P>
<P align=justify>if (nIndex &lt; 0)</P>
<P align=justify>return -1;</P>
<P align=justify></P>
<P align=justify>return SetPaneText(nIndex, (LPCTSTR)lParam) ? 0 : -1;</P>
<P align=justify>}</P>
<P align=justify></P>
<LI>OnPopMessageString
<p></LI></OL>
<P align=justify>LRESULT CFrameWnd::OnPopMessageString(WPARAM wParam, </P>
<DIR>
<DIR>
<P align=justify>LPARAM lParam)</P></DIR></DIR>
<P align=justify>{</P>
<P align=justify>//WF_NOPOPMSG表示边框窗口不处理WM_POPMESSAGESTRING</P>
<P align=justify>if (m_nFlags &amp; WF_NOPOPMSG)</P>
<DIR>
<P align=justify>return 0;</P>
<P align=justify></P></DIR>
<P align=justify>//调用OnSetMessageString</P>
<P align=justify>return SendMessage(WM_SETMESSAGESTRING, wParam, lParam);</P>
<P align=justify>}</P>
<P align=justify>一般,在清除状态栏消息时,发送WM_POPMESSAGESTRING,通过消息参数wParam指定一个字符串资源,其ID 为AFX_IDS_IDLEMESSAGE,对应的字符串是“Ready”。</P>
<OL>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>状态栏显示菜单项的提示信息</B>
<p>
<P align=justify>状态栏的一个重要作用是显示菜单命令或者工具条按钮的提示信息。本节讨论如何显示菜单命令的提示信息,关于工具条按钮在这方面的讨论见后面13.2.4.4章节。</P>
<P align=justify>显示菜单命令的提示信息,就是每当一个菜单项被选中之后,在状态栏显示该菜单的功能、用法等信息。这些信息以字符串资源的形式保存,字符串ID对应于菜单项的命令ID。</P>
<P align=justify>所以,必须处理菜单选择消息WM_MENUSELECT。CFrameWnd实现了消息处理函数OnMenuSelect,其实现如下:</P>
<P align=justify>void CFrameWnd::OnMenuSelect(UINT nItemID, </P>
<P align=justify>UINT nFlags, HMENU /*hSysMenu*/)</P>
<P align=justify>{</P>
<P align=justify>CFrameWnd* pFrameWnd = GetTopLevelFrame();</P>
<P align=justify>ASSERT_VALID(pFrameWnd);</P>
<P align=justify></P>
<P align=justify>//跟踪被选中的菜单项</P>
<P align=justify>if (nFlags == 0xFFFF)</P>
<P align=justify>{</P>
<P align=justify>//取消菜单操作</P>
<P align=justify>m_nFlags &amp;= ~WF_NOPOPMSG;</P>
<P align=justify>if (!pFrameWnd-&gt;m_bHelpMode)</P>
<P align=justify>m_nIDTracking = AFX_IDS_IDLEMESSAGE;</P>
<P align=justify>else</P>
<P align=justify>m_nIDTracking = AFX_IDS_HELPMODEMESSAGE;</P>
<P align=justify>//在状态栏显示</P>
<P align=justify>SendMessage(WM_SETMESSAGESTRING, (WPARAM)m_nIDTracking);</P>
<P align=justify>ASSERT(m_nIDTracking == m_nIDLastMessage);</P>
<P align=justify></P>
<P align=justify>// update right away</P>
<P align=justify>CWnd* pWnd = GetMessageBar();</P>
<P align=justify>if (pWnd != NULL)</P>
<P align=justify>pWnd-&gt;UpdateWindow();</P>
<P align=justify>}</P>
<P align=justify>else</P>
<P align=justify>{</P>
<P align=justify>//选中分隔栏、Popup子菜单或者没有选中一个菜单项</P>
<P align=justify>if (nItemID == 0 || nFlags &amp; (MF_SEPARATOR|MF_POPUP))</P>
<P align=justify>{</P>
<P align=justify>// nothing should be displayed</P>
<P align=justify>m_nIDTracking = 0;</P>
<P align=justify>}</P>
<P align=justify>else if (nItemID &gt;= 0xF000 &amp;&amp; nItemID &lt; 0xF1F0) // max of 31 SC_s</P>
<P align=justify>{</P>
<P align=justify>//系统菜单的菜单项被选中</P>
<P align=justify>m_nIDTracking = ID_COMMAND_FROM_SC(nItemID);</P>
<P align=justify>ASSERT(m_nIDTracking &gt;= AFX_IDS_SCFIRST &amp;&amp;</P>
<P align=justify>m_nIDTracking &lt; AFX_IDS_SCFIRST + 31);</P>
<P align=justify>}</P>
<P align=justify>else if (nItemID &gt;= AFX_IDM_FIRST_MDICHILD)</P>
<P align=justify>{</P>
<P align=justify>//如果选中的菜单项表示一个MDI子窗口</P>
<P align=justify>m_nIDTracking = AFX_IDS_MDICHILD;</P>
<P align=justify>}</P>
<P align=justify>else</P>
<P align=justify>{</P>
<P align=justify>//选中了一个菜单项</P>
<P align=justify>m_nIDTracking = nItemID;</P>
<P align=justify>}</P>
<P align=justify>pFrameWnd-&gt;m_nFlags |= WF_NOPOPMSG;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>// when running in-place, it is necessary to cause a message to</P>
<P align=justify>// be pumped through the queue.</P>
<P align=justify>if (m_nIDTracking != m_nIDLastMessage &amp;&amp; GetParent() != NULL)</P>
<P align=justify>PostMessage(WM_KICKIDLE);</P>
<P align=justify>}</P>
<P align=justify>OnMenuSelect的作用在于跟踪当前选中的菜单项,把菜单项对应的ID保存在CFrameWnd的成员变量m_nIDTracking中。</P>
<P align=justify>如果菜单项没有选中,或者选中的是一个子菜单,则设置nIDTracking为0。</P>
<P align=justify>如果选中的是系统菜单,则把系统菜单ID转换成一个对应的命令ID;保存该值到nIDTracking中。</P>
<P align=justify>如果选中的菜单是MDI子窗口创建时添加的(用来表示MDI子窗口),则转换菜单ID为AFX_IDS_MDICHILD,所有对应MDI子窗口的菜单项都使用AFX_IDS_MDICHILD,保存该值到nIDTracking中。</P>
<P align=justify>其他情况,就是选中菜单项的ID,把它保存到nIDTracking中。</P>
<P align=justify></P>
<P align=justify>跟踪被选择的菜单项并保存其ID在m_nIDTracking中,OnEnterIdle将用到m_nIDTracking。OnEnterIlde是消息WM_ENTERIDLE的处理函数,CFrameWnd的实现如下。</P>
<P align=justify>void CFrameWnd::OnEnterIdle(UINT nWhy, CWnd* pWho)</P>
<P align=justify>{</P>
<P align=justify>CWnd::OnEnterIdle(nWhy, pWho);</P>
<P align=justify></P>
<P align=justify>//若不是因为菜单选择进入该函数</P>
<P align=justify>//或者当前跟踪到的菜单项ID是最近一次处理的,则返回</P>
<P align=justify>if (nWhy != MSGF_MENU || m_nIDTracking == m_nIDLastMessage)</P>
<P align=justify>return;</P>
<P align=justify></P>
<P align=justify>//将发送消息WM_SETMESSAGETEXT</P>
<P align=justify>//在状态栏显示m_nIDTracking对应的字符串</P>
<P align=justify>SetMessageText(m_nIDTracking);</P>
<P align=justify>ASSERT(m_nIDTracking == m_nIDLastMessage);</P>
<P align=justify>}</P>
<P align=justify>当一个对话框或者菜单被显示的时候,Windows发送WM_ENTERIDLE消息。消息参数wParam取值为MSGF_DIALOGBOX或者MSGF_MENU。前者表示显示对话框时发送该消息,这时消息参数lParam表示对话框的句柄;后者表示显示菜单时发送该消息,这时消息参数lParam表示菜单的句柄。</P>
<P align=justify>经过消息映射,wParam的值传递给OnEnterIdle的参数nWhy,参数lParam的值传给参数who。如果参数1取值为MSGF_MENU,并且OnEnterIdle最近一次在菜单显示被调用时的菜单ID不同于这一次,则调用SetMessageText在状态栏显示对应ID命令的字符串,并且记录当前菜单ID到变量m_nIDTracking中(见消息处理函数OnSetMessageText)。</P>
<P align=justify>这样,在菜单选择期间,用户选择的菜单项ID被OnMenuSelect记录,在消息WM_ENTERIDLE处理时在状态栏显示ID命令的提示。</P>
<P align=justify></P></LI></OL></OL></OL></OL>
 楼主| 发表于 2006-12-8 10:36:07 | 显示全部楼层
<OL>
<LI>
<><B>控制条的消息分发处理</B> </P>
<p></LI></OL>
< align=justify>工具条(包括对话框工具条)是一个子窗口,它们可以响应各种消息。如果按标准的Windows消息和命令消息的分发途径,一些消息不能送到拥有工具条的边框窗口,因为这些消息都将被工具条(对话框工具条)处理掉。所以,CControlBar覆盖了虚拟函数PreTranslateMessage和WindowProc以便实现特定的消息分发路径。</P>
<OL>
< align=justify>
<LI>WindowProc
<p>
< align=justify>CControlBar 的WindowProc实现了如下的消息分发路径:</P>
< align=justify>用户对控制条的输入消息或者分发给CControlBar及其派生类处理,或者送给拥有控制条的边框窗口处理,或者送给Windows控制“窗口类”的窗口过程处理。</P>
< align=justify>WindowProc的实现如下:</P>
< align=justify>LRESULT CControlBar::WindowProc(UINT nMsg, </P>
< align=justify>WPARAM wParam, LPARAM lParam)</P>
< align=justify>{</P>
< align=justify>ASSERT_VALID(this);</P>
<P align=justify></P>
<P align=justify>LRESULT lResult;</P>
<P align=justify>switch (nMsg)</P>
<P align=justify>{</P>
<P align=justify>//本函数处理以下消息</P>
<P align=justify>case WM_NOTIFY:</P>
<P align=justify>case WM_COMMAND:</P>
<P align=justify>case WM_DRAWITEM:</P>
<P align=justify>case WM_MEASUREITEM:</P>
<P align=justify>case WM_DELETEITEM:</P>
<P align=justify>case WM_COMPAREITEM:</P>
<P align=justify>case WM_VKEYTOITEM:</P>
<P align=justify>case WM_CHARTOITEM:</P>
<P align=justify>//首先,工具条处理上述消息,如果没有处理,则接着给所属边框窗口处理</P>
<P align=justify>if (OnWndMsg(nMsg, wParam, lParam, &amp;lResult))</P>
<P align=justify>return lResult;</P>
<P align=justify>else</P>
<P align=justify>return GetOwner()-&gt;SendMessage(nMsg, wParam, lParam);</P>
<P align=justify>}</P>
<P align=justify>}</P>
<P align=justify>// 最后,给基类CWnd,按缺省方式处理</P>
<P align=justify>lResult = CWnd::WindowProc(nMsg, wParam, lParam);</P>
<P align=justify>return lResult;</P>
<P align=justify>}</P>
<P align=justify>从上述实现可以看出,对于case范围内的一些消息,如WM_COMMAND、WM_NOTIFY等,控制条如果不能处理,则优先分发给其父窗口(边框窗口)处理,然后进入缺省处理,对于其他消息直接调用基类CWnd的实现(缺省处理)。基于这样的机制,可以把用户对工具条按钮或者对话框工具条内控制的操作解释成相应的命令消息,执行对应的命令处理。</P>
<P align=justify>对于工具条,当用户选中某个按钮时(鼠标左键弹起,消息是WM_LBUTTONUP),工具条窗口接收到WM_LBUTTONUP消息,该消息不在CControlBar::WindowProc特别处理的消息范围内,于是进行缺省处理,也就是说,把该消息派发给控制条对应的Windows控制的窗口过程处理(即被MFC统一窗口过程所取代的原窗口过程),该窗口过程则把该消息转换成一条命令消息WM_COMMAND,命令ID就是选中按钮对应的ID,然后,发送该命令消息给拥有工具条的边框窗口,导致相应的命令处理函数被调用。</P>
<P align=justify>对于对话框工具条,当工具条的某个控制子窗口被选中之后,则产生一条命令通知消息WM_COMMAND,wParam是控制子窗口的ID。CControlBar::WindowProc处理该消息。WindowProc首先调用OnWndMsg把消息发送给对话框工具条或者对话框工具条的基类处理,如果没有被处理的话,则OnWndMsg返回FALSE。接着,WindowPoc把命令消息传递给父窗口(边框窗口)处理。由于工具条的控制窗口的ID对应的是命令ID,所以,这条WM_COMMAND消息传递给边框窗口时,被解释成一个命令消息,相应的命令处理函数被调用。</P>
<P align=justify></P>
<LI>PreTranslateMessage
<p></LI></OL>
<P align=justify>CControlBar覆盖PreTranslateMessage函数,主要是为了在光标落在工具条按钮上时显示FLYBY信息,并且让对话框工具条过滤Dialog消息。</P>
<P align=justify>BOOL CControlBar:reTranslateMessage(MSG* pMsg)</P>
<P align=justify>{</P>
<P align=justify>ASSERT_VALID(this);</P>
<P align=justify>ASSERT(m_hWnd != NULL);</P>
<P align=justify></P>
<P align=justify>//过滤Tooltip消息</P>
<P align=justify>if (CWnd:reTranslateMessage(pMsg))</P>
<DIR>
<P align=justify>return TRUE; //是Tooltip消息,已经被处理</P>
<P align=justify></P></DIR>
<P align=justify>UINT message = pMsg-&gt;message;</P>
<P align=justify>//控制条的父窗口,对工具条和对话框工具条,总是创建它的边框窗口</P>
<P align=justify>CWnd* pOwner = GetOwner();</P>
<P align=justify></P>
<P align=justify>//必要的话,在状态条显示工具栏按钮的提示</P>
<P align=justify>if (((m_dwStyle &amp; CBRS_FLYBY) ||</P>
<DIR>
<P align=justify>message == WM_LBUTTONDOWN || message == WM_LBUTTONUP) &amp;&amp;</P>
<P align=justify>((message &gt;= WM_MOUSEFIRST &amp;&amp; message &lt;= WM_MOUSELAST) ||</P>
<P align=justify>(message &gt;= WM_NCMOUSEFIRST &amp;&amp; </P>
<DIR>
<P align=justify>message &lt;= WM_NCMOUSELAST)))</P></DIR></DIR>
<P align=justify>{</P>
<DIR>
<P align=justify>_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();</P>
<P align=justify></P>
<P align=justify>//确认鼠标在工具栏的哪个按钮上</P>
<P align=justify>CPoint point = pMsg-&gt;pt;</P>
<P align=justify>ScreenToClient(&amp;point);</P>
<P align=justify>TOOLINFO ti; memset(&amp;ti, 0, sizeof(TOOLINFO));</P>
<P align=justify>ti.cbSize = sizeof(TOOLINFO);</P>
<P align=justify>int nHit = OnToolHitTest(point, &amp;ti);</P>
<P align=justify>if (ti.lpszText != LPSTR_TEXTCALLBACK)</P>
<DIR>
<P align=justify>free(ti.lpszText);</P></DIR>
<P align=justify>BOOL bNotButton =</P>
<DIR>
<P align=justify>message == WM_LBUTTONDOWN &amp;&amp; (ti.uFlags &amp; TTF_NOTBUTTON);</P></DIR>
<P align=justify>if (message != WM_LBUTTONDOWN &amp;&amp; GetKeyState(VK_LBUTTON) &lt; 0)</P>
<DIR>
<P align=justify>nHit = pThreadState-&gt;m_nLastStatus;</P>
<P align=justify></P></DIR>
<P align=justify>//更新状态栏的提示信息</P>
<P align=justify>if (nHit &lt; 0 || bNotButton)</P>
<P align=justify>{</P>
<DIR>
<P align=justify>if (GetKeyState(VK_LBUTTON) &gt;= 0 || bNotButton)</P>
<P align=justify>{</P>
<P align=justify>SetStatusText(-1);</P>
<P align=justify>KillTimer(ID_TIMER_CHECK);</P>
<P align=justify>}</P></DIR>
<P align=justify>}</P>
<P align=justify>else</P>
<P align=justify>{</P>
<DIR>
<P align=justify>if (message == WM_LBUTTONUP)</P>
<P align=justify>{</P>
<DIR>
<DIR>
<P align=justify>SetStatusText(-1);</P>
<P align=justify>ResetTimer(ID_TIMER_CHECK, 200);</P></DIR></DIR>
<P align=justify>}</P></DIR>
<P align=justify>else</P>
<P align=justify>{</P>
<DIR>
<P align=justify>if ((m_nStateFlags &amp; statusSet) || GetKeyState(VK_LBUTTON) &lt; 0)</P>
<P align=justify>SetStatusText(nHit);</P></DIR>
<P align=justify>else if (nHit != pThreadState-&gt;m_nLastStatus)</P>
<DIR>
<P align=justify>ResetTimer(ID_TIMER_WAIT, 300);</P></DIR>
<P align=justify>}</P></DIR>
<P align=justify>}</P>
<P align=justify>pThreadState-&gt;m_nLastStatus = nHit;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>// don't translate dialog messages when in Shift+F1 help mode</P>
<P align=justify>CFrameWnd* pFrameWnd = GetTopLevelFrame();</P>
<P align=justify>if (pFrameWnd != NULL &amp;&amp; pFrameWnd-&gt;m_bHelpMode)</P>
<DIR>
<P align=justify>return FALSE;</P>
<P align=justify></P></DIR>
<P align=justify>//在IsDialogMessage之前调用边框窗口的PreTranslateMessage,</P>
<P align=justify>//给边框窗口一个处理快捷键的机会</P>
<P align=justify>while (pOwner != NULL)</P>
<P align=justify>{</P>
<DIR>
<P align=justify>// allow owner &amp; frames to translate before IsDialogMessage does</P>
<P align=justify>if (pOwner-&gt;PreTranslateMessage(pMsg))</P>
<DIR>
<P align=justify>return TRUE;</P>
<P align=justify></P></DIR>
<P align=justify>// try parent frames until there are no parent frames</P>
<P align=justify>pOwner = pOwner-&gt;GetParentFrame();</P></DIR>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>//过滤给对话框的消息和来自子窗口的消息</P>
<P align=justify>return PreTranslateInput(pMsg);</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>函数PreTranslateMessage主要是针对工具栏的,用来处理工具栏的CBRS_FLYBY特征。</P>
<P align=justify>对于对话框工具栏,也可以有CBRS_FLYBY特征。但在这种情况下,还需要把一些用户键盘输入解释成对话框消息。为了防止快捷键被解释成对话框消息,在调用函数PreTranslateInput之前,必须调用所有父边框窗口的PreTranslateMessage,给父边框窗口一个机会处理用户的输入消息,判断快捷键是否被按下。</P>
<P align=justify>关于Tooltip和本PreTranslateMessage函数处理Tooltip的详细解释见下一节的讨论。</P>
<OL>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>Tooltip</B>
<p></LI></OL></OL></OL></OL>
<P align=justify>工具条(或对话框工具条)如果指定了CBRS_TOOLTIPS风格(创建时指定或者创建后用SetBarStyle设置),则当鼠标落在某个按钮上(或者对话框的子控制窗口)时,在鼠标附近弹出一个文本框──Tooltip提示窗口。</P>
<P align=justify>如果还指定了CBRS_FLYBY风格,则还在状态栏显示和按钮(或子控制窗口)ID对应的字符串信息。当然,鼠标左键在某个按钮(或子控制窗口)按下时,也要在状态栏显示按钮的提示信息,当左键弹起时,则重置状态栏的状态。</P>
<P align=justify>如前所述,Tooltip窗口是Windows控制窗口。MFC使用了CToolTipCtrl类封装Tooltip的HWND窗口。在一个线程的生存期间,至多拥有一个Tooltip窗口,该窗口对象的指针保存在线程状态的成员变量m_pToolTip中。线程状态类AFX_THREAD_STATE的析构函数如果检测到m_pToolTip,则销毁MFC窗口对象和相应的Windows窗口对象。</P>
<OL>
<P align=justify>
<LI>CWnd对Tooltip消息的预处理
<p>
<P align=justify>为了支持Tooltip显示,CWnd提供了以下函数:EnableTooltip,CancelTooltip,PreTranslateMessage,FilterTooltipMessage,OnToolHitTest。</P>
<P align=justify>EnableTooltip设置CBRS_TOOLTIP风格,相反CancelTootip取消这种风格。</P>
<P align=justify>PreTranslateMessage调用了FilterTooltipMessage过滤Tooltip消息。</P>
<P align=justify>OnToolHitTest是一个由CWnd定义的虚拟函数。CToolBar通过覆盖该函数,来检测对话框工具栏的控制子窗口或者工具栏按钮是否被选中、哪个被选中。</P>
<P align=justify></P>
<P align=justify>CWnd的PreTranslateMessage在4.5节讨论过,它的实现如下:</P>
<P align=justify>BOOL CWnd:reTranslateMessage(MSG* pMsg)</P>
<P align=justify>{</P>
<P align=justify>//处理Tooltip消息</P>
<P align=justify>AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();</P>
<P align=justify>if (pModuleState-&gt;m_pfnFilterToolTipMessage != NULL)</P>
<P align=justify>//导致调用FilterTooltipMessage</P>
<P align=justify>(*pModuleState-&gt;m_pfnFilterToolTipMessage)(pMsg, this);</P>
<P align=justify></P>
<P align=justify>//不是Tooltip消息</P>
<P align=justify>return FALSE;</P>
<P align=justify>}</P>
<P align=justify>至于为什么MFC在模块状态中保存一个处理Tooltip消息的函数地址,通过该函数调用FilterTooltipMessage,是因为Tooltip窗口是模块线程局部有效的。</P>
<P align=justify>FilterTooltipMessage检测是否是Tooltip消息。如果是,在必要时创建一个CTooltipCtrl对象和对应的HWND,调用OnToolHitTest确定被选中的按钮或者控制的ID,接着弹出Tooltip窗口。</P>
<P align=justify>其他函数和CTooltipCtrl这里不作详细论述了。</P>
<P align=justify></P>
<LI>处理TTN_NEEDTEXT通知消息
<p>
<P align=justify>Tooltip窗口在弹出之前,它给工具条(或者对话框工具栏)的父窗口发送通知消息TTN_NEEDTEXT,请求得到要显示的文本。</P>
<P align=justify>CFrameWnd类处理了TTN_NEEDTEXT通知消息,消息处理函数是OnToolTipText。</P>
<P align=justify>消息映射的定义:</P>
<P align=justify>ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)</P>
<P align=justify>ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)</P>
<P align=justify>这里,使用了扩展消息映射宏把子窗口ID在0和0xFFFF之间的控制条窗口的通知消息TTN_NEEDTEXTA和TTN_NEEDTEXTW映射到函数OnToolTipText。</P>
<P align=justify>消息映射的实现:</P>
<P align=justify>BOOL CFrameWnd::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(pNMHDR-&gt;code == TTN_NEEDTEXTA || </P>
<P align=justify>pNMHDR-&gt;code == TTN_NEEDTEXTW);</P>
<P align=justify></P>
<P align=justify>//让上一层的边框窗口优先处理该消息</P>
<P align=justify>if (GetRoutingFrame() != NULL)</P>
<P align=justify>return FALSE;</P>
<P align=justify></P>
<P align=justify>//分ANSI and UNICODE两个处理版本</P>
<P align=justify>TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;</P>
<P align=justify>TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;</P>
<P align=justify>TCHAR szFullText[256];</P>
<P align=justify>CString strTipText;</P>
<P align=justify>UINT nID = pNMHDR-&gt;idFrom;</P>
<P align=justify>//如果idFrom是一个子窗口,则得到其ID。</P>
<P align=justify>if (pNMHDR-&gt;code == TTN_NEEDTEXTA &amp;&amp; </P>
<P align=justify>(pTTTA-&gt;uFlags &amp; TTF_IDISHWND) ||</P>
<P align=justify>pNMHDR-&gt;code == TTN_NEEDTEXTW &amp;&amp;</P>
<P align=justify>(pTTTW-&gt;uFlags &amp; TTF_IDISHWND))</P>
<P align=justify>{</P>
<P align=justify>//idFrom是工具条的句柄</P>
<P align=justify>nID = _AfxGetDlgCtrlID((HWND)nID);</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>if (nID != 0) //若是0,为一分隔栏,不是按钮</P>
<P align=justify>{</P>
<P align=justify>//得到nID对应的字符串</P>
<P align=justify>AfxLoadString(nID, szFullText);</P>
<P align=justify>//从上面得到的字符串中取出Tooltip使用的文本</P>
<P align=justify>AfxExtractSubString(strTipText, szFullText, 1, '\n');</P>
<P align=justify>}</P>
<P align=justify>//复制分离出的文本</P>
<P align=justify>#ifndef _UNICODE</P>
<P align=justify>if (pNMHDR-&gt;code == TTN_NEEDTEXTA)</P>
<P align=justify>lstrcpyn(pTTTA-&gt;szText, strTipText, _countof(pTTTA-&gt;szText));</P>
<P align=justify>else</P>
<P align=justify>_mbstowcsz(pTTTW-&gt;szText, strTipText, _countof(pTTTW-&gt;szText));</P>
<P align=justify>#else</P>
<P align=justify>if (pNMHDR-&gt;code == TTN_NEEDTEXTA)</P>
<P align=justify>_wcstombsz(pTTTA-&gt;szText, strTipText, _countof(pTTTA-&gt;szText));</P>
<P align=justify>else</P>
<P align=justify>lstrcpyn(pTTTW-&gt;szText, strTipText, _countof(pTTTW-&gt;szText));</P>
<P align=justify>#endif</P>
<P align=justify>*pResult = 0;</P>
<P align=justify></P>
<P align=justify>//显示Tooltip窗口</P>
<P align=justify>::SetWindowPos(pNMHDR-&gt;hwndFrom, HWND_TOP, 0, 0, 0, 0,</P>
<P align=justify>SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE);</P>
<P align=justify></P>
<P align=justify>return TRUE; //消息处理完毕</P>
<P align=justify>}</P>
<P align=justify>OnToolTipText是一个扩展映射宏定义的消息处理函数,所以有一个UINT参数并且返回BOOL类型的值。不过,由于第二个参数(NMHDR)的idFrom域包含了有关信息,所以第一个UINT类型的参数没有用上。</P>
<P align=justify>OnToolTipText也是一个处理通知消息的例子。其中,通知参数wParam的结构如4.4.4.2节所述,具体如下:</P>
<P align=justify>typedef struct {</P>
<P align=justify>NMHDR hdr; //WM_NOTIFY消息要求的头</P>
<P align=justify>LPTSTR lpszText; //接收工具条按钮对应文本的缓冲区</P>
<P align=justify>WCHAR szText[80]; //接收Tooltip显示文本的缓冲区</P>
<P align=justify>HINSTANCE hinst; //包含了szText的实例句柄</P>
<P align=justify>UINT uflags; //标识了NMHDR的idFrom成员的意义</P>
<P align=justify>} TOOLTIPTEXT, FAR *LPTOOLTIPTEXT;</P>
<P align=justify>uflags如果等于TTF_IDISHWND,则表示通知消息来自对话框工具条的一个子窗口,而不是包含工具条按钮。</P>
<P align=justify>OnToolTipText根据子窗口ID或者工具条按钮对应的ID,得到字符串ID。如前所述,字符串ID由两部分组成,第二部分用于Tooltip显示,分隔符号是“\n”。根据这种格式OnToolTipText分离出Tooltip文本。</P>
<P align=justify>得到了Tooltip文本之后,可以有三种方法返回文本信息:把文本信息复制到szText缓冲区;把文本地址复制到lpszText;复制字符串资源的ID到lpszText、复制包含资源的实例句柄到hint。本函数采用了第一种方法。</P>
<P align=justify>在得到了返回的Tooltip文本之后,该文本在Tooltip窗口中被显示出来。</P>
<P align=justify>其他的OnToolHist等函数的实现不作详细的解释了。下面,讨论CBRS_FLYBY风格的实现。</P>
<P align=justify></P></LI></OL>
 楼主| 发表于 2006-12-8 10:37:47 | 显示全部楼层
<OL>
<LI>
<>CBRS_FLYBY风格的实现 </P>
<p></LI></OL>
< align=justify>CBRS_FLYBY是MFC提供的特征。当鼠标落在工具条按钮(或者对话框工具条的子窗口)上且稳定300ms后,在状态栏显示对应的提示信息。如果选中某个按钮或者子窗口(鼠标左键按下),则在相应命令消息处理之前在状态栏显示有关提示信息,之后(鼠标左键弹起),重新设置状态栏的状态信息。</P>
< align=justify>为了支持这种特征,CControlBar覆盖虚拟函数PreTranslateMessage来处理和CBRS_FLYBY相关的消息,该函数前面已经讨论过,这里解释它如何处理CBRS_FLYBY特征。</P>
< align=justify>如果同时具备</P>
< align=justify>条件1:控制条具有CBRS_FLYBY特征或者当前消息是WM_LBUTTONUP或者WM_LBUTTONDOWN。</P>
< align=justify>条件2:当前消息是鼠标消息(在WM_MOUSEFIRST和WM_MOUSELAST之间或者在WM_NCMOUSEFIRST和WM_NCMOUSELAST之间)。</P>
< align=justify>则进行FLYBY处理。</P>
< align=justify>首先,调用OnToolHitTest测试用户是否选中了工具条的按钮或者子窗口;</P>
< align=justify>如果没有按钮或者子窗口被选中,则重新设置状态栏的状态,取消曾经设置的Check定时器。重置状态栏的状态时调用了SetStatusText(int nHit)函数,它是CControlBar内部使用的函数,若nHit等于-1,它向父窗口发送WM_POPMESSAGETEXT,消息参数是AFX_IDS_IDLEMESSAGE,结果导致状态栏显示“Ready”字样;否则发送WM_SETMESSAGETEXT消息,wParm设置为nHit,结果导致在状态栏显示ID为nHit的字符串。</P>
< align=justify>如果有按钮或者子窗口被选中,若左键弹起,则重新设置状态栏信息,取消Wait定时器,并重新设置Check定时器,定时是200ms;若左键按下,则在状态栏显示消息ID对应的提示信息;若是其他鼠标消息,如果当前鼠标所在按钮(子窗口)不同于最近一次,则取消Check定时器,重新设置Wait定时器,定时300毫秒。</P>
<P align=justify>CControlBar覆盖了消息处理函数OnTimer,在指定时间之后,检查鼠标位置,如果鼠标还在某个按钮或者子窗口上,则在状态条显示提示信息。Wait定时器在等待之后准备在状态条显示信息,触发一次后被取消;Check定时器在等待之后,判断是否需要取消状态条当前显示的信息,重新设置状态条,若这样的话,同时也取消Check定时器。</P>
<P align=justify>注意,这些鼠标消息被处理之后,并没有终止,它们将继续被发送给控制条的窗口过程处理。</P>
<P align=justify>至此,CBRS_FLYBY特征的支持实现描述完毕。</P>
<OL>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>禁止和允许</B>
<p></LI></OL></OL></OL></OL>
<P align=justify>在MFC下,工具条、状态条还有一个重要的特征,就是自动地根据条件禁止或者允许使用某个按钮、窗格等。在4.4.5节命令消息的处理中,曾详细讨论了其实现原理,现在,详细地分析所涉及函数是如何实现的。有关的消息处理函数和虚拟函数如下。</P>
<P align=justify>处理WM_INITIALUPDATE消息的OnInitialUpdate;</P>
<P align=justify>处理WM_IDLEUPDATECMDUI消息的OnIdleUpdateCmdUI;</P>
<P align=justify>虚拟函数OnUpdateCmdUI。</P>
<P align=justify>回顾5.3.3.5节,在边框窗口的创建之后,给所有的子窗口发送初始化消息,控制子窗口用OnInitialUpdate响应它,调用OnIdleUpdateCmdUI完成状态的初始化。</P>
<P align=justify>OnIdleUpdateCmdUI还在IDLE处理时进行状态的更新处理,它生成用于处理状态更新消息的命令目标pTarget,然后调用虚拟函数OnUpdateCmdUI(pTarget,…)来更新工具栏或者状态栏的状态。</P>
<P align=justify>CControlBar的子类都实现了自己的OnUpdateCmdUI函数,用该函数生成适当的CCmdUI对象state,然后调用CCmdUI的DoUpdate(pTarget,…)给pTarget所指对象发送状态更新消息。为了完成具体的状态更新,从CCmdUI派生出CToolCmdUI和CStatusCCmUI,它们实现了自己的Enable、SetCheck等等。</P>
<OL>
<P align=justify>
<LI>初始化控制窗口
<p>
<P align=justify>CControlBar使用OnInitialUpdate消息处理函数初始化控制窗口的状态。</P>
<P align=justify>void CControlBar::OnInitialUpdate()</P>
<P align=justify>{</P>
<P align=justify>//在窗口显示之前,更新状态</P>
<P align=justify>OnIdleUpdateCmdUI(TRUE, 0L);</P>
<P align=justify>}</P>
<P align=justify>CControlBar实现了OnInitialUpdate函数,通过它来处理WM_INITIALUPDATE消息。各个子类不必覆盖该消息处理函数。</P>
<P align=justify></P>
<LI>处理Idle消息更新工具条状态
<p>
<P align=justify>CControlBar使用OnIdleUpdateCmdUI消息处理函数处理IDLE消息。</P>
<P align=justify>LRESULT CControlBar::OnIdleUpdateCmdUI(WPARAM wParam, LPARAM)</P>
<P align=justify>{</P>
<P align=justify>// handle delay hide/show</P>
<P align=justify>BOOL bVis = GetStyle() &amp; WS_VISIBLE;</P>
<P align=justify>UINT swpFlags = 0;</P>
<P align=justify>if ((m_nStateFlags &amp; delayHide) &amp;&amp; bVis)</P>
<P align=justify>swpFlags = SWP_HIDEWINDOW;</P>
<P align=justify>else if ((m_nStateFlags &amp; delayShow) &amp;&amp; !bVis)</P>
<P align=justify>swpFlags = SWP_SHOWWINDOW;</P>
<P align=justify>m_nStateFlags &amp;= ~(delayShow|delayHide);</P>
<P align=justify>if (swpFlags != 0)</P>
<P align=justify>{</P>
<P align=justify>SetWindowPos(NULL, 0, 0, 0, 0, swpFlags|</P>
<P align=justify>SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>// the style must be visible and if it is docked</P>
<P align=justify>// the dockbar style must also be visible</P>
<P align=justify>if ((GetStyle() &amp; WS_VISIBLE) &amp;&amp;</P>
<P align=justify>(m_pDockBar == NULL || (m_pDockBar-&gt;GetStyle() &amp; WS_VISIBLE)))</P>
<P align=justify>{</P>
<P align=justify>//得到父边框窗口,状态更新消息将发送给它</P>
<P align=justify>CFrameWnd* pTarget = (CFrameWnd*)GetOwner();</P>
<P align=justify>if (pTarget == NULL || !pTarget-&gt;IsFrameWnd())</P>
<P align=justify>pTarget = GetParentFrame();</P>
<P align=justify>if (pTarget != NULL)</P>
<P align=justify>OnUpdateCmdUI(pTarget, (BOOL)wParam);</P>
<P align=justify>}</P>
<P align=justify>return 0L;</P>
<P align=justify>}</P>
<P align=justify>OnIdleUpdateCmdUI或者在初始化时被OnInitialUpdate调用,或者作为消息处理函数来处理WM_IDLEUPDATECMDUI消息。</P>
<P align=justify>CControlBar实现了OnIdleUpdateCmdUI函数,把具体的用户界面更新动作委托给虚拟函数OnUpdateCmdUI完成。</P>
<P align=justify>由于各个用户界面的特殊性,所以CControlBar本身没有实现OnUpdateCmdUI,而是留给各个派生类去实现。例如,CToolBar覆盖了OnUpdateCmdUI,其实现如下:</P>
<P align=justify>void CToolBar::OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler)</P>
<P align=justify>{</P>
<P align=justify>//定义一个CCmdUI对象,CToolCmdUI派生于CCmdUI</P>
<P align=justify>CToolCmdUI state;</P>
<P align=justify>//给CCmdUI的各个成员赋值</P>
<P align=justify>state.m_pOther = this;</P>
<P align=justify></P>
<P align=justify>//得到总的按钮数目</P>
<P align=justify>state.m_nIndexMax = (UINT)DefWindowProc(TB_BUTTONCOUNT, 0, 0);</P>
<P align=justify>//逐个按钮进行状态更新</P>
<P align=justify>for (state.m_nIndex = 0; state.m_nIndex &lt; state.m_nIndexMax; state.m_nIndex++)</P>
<P align=justify>{</P>
<P align=justify>//获取按钮状态信息</P>
<P align=justify>TBBUTTON button;</P>
<P align=justify>_GetButton(state.m_nIndex, &amp;button);</P>
<P align=justify>//得到按钮的ID</P>
<P align=justify>state.m_nID = button.idCommand;</P>
<P align=justify></P>
<P align=justify>// ignore separators</P>
<P align=justify>if (!(button.fsStyle &amp; TBSTYLE_SEP))</P>
<P align=justify>{</P>
<P align=justify>//优先让CToolBar对象处理状态更新消息</P>
<P align=justify>if (CWnd::OnCmdMsg(state.m_nID, </P>
<P align=justify>CN_UPDATE_COMMAND_UI, &amp;state, NULL))</P>
<P align=justify>continue;//处理了更新消息,更新下一个按钮</P>
<P align=justify></P>
<P align=justify>//CToolBar没有处理,将发送给pTarget处理状态更新消息</P>
<P align=justify>//第二个参数bDisableIfNoHndler往下传</P>
<P align=justify>state.DoUpdate(pTarget, bDisableIfNoHndler);</P>
<P align=justify>}</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>//更新加到控制条中的对话框控制的状态</P>
<P align=justify>UpdateDialogControls(pTarget, bDisableIfNoHndler);</P>
<P align=justify>}</P>
<P align=justify>CToolBar的OnUpdateCmdUI函数完成工具条按钮的状态更新。它接受两个参数,参数1表示接收状态更新命令消息的对象,由CControlBar的函数OnIdleUpdateCmdUI传递过来,一般是边框窗口对象;参数2表示如果某条命令消息没有处理函数时,对应的用户接口对象是否被禁止。</P>
<P align=justify>OnUpdateCmdUI通过发送状态更新通知消息,逐个更新按钮的状态。更新消息首先让工具条对象处理,如果没有处理的话,送给边框窗口对象处理,导致状态更新命令消息的处理函数被调用,参见4.4.5节。</P>
<P align=justify>CStatusBar的OnUpdateCmdUI类似于此。</P>
<P align=justify>CDialogBar的OnUpdateCmdUI则调用了虚拟函数UpdateDialogControls来进行状态更新,CWnd提供了该函数的实现,过程类似于CToolBar的函数OnUpdateCmdUI。</P>
<P align=justify></P></LI></OL>
 楼主| 发表于 2006-12-8 10:38:30 | 显示全部楼层
<OL>
<LI>
<>菜单项的自动更新 </P>
<p></LI></OL>
< align=justify>那么,菜单项的自动更新如何实现的呢?OnInitMenuPopup在菜单项状态的自动更新中曾经被提到,其实现如下:</P>
< align=justify>void CFrameWnd::OnInitMenuPopup(CMenu* pMenu, UINT, BOOL bSysMenu)</P>
< align=justify>{</P>
< align=justify>AfxCancelModes(m_hWnd);</P>
< align=justify></P>
< align=justify>if (bSysMenu)</P>
<DIR>
< align=justify>return; // don't support system menu</P>
< align=justify></P></DIR>
< align=justify>ASSERT(pMenu != NULL);</P>
<P align=justify>// check the enabled state of various menu items</P>
<P align=justify></P>
<P align=justify>CCmdUI state;</P>
<P align=justify>state.m_pMenu = pMenu;</P>
<P align=justify>ASSERT(state.m_pOther == NULL);</P>
<P align=justify>ASSERT(state.m_pParentMenu == NULL);</P>
<P align=justify></P>
<P align=justify>//判断菜单是否在顶层菜单(top level menu)中弹出,如果这样</P>
<P align=justify>//则设置m_pParentMenu指向顶层菜单,否则m_pParentMenu</P>
<P align=justify>//为空,表示它是一个二级弹出菜单</P>
<P align=justify>HMENU hParentMenu;</P>
<P align=justify>//是否是浮动式的弹出菜单(floating pop up menu)</P>
<P align=justify>if (AfxGetThreadState()-&gt;m_hTrackingMenu == pMenu-&gt;m_hMenu)</P>
<DIR>
<P align=justify>state.m_pParentMenu = pMenu; // parent == child for tracking popup</P></DIR>
<P align=justify>else if ((hParentMenu = ::GetMenu(m_hWnd)) != NULL)//</P>
<P align=justify>{</P>
<DIR>
<P align=justify>CWnd* pParent = GetTopLevelParent();</P>
<P align=justify>// child windows don't have menus -- need to go to the top!</P>
<P align=justify>//得到顶层窗口的菜单</P>
<P align=justify>if (pParent != NULL &amp;&amp;</P>
<DIR>
<P align=justify>(hParentMenu = ::GetMenu(pParent-&gt;m_hWnd)) != NULL)</P></DIR>
<P align=justify>{</P>
<DIR>
<P align=justify>int nIndexMax = ::GetMenuItemCount(hParentMenu);</P>
<P align=justify>//确定顶层窗口的菜单是否包含本菜单项</P>
<P align=justify>for (int nIndex = 0; nIndex &lt; nIndexMax; nIndex++)</P>
<P align=justify>{</P>
<DIR>
<DIR>
<P align=justify>if (::GetSubMenu(hParentMenu, nIndex) == pMenu-&gt;m_hMenu)</P>
<P align=justify>{</P>
<DIR>
<P align=justify>//顶层窗口菜单是本菜单的父菜单</P>
<P align=justify>state.m_pParentMenu = CMenu::FromHandle(hParentMenu);</P>
<P align=justify>break;</P></DIR>
<P align=justify>}</P></DIR></DIR>
<P align=justify>}</P></DIR>
<P align=justify>}</P></DIR>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>//本菜单的菜单项(menu item)数量</P>
<P align=justify>state.m_nIndexMax = pMenu-&gt;GetMenuItemCount();</P>
<P align=justify>//对所有菜单项逐个进行状态更新</P>
<P align=justify>for (state.m_nIndex = 0; state.m_nIndex &lt; state.m_nIndexMax;</P>
<P align=justify>state.m_nIndex++)</P>
<P align=justify>{</P>
<DIR>
<P align=justify>state.m_nID = pMenu-&gt;GetMenuItemID(state.m_nIndex);</P>
<P align=justify>if (state.m_nID == 0)</P>
<DIR>
<P align=justify>continue; // menu separator or invalid cmd - ignore it</P>
<P align=justify></P></DIR>
<P align=justify>ASSERT(state.m_pOther == NULL);</P>
<P align=justify>ASSERT(state.m_pMenu != NULL);</P>
<P align=justify>if (state.m_nID == (UINT)-1)</P>
<P align=justify>{</P>
<DIR>
<P align=justify>// 可能是一个popup菜单,得到其第一个子菜单项目</P>
<P align=justify>state.m_pSubMenu = pMenu-&gt;GetSubMenu(state.m_nIndex);</P>
<P align=justify>if (state.m_pSubMenu == NULL ||</P>
<DIR>
<DIR>
<P align=justify>(state.m_nID = state.m_pSubMenu-&gt;GetMenuItemID(0)) == 0 ||</P>
<P align=justify>state.m_nID == (UINT)-1)</P></DIR></DIR>
<P align=justify>{</P>
<DIR>
<DIR>
<P align=justify>continue; // 找不到popup菜单的子菜单项</P></DIR></DIR>
<P align=justify>}</P>
<P align=justify>//popup菜单不会被自动的禁止</P>
<P align=justify>state.DoUpdate(this, FALSE); </P></DIR>
<P align=justify>}</P>
<P align=justify>else</P>
<P align=justify>{</P>
<DIR>
<P align=justify>//正常的菜单项,若边框窗口的m_bAutoMenuEnable设置为</P>
<P align=justify>//TURE且菜单项非系统菜单,则自动enable/disable该菜单项</P>
<P align=justify>state.m_pSubMenu = NULL;</P>
<P align=justify>state.DoUpdate(this, m_bAutoMenuEnable &amp;&amp; state.m_nID &lt; 0xF000);</P></DIR>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>//经过菜单状态的更新处理,可能增加或删除了一些菜单项</P>
<P align=justify>UINT nCount = pMenu-&gt;GetMenuItemCount();</P>
<P align=justify>if (nCount &lt; state.m_nIndexMax)</P>
<P align=justify>{</P>
<DIR>
<P align=justify>state.m_nIndex -= (state.m_nIndexMax - nCount);</P>
<P align=justify>while (state.m_nIndex &lt; nCount &amp;&amp;</P>
<DIR>
<DIR>
<P align=justify>pMenu-&gt;GetMenuItemID(state.m_nIndex) == state.m_nID)</P></DIR></DIR>
<P align=justify>{</P>
<DIR>
<DIR>
<P align=justify>state.m_nIndex++;</P></DIR></DIR>
<P align=justify>}</P></DIR>
<P align=justify>}</P>
<P align=justify>state.m_nIndexMax = nCount;</P>
<P align=justify>}</P></DIR>
<P align=justify>}</P>
<P align=justify>菜单弹出之前,发送WM_INITMENUPOPUP消息,OnInitMenuPopup消息处理函数被调用,逐个更新菜单项目(menu item)的状态。程序员可以处理它们对应的状态更新消息,禁止/允许菜单项目被使用(disable/enable),在菜单项目上打钩或者取消(checked/unchecked),等等。</P>
<OL>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>显示或者隐藏工具栏和状态栏</B>
<p></LI></OL></OL></OL></OL>
<P align=justify>这里讨论显示或者隐藏工具栏、状态栏的操作,以及工具栏、状态栏被显示/隐藏时,相关的两个菜单项ID_VIEW_STATUS_BAR、ID_VIEW_TOOLBAR的状态更新。这两个菜单命令及对应的状态更新命令是标准命令消息所包含的。MFC边框窗口实现了菜单命令消息的处理和菜单项状态的更新。</P>
<P align=justify>CFrameWnd提供了OnBarCheck来响应与ID_VIEW_STATUS_BAR、ID_VIEW_TOOLBAR菜单项对应的命令。</P>
<P align=justify>消息映射:</P>
<P align=justify>ON_COMMAND_EX(ID_VIEW_STATUS_BAR, OnBarCheck)</P>
<P align=justify>ON_COMMAND_EX(ID_VIEW_TOOLBAR, OnBarCheck)</P>
<P align=justify>这里,使用了扩展命令消息映射宏把ID_VIEW_STATUS_BAR和ID_VIEW_TOOLBAR命令映射给同一个函数OnBarCheck处理。</P>
<P align=justify>OnBarCheck函数的实现:</P>
<P align=justify>BOOL CFrameWnd::OnBarCheck(UINT nID)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(ID_VIEW_STATUS_BAR == AFX_IDW_STATUS_BAR);</P>
<P align=justify>ASSERT(ID_VIEW_TOOLBAR == AFX_IDW_TOOLBAR);</P>
<P align=justify></P>
<P align=justify>//得到工具条或者状态条</P>
<P align=justify>CControlBar* pBar = GetControlBar(nID);</P>
<P align=justify>if (pBar != NULL)</P>
<P align=justify>{</P>
<DIR>
<P align=justify>//若控制条可见,则隐藏它;否则,显示它</P>
<P align=justify>ShowControlBar(pBar, (pBar-&gt;GetStyle() &amp; WS_VISIBLE) == 0, FALSE);</P>
<P align=justify>//处理完毕</P>
<P align=justify>return TRUE;</P></DIR>
<P align=justify>}</P>
<P align=justify>//可以让下一个命令目标继续处理</P>
<P align=justify>return FALSE;</P>
<P align=justify>}</P>
<P align=justify>由于是扩展映射宏定义的消息处理函数,所以OnBarCheck函数有一个UINT类型的参数和一个BOOL返回值。</P>
<P align=justify>当用户从“View”菜单选择打了钩的“Toolbar”时,消息处理函数OnBarCheck被调用,参数就是菜单项的ID号ID_VIEW_TOOLBAR,它等于工具条的子窗口IDAFX_IDW_TOOLBAR。处理结果,工具条被隐藏;当再次选择该菜单项则工具条被显示。</P>
<P align=justify>处理状态条的过程类似于工具条的处理。</P>
<P align=justify>ShowControlBar是CFrameWnd的成员函数,参数1表示控制条对象指针,参数2表示显示(TRUE)或者隐藏(FALSE),参数3表示是立即显示(FALSE)或者延迟显示(TRUE)。</P>
<P align=justify></P>
<P align=justify>如果工具条或者状态条被隐藏,则相应的菜单项ID_VIEW_STATUS_BAR 或者ID_VIEW_TOOLBAR 变成uncheked(菜单项被标记为没有选择),否则,checked(菜单项被标记选择)。CFrameWnd实现了这两个菜单项的状态更新处理,列举其中一个如下:</P>
<P align=justify>声明处理ID_VIEW_TOOLBAR的状态更新消息:</P>
<P align=justify>ON_UPDATE_COMMAND_UI(ID_VIEW_TOOLBAR, OnUpdateControlBarMenu)</P>
<P align=justify>函数的实现:</P>
<P align=justify>void CFrameWnd::OnUpdateControlBarMenu(CCmdUI* pCmdUI)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(ID_VIEW_STATUS_BAR == </P>
<P align=justify>AFX_IDW_STATUS_BAR);</P>
<P align=justify>ASSERT(ID_VIEW_TOOLBAR == AFX_IDW_TOOLBAR);</P>
<P align=justify></P>
<P align=justify>CControlBar* pBar = GetControlBar(pCmdUI-&gt;m_nID);</P>
<P align=justify>//存在工具栏</P>
<P align=justify>if (pBar != NULL)</P>
<P align=justify>{</P>
<P align=justify>//工具条窗口被显示则checked,被隐藏则uncheked</P>
<DIR>
<P align=justify>pCmdUI-&gt;SetCheck((pBar-&gt;GetStyle() &amp; WS_VISIBLE) != 0);</P>
<P align=justify>return;</P></DIR>
<P align=justify>}</P>
<P align=justify>pCmdUI-&gt;ContinueRouting();</P>
<P align=justify>}</P>
<P align=justify>GetControlBar是CFrameWnd的成员函数,用来返回边框窗口的指定ID的控制条对象(指定ID是控制条的子窗口ID)。</P>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>泊位和漂浮</B>
<p></LI></OL></OL></OL>
<P align=justify>工具条可以泊位在边框窗口的任一边(上、下、左、右),或者漂浮在屏幕上的任何地方。</P>
<OL>
<P align=justify>
<LI>实现泊位的方法
<p>
<P align=justify>首先,边框窗口调用CFrameWnd::EnableDocking函数使控制条泊位在边框窗口中有效,指明在边框窗口的哪边接受泊位。如果想在任何边都可以泊位,则使用参数CBRS_ALIGN_ANY。</P>
<P align=justify>然后,工具条调用ControlBar::EnableDocking使泊位对工具条有效,如果在调用ControlBar::EnableDocking时指定的泊位目的边和边框窗口能够泊位的边不符合,那么工具条不能泊位,它将漂浮。</P>
<P align=justify>最后,边框窗口调用CFrameWnd:ockControlBar泊位工具条。</P>
<P align=justify></P>
<LI>泊位后形成窗口层次关系
<p>
<P align=justify>边框窗口、泊位条、工具条的包含关系如下:</P>
<P align=justify>边框窗口</P>
<P align=justify>泊位条1</P>
<P align=justify>工具条1</P>
<P align=justify>工具条2</P>
<P align=justify>…</P>
<P align=justify>泊位条2</P>
<P align=justify>…</P>
<P align=justify>边框窗口包含1到4个泊位条子窗口,每个泊位条包含若干个控制条子窗口。</P>
<P align=justify></P>
<LI>泊位的实现
<p>
<P align=justify>CFrameWnd::EnableDocking指定哪边接受泊位,则为泊位准备一个泊位条。泊位条用CDockBar描述,派生于CControlBar。如果指定任何边都可以泊位,则创建四个CDockBar对象和对应的HWND窗口。然后,调用ControlBar::EnableDocking在对应的泊位条内安置工具条。</P>
<P align=justify>MFC设计了CDockBar类和CFrameWnd的一些函数来实现泊位,具体代码实现在此不作详细讨论。</P>
<P align=justify></P>
<LI>实现漂浮工具条的方法:
<p>
<P align=justify>边框窗口调用FloatControlBar实现工具条的漂浮。</P>
<P align=justify></P>
<LI>漂浮的实现:
<p></LI></OL>
<P align=justify>首先,创建一个微型漂浮边框窗口,该边框窗口有一个泊位条。</P>
<P align=justify>然后,在微型边框窗口的泊位条内放置工具条。</P>
<P align=justify>MFC设计了微型边框类CMiniFrameWnd,在此基础上派生出微型泊位边框窗口类CMiniDockFrameWnd。CMiniDockFrameWnd增加了一个CDockBar类型成员变量m_wndDockBar,即泊位条。</P>
<P align=justify>在CMiniDockFrameWnd对象被创建时,创建泊位条m_wndDockBar。泊位条m_wndDockBar的父窗口如同CMiniDockFrameWnd的父窗口一样,是调用FloatControlBar的边框窗口,而不是微型泊位边框窗口。微型边框窗口和泊位条创建完成之后,调用ControlBar:ockControlBar泊位工具条在CMiniDockFrameWnd窗口。</P>
<P align=justify>具体的代码实现略。</P>
<P align=justify><STRONG>SOCKET类的设计和实现</STRONG> </P>
<p>
<OL>
<P align=justify>
<LI><B>WinSock基本知识</B>
<p>
<P align=justify>这里不打算系统地介绍socket或者WinSock的知识。首先介绍WinSock API函数,讲解阻塞/非阻塞的概念;然后介绍socket的使用。</P>
<OL>
<P align=justify>
<LI><B>WinSock API</B>
<p>
<P align=justify>Socket接口是网络编程(通常是TCP/IP协议,也可以是其他协议)的API。最早的Socket接口是Berkeley接口,在Unxi操作系统中实现。WinSock也是一个基于Socket模型的API,在Microsoft Windows操作系统类中使用。它在Berkeley接口函数的基础之上,还增加了基于消息驱动机制的Windows扩展函数。Winscok1.1只支持TCP/IP网络,WinSock2.0增加了对更多协议的支持。这里,讨论TCP/IP网络上的API。</P>
<P align=justify>Socket接口包括三类函数:</P>
<P align=justify>第一类是WinSock API包含的Berkeley socket函数。这类函数分两部分。第一部分是用于网络I/O的函数,如</P>
<P align=justify>accept、Closesocket、connect、recv、recvfrom、Select、Send、Sendto</P>
<P align=justify>另一部分是不涉及网络I/O、在本地端完成的函数,如</P>
<P align=justify>bind、getpeername、getsockname、getsocketopt、htonl、htons、inet_addr、inet_nton</P>
<P align=justify>ioctlsocket、listen、ntohl、ntohs、setsocketopt、shutdow、socket等</P>
<P align=justify>第二类是检索有关域名、通信服务和协议等Internet信息的数据库函数,如</P>
<P align=justify>gethostbyaddr、gethostbyname、gethostname、getprotolbyname</P>
<P align=justify>getprotolbynumber、getserverbyname、getservbyport。</P>
<P align=justify>第三类是Berkekley socket例程的Windows专用的扩展函数,如gethostbyname对应的WSAAsynGetHostByName(其他数据库函数除了gethostname都有异步版本),select对应的WSAAsynSelect,判断是否阻塞的函数WSAIsBlocking,得到上一次Windsock API错误信息的WSAGetLastError,等等。</P>
<P align=justify>从另外一个角度,这些函数又可以分为两类,一是阻塞函数,一是非阻塞函数。所谓阻塞函数,是指其完成指定的任务之前不允许程序调用另一个函数,在Windows下还会阻塞本线程消息的发送。所谓非阻塞函数,是指操作启动之后,如果可以立即得到结果就返回结果,否则返回表示结果需要等待的错误信息,不等待任务完成函数就返回。</P>
<P align=justify>首先,异步函数是非阻塞函数;</P>
<P align=justify>其次,获取远地信息的数据库函数是阻塞函数(因此,WinSock提供了其异步版本);</P>
<P align=justify>在Berkeley socket函数部分中,不涉及网络I/O、本地端工作的函数是非阻塞函数;</P>
<P align=justify>在Berkeley socket函数部分中,网络I/O的函数是可阻塞函数,也就是它们可以阻塞执行,也可以不阻塞执行。这些函数都使用了一个socket,如果它们使用的socket是阻塞的,则这些函数是阻塞函数;如果它们使用的socket是非阻塞的,则这些函数是非阻塞函数。</P>
<P align=justify>创建一个socket时,可以指定它是否阻塞。在缺省情况下,Berkerley的Socket函数和WinSock都创建“阻塞”的socket。阻塞socket通过使用select函数或者WSAAsynSelect函数在指定操作下变成非阻塞的。WSAAsyncSelect函数原型如下。</P>
<P align=justify>int WSAAsyncSelect( </P>
<P align=justify>SOCKET s, </P>
<P align=justify>HWND hWnd, </P>
<P align=justify>u_int wMsg, </P>
<P align=justify>long lEvent </P>
<P align=justify>);</P>
<P align=justify>其中,参数1指定了要操作的socket句柄;参数2指定了一个窗口句柄;参数3指定了一个消息,参数4指定了网络事件,可以是多个事件的组合,如:</P>
<P align=justify>FD_READ 准备读</P>
<P align=justify>FD_WRITE 准备写</P>
<P align=justify>FD_OOB 带外数据到达</P>
<P align=justify>FD_ACCEPT 收到连接</P>
<P align=justify>FD_CONNECT 完成连接</P>
<P align=justify>FD_CLOSE 关闭socket。</P>
<P align=justify>用OR操作组合这些事件值,如FD_READ|FD_WRITE</P>
<P align=justify>WSAAsyncSelect函数表示对socket s监测lEvent指定的网络事件,如果有事件发生,则给窗口hWnd发送消息wMsg。</P>
<P align=justify>假定应用程序的一个socket s指定了监测FD_READ事件,则在FD_READ事件上变成非阻塞的。当read函数被调用时,不管是否读到数据都马上返回,如果返回一个错误信息表示还在等待,则在等待的数据到达后,消息wMsg发送给窗口hWnd,应用程序处理该消息读取网络数据。</P>
<P align=justify>对于异步函数的调用,以类似的过程最终得到结果数据。以gethostbyname的异步版本的使用为例进行说明。该函数原型如下:</P>
<P align=justify>HANDLE WSAAsyncGetHostByName( </P>
<P align=justify>HWND hWnd, </P>
<P align=justify>u_int wMsg, </P>
<P align=justify>const char FAR *name, </P>
<P align=justify>char FAR *buf, </P>
<P align=justify>int buflen </P>
<P align=justify>);</P>
<P align=justify>在调用WSAAsyncGetHostByName启动操作时,不仅指定主机名字name,还指定了一个窗口句柄hWnd,一个消息ID wMsg,一个缓冲区及其长度。如果不能立即得到主机地址,则返回一个错误信息表示还在等待。当要的数据到达时,WinSock DLL给窗口hWnd发送消息wMsg告知得到了主机地址,窗口过程从指定的缓冲区buf得到主机地址。</P>
<P align=justify>使用异步函数或者非阻塞的socket,主要是为了不阻塞本线程的执行。在多进程或者多线程的情况下,可以使用两个线程通过同步手段来完成异步函数或者非阻塞函数的功能。</P>
<P align=justify></P></LI></OL></LI></OL>
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2025-2-6 06:52

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表