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

 找回密码
 立即注册

QQ登录

只需一步,快速开始

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

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

楼主: abc

MFC 教程

[复制链接]
 楼主| 发表于 2006-12-8 10:25:40 | 显示全部楼层
< align=justify>HINSTANCE AfxFindResourceHandle(LPCTSTR lpszName, LPCTSTR lpszType):</P>
< align=justify>其中:</P>
< align=justify>参数1是要查找的资源名称,参数2是要查找的资源类型。</P>
< align=justify>返回包含指定资源的模块的句柄。</P>
< align=justify></P>
< align=justify>上述函数的查找算法如下:</P>
<OL>
< align=justify>
<LI>如果进程模块状态(主模块)不是系统模块,则使用::FindResource(下同)搜索它,成功则返回;
<p>
< align=justify></P>
<LI>如果没有找到,则遍历CDynLinkLibrary对象列表,搜索所有的非系统模块,成功则返回;
<p>
< align=justify></P>
<LI>如果没有找到,则检查主模块的语言资源,成功则返回;
<p>
< align=justify></P>
<LI>如果没有找到,并且主模块是系统模块,则搜索它,成功则返回;
<p>
<P align=justify></P>
<LI>如果没有找到,则遍历CDynLinkLibrary对象列表,搜索所有的系统模块,成功则返回;
<p>
<P align=justify></P>
<LI>如果没有找到,则使用AfxGetResourceHanlde返回应用程序的资源。
<p></LI></OL>
<P align=justify>需要指出的是,遍历CDynLinkLibrary对象列表时,必须采取同步措施,防止其他线程改变链表。MFC是通过锁定全局变量CRIT_DYNLINKLIST来实现的,类似的全局变量MFC定义了多个。</P>
<P align=justify></P>
<P align=justify>运行时类信息的查找算法类似。</P>
<P align=justify>3.3.4节指出,对象进行“&lt;&lt;”序列化操作时,首先需要搜索到指定类的运行时信息,方法如下:</P>
<P align=justify>CRuntimeClass* PASCAL CRuntimeClass:oad(</P>
<DIR>
<P align=justify>CArchive&amp; ar, UINT* pwSchemaNum)</P></DIR>
<OL>
<P align=justify>
<LI>遍历主模块的CRuntimeClass对象列表m_classList,搜索主模块是否实现了指定的CRuntimeClass类;
<p>
<P align=justify></P>
<LI>遍历CDynLinkLibrary对象列表m_libraryList;对每一个CDynLinkLibrary对象,遍历它的CRuntimeClass对象列表m_classList。这样,所有的扩展DLL模块的CRuntimeClass对象都会被搜索到。
<p></LI></OL>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>模块信息的显示</B>
<p>
<P align=justify>遍历模块状态和CDynLinkLibrary列表,可以显示模块状态及其扩展模块状态的有关信息。下面,给出一个实现,它显示程序的当前模块名称、句柄和初始化的CRuntimeClass类,然后显示所有扩展模块的名称名称、句柄和初始化的CRuntimeClass类。</P>
<P align=justify></P>
<P align=justify>#ifdef _DEBUG</P>
<P align=justify>AFX_MODULE_STATE* pState = AfxGetModuleState();</P>
<P align=justify>//显示应用程序的名称和句柄</P>
<P align=justify>TRACE("APP %s HANDLE %x\r\n", pState-&gt;m_lpszCurrentAppName,</P>
<P align=justify>pState-&gt;m_hCurrentInstanceHandle);</P>
<P align=justify>TCHAR szT[256];</P>
<P align=justify>int nClasses;</P>
<P align=justify>nClasses=0;</P>
<P align=justify>//显示CRuntimeClass类信息</P>
<P align=justify>AfxLockGlobals(CRIT_RUNTIMECLASSLIST);</P>
<P align=justify>for (CRuntimeClass* pClass = pModuleState-&gt;m_classList; </P>
<P align=justify>pClass != NULL;pClass = pClass-&gt;m_pNextClass)</P>
<P align=justify>{</P>
<P align=justify>nClasses++;</P>
<P align=justify>TRACE("CRuntimeClass: %s\r\n",pClass-&gt;m_lpszClassName, );</P>
<P align=justify>}</P>
<P align=justify>AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);</P>
<P align=justify>TRACE("all %d classes\r\n", nClasses);</P>
<P align=justify></P>
<P align=justify>//遍历CDynLinkLibrary列表</P>
<P align=justify>AfxLockGlobals(CRIT_DYNLINKLIST);</P>
<P align=justify>for (CDynLinkLibrary* pDLL = pState-&gt;m_libraryList; pDLL != NULL;</P>
<P align=justify>pDLL = pDLL-&gt;m_pNextDLL)</P>
<P align=justify>{</P>
<P align=justify>// 得到模块名并且显示</P>
<P align=justify>TCHAR szName[64];</P>
<P align=justify>GetModuleFileName(pDLL-&gt;m_hModule, szName, sizeof(szName));</P>
<P align=justify>TRACE("MODULE %s HANDLE IS %x \r\n", szName, pDLL-&gt;m_hModule);</P>
<P align=justify></P>
<P align=justify>//得到CRuntimeClass信息并显示</P>
<P align=justify>nClasses = 0;</P>
<P align=justify>for (CRuntimeClass* pClass = pDLL-&gt;m_classList;</P>
<P align=justify>pClass != NULL; pClass = pClass-&gt;m_pNextClass)</P>
<P align=justify>{</P>
<P align=justify>nClasses++;</P>
<P align=justify>TRACE("CRuntimeClass: %s\r\n",pClass-&gt;m_lpszClassName, );</P>
<P align=justify>}</P>
<P align=justify>wsprintf(szT, _T(" Module %s has %d classes"),szName, nClasses);</P>
<P align=justify>}</P>
<P align=justify>AfxUnlockGlobals(CRIT_DYNLINKLIST);</P>
<P align=justify>#endif</P>
<P align=justify>使用MFC提供的调试函数AfxDoForAllClasses可以得到DLL模块的输出CRuntimeClass类的信息。上述实现类似于AfxDoForAllClasses函数的处理,只不过增加了模块名和模块句柄信息。</P>
<P align=justify></P>
<LI><B>模块-线程状态的作用</B>
<p>
<P align=justify>由模块-线程状态类的定义可知,一个模块-线程状态包含了几类Windows对象—MFC对象的映射。下面讨论它们的作用。</P>
<OL>
<P align=justify>
<LI><B>只能访问本线程MFC对象的原因</B>
<p></LI></OL></LI></OL></OL></OL>
<P align=justify>MFC规定:</P>
<OL>
<P align=justify>
<LI>不能从一个非MFC线程创建和访问MFC对象
<p>
<P align=justify>如果一个线程被创建时没有用到CWinThread对象,比如,直接使用“C”的_beginthread或者_beginthreadex创建的线程,则该线程不能访问MFC对象;换句话说,只有通过CWinThread创建MFC线程对象和Win32线程,才可能在创建的线程中使用MFC对象。</P>
<P align=justify></P>
<LI>一个线程仅仅能访问它所创建的MFC对象
<p></LI></OL>
<P align=justify></P>
<DIR>
<P align=justify>这两个规定的原因是:</P></DIR>
<P align=justify>为了防止多个线程并发地访问同一个MFC对象,MFC对象和Windows对象之间有一个一一对应的关系,这种关系以映射的形式保存在创建线程的当前模块的模块-线程状态信息中。当一个线程使用某个MFC对象指针P时,ASSERT_VALID(P)将验证当前线程的当前模块是否有Windows句柄和P对应,即是否创建了P所指的Windows对象,验证失败导致ASSERT断言中断程序的执行。如果一个线程要使用其他线程的Windows对象,则必须传递Windows对象句柄,不能传递MFC对象指针。</P>
<P align=justify>当然一般来说,MFC应用程序仅仅在Debug版本下才检查这种映射关系,所以访问其他线程的MFC对象的程序在Realease版本下表面上不会有问题,但是MFC对象被并发访问的后果是不可预见的。</P>
<OL>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>实现MFC对象和Windows对象之间的映射</B>
<p></LI></OL></OL></OL></OL>
<P align=justify>MFC提供了几个函数完成MFC对象和Windows对象之间的映射或者解除这种映射关系,以及从MFC对象得到Windows对象或者从Windows对象得到或创建相应的MFC对象。</P>
<P align=justify>每一个MFC对象类都有成员函数Attach和Detach,FromHandle和FromHandlePermanent,AssertValid。这些成员函数的形式如下:</P>
<UL>
<P align=justify>
<LI>Attach(HANDLE Windows_Object_Handle)
<p></LI></UL>
<P align=justify>例如:CWnd类的是Attach(HANLDE hWnd),CDC类的是Attach(HDC hDc)。</P>
<P align=justify>Attach用来把一个句柄永久性(Perment)地映射到一个MFC对象上:它把一个Windows对象捆绑(Attach)到一个MFC对象上,MFC对象的句柄成员变量赋值为Windows对象句柄,该MFC对象应该已经存在,但是句柄成员变量为空。</P>
<UL>
<P align=justify>
<LI>Detach()
<p></LI></UL>
<P align=justify>Detach用来取消Windows对象到MFC对象的永久性映射。如果该Windows对象有一个临时的映射存在,则Detach不理会它。MFC让线程的Idle清除临时映射和临时MFC对象。</P>
<UL>
<P align=justify>
<LI>FromHandle(HANDLE Windows_Object)
<p></LI></UL>
<P align=justify>它是一个静态成员函数。如果该Windows对象没有映射到一个MFC对象,FromHandle则创建一个临时的MFC对象,并把Windows对象映射到临时的MFC对象上,然后返回临时MFC对象。</P>
<UL>
<P align=justify>
<LI>FromHandlePermanent(HANDLE Windows_Object)
<p></LI></UL>
<P align=justify>它是一个静态成员函数。如果该Windows对象没有永久地映射到一个MFC对象上,则返回NULL,否则返回对应的MFC对象。</P>
<UL>
<P align=justify>
<LI>AssertValid()
<p></LI></UL>
<P align=justify>它是从CObject类继承来的虚拟函数。MFC覆盖该函数,实现了至少一个功能:判断当前MFC对象的指针this是否映射到一个对应的可靠的Windows对象。</P>
<P align=justify></P>
<P align=justify>图 9-9示意了MFC对映射结构的实现层次,对图9-9解释如下。</P><IMG src="http://www.vczx.com/tutorial/mfc/image151.gif" align=left>
<P align=justify></P>
<P align=justify>图中上面的虚线框表示使用映射关系的高层调用,包括上面讲述的几类函数。MFC和应用程序通过它们创建、销毁、使用映射关系。</P>
<P align=justify>图中中间的虚线框表示MFC使用CHandleMap类实现对映射关系的管理。一个CHandleMap对象可以通过两个成员变量来管理两种映射数据:临时映射和永久映射。模块-线程状态给每一类MFC对象分派一个CHandleMap对象来管理其映射数据(见模块-线程类的定义),例如m_pmapHWND所指对象用来保存CWnd对象(或派生类对象)和Windows window之间的映射。</P>
<P align=justify>下面的虚线框表示映射关系的最底层实现,MFC使用通用类CMapPtrToPtr来管理MFC对象指针和Windows句柄之间的映射数据。</P>
<P align=justify>对本节总结如下:</P>
<UL>
<P align=justify>
<LI>MFC的映射数据保存在模块-线程状态中,是线程和模块局部的。每个线程管理自己映射的数据,其他线程不能访问到本线程的映射数据,也就不允许使用本线程的MFC对象。
<p>
<P align=justify></P>
<LI>每一个MFC对象类(CWnd、CDC等)负责创建或者管理这类线程-模块状态的对应CHandleMap类对象。例如,CWnd::Attach创建一个永久性的映射保存在m_pmapHwnd所指对象中,如果m_pmapHand还没有创建,则使用AfxMapHWND创建相应的CHandleMap对象。
<p>
<P align=justify></P>
<LI>映射分两类:永久性的或者临时的。
<p></LI></UL>
<OL>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>临时对象的处理</B>
<p></LI></OL></OL></OL></OL>
<P align=justify>在2.4节就曾经提到了临时对象,现在是深入了解它们的时候了。</P>
<OL>
<P align=justify>
<LI>临时对象指MFC对象,是MFC或者程序员使用FromHandle或者SelectObject等从一个Windows对象句柄创建的对应的MFC对象。
<p>
<P align=justify></P>
<LI>在模块-线程状态中,临时MFC对象的映射是和永久映射分开保存的。
<p>
<P align=justify></P>
<LI>临时MFC对象在使用完毕后由MFC框架自动删除,MFC在线程的Idle处理中删除本线程的临时MFC对象,为了防止并发修改,通过线程状态m_nTempMapLock(等于0,可以修改,大于0,等待)来同步。所以,临时MFC对象不能保存备用。
<p></LI></OL>
<OL>
<OL>
<P align=justify>
<LI><B>状态对象的删除和销毁</B>
<p></LI></OL></OL>
<P align=justify>至此,本章讨论了MFC的线程局部存储机制,MFC状态的定义、实现和用途。在程序或者DLL退出之前,模块状态被销毁;在线程退出时,线程状态被销毁。状态对象被销毁之前,它活动期间所动态创建的对象被销毁,动态分配的内存被释放。</P>
<P align=justify>先解释几个函数:</P>
<P align=justify>AfxTermExtensionModule(HANDLE hInstanceOfDll,BOOL bAll);</P>
<P align=justify>若bAll为真,则该函数销毁本模块(hInstanceOfDll标识的模块)的模块状态的m_libraryList列表中所有动态分配的CDynLinkLibrary对象,否则,该函数清理本DLL动态分配的CDynLinkLibrary对象,并调用AfxTerLocalData释放本DLL模块为当前线程的线程局部变量分配的堆空间。</P>
<P align=justify>AfxTermLocalData(HANDLE hInstance, BOOL bAll);</P>
<P align=justify>若bAll为真,则删除MFC线程局部存储的所有槽的指针所指的对象,也就是销毁当前线程的全部局部变量,释放为这些线程局部变量分配的内存;否则,仅仅删除、清理当前线程在hInstance表示的DLL模块中创建的线程局部变量。</P>
<P align=justify></P>
<P align=justify>参与清理工作的函数有多种、多个,下面结合具体情况简要描述它们的作用。</P>
<P align=justify>(1)对动态链接到MFC DLL的应用程序</P>
<P align=justify>动态链接到MFC DLL的应用程序退出时,将在DllMain和RawDllMain处理进程分离时清理状态对象,该DllMain和RawDllMain是核心MFC DLL的入口和出口,在DLLINIT.CPP文件中实现,和进程分离时完成如下动作:</P>
<P align=justify>DllMain调用AfxTermExtensionModule(coreDll)清理核心MFC DLL的模块状态;调用AfxTermExtensionModule(coreDll, TRUE)清理OLE私有的模块状态;调用AfxTermLocalData(NULL, TRUE)释放本进程或者线程所有的局部变量。</P>
<P align=justify>RawDllMain在DllMain之后调用,它调用AfxTlsRealease;AfxTlsRealease减少对_afxThreadData的引用计数,如果引用数为零,则调用对应的CThreadSlotData析构函数清理_afxThreadData所指对象。</P>
<P align=justify>(2)对静态链接到MFC DLL的应用程序</P>
<P align=justify>如果是静态链接到MFC DLL的应用程序,由于RawDllMain和DllMain不起作用,将由一个静态变量析构时完成状态的清除:</P>
<P align=justify>有一个AFX_TERM_APP_STATE类型的静态变量,在程序结束时将被销毁,导致析构函数被调用,析构函数完成以下动作:</P>
<P align=justify>调用AfxTermLocalData(NULL, TRUE)释放本进程(主线程)的所用局部数据。</P>
<P align=justify>(3)对于动态链接到MFC DLL的规则DLL</P>
<P align=justify>对于动态链接到MFC DLL的规则DLL,将在RawDllMain和DllMain中清理状态对象。这两个函数在DllModule.cpp中定义,是规则DLL的入口和出口。当和进程分离时,分别有如下动作:</P>
<P align=justify>DllMain清除该模块的模块-线程状态中的所有临时映射,清除临时MFC对象;调用AfxWinTerm;调用AfxTermExtensionModule(controlDLL, TRUE),释放本DLL模块状态m_libraryList中的所有CDynLinkLibrary对象。</P>
<P align=justify>RawDllMain设置线程状态的模块状态指针,使它指向线程状态的m_PrevModuleState所指状态。</P>
<P align=justify>(4)对于静态链接到MFC DLL的DLL</P>
<P align=justify>对于静态链接到MFC DLL的DLL,只有DllMain会被调用,执行以下动作:</P>
<P align=justify>清除该模块的模块-线程状态中的所有临时映射,清除临时MFC对象;调用AfxWinTerm;调用AfxTermLocalData(hInstance, TRUE)清理本DLL模块的当前线程的线程局部数据。</P>
<P align=justify>另外,它定义一个_AFX_TERM_DLL_STATE类型的静态变量,在DLL退出时该变量被销毁,导致其析构函数被调用。析构函数完成如下动作:</P>
<P align=justify>调用AfxTermateLocalData(NULL, TRUE);调用AfxCriticlTerm结束关键变量;调用AfxTlsRealease。</P>
<P align=justify>(5)线程终止时</P>
<P align=justify>当使用AFxBeginThread创建的线程终止时,将调用AfxTermThread(HANDLE hInstance)作结束线程的清理工作(参数为NULL):销毁临时MFC对象,销毁本线程的线程局部变量,等等。</P>
<P align=justify>另外,当DLL模块和AfxBeginThread创建的线程分离时,也调用AfxTermThread(hInstance),参数是模块的句柄,销毁临时MFC对象,销毁本线程在本DLL创建的线程局部变量,等等。所以,AfxTermThread可能被调用两次。</P>
<P align=justify>最后,CThreadLocal和CProcessLocal的实例将被销毁,析构函数被调用:如果MFC线程局部存储空间的槽m_nSlot所指的线程局部对象还没有销毁,则销毁它。</P>
<P align=justify>_afxThreadData在MFC DLL的RawDllMain或者随着_AFX_TERM_APP_STATE析构函数的调用,_afxThreadData所指对象被销毁。_afxThreadData所指对象销毁之后,所有的状态相关的内存都被释放。</P>
 楼主| 发表于 2006-12-8 10:26:04 | 显示全部楼层
<OL start=10>
< align=justify>
<LI><B>内存分配方式和调试机制</B>
<p>
<OL>
< align=justify>
<LI><B>M内存分配</B>
<p>
<OL>
< align=justify>
<LI><B>内存分配函数</B>
<p></LI></OL></LI></OL></LI></OL>
< align=justify>MFCWin32或者C语言的内存分配API,有四种内存分配API可供使用。</P>
<OL>
< align=justify>
<LI>Win32的堆分配函数
<p>
< align=justify>每一个进程都可以使用堆分配函数创建一个私有的堆──调用进程地址空间的一个或者多个页面。DLL创建的私有堆必定在调用DLL的进程的地址空间内,只能被调用进程访问。</P>
< align=justify>HeapCreate用来创建堆;HeapAlloc用来从堆中分配一定数量的空间,HeapAlloc分配的内存是不能移动的;HeapSize可以确定从堆中分配的空间的大小;HeapFree用来释放从堆中分配的空间;HeapDestroy销毁创建的堆。</P>
< align=justify></P>
<LI>Windows传统的全局或者局部内存分配函数
<p>
< align=justify>由于Win32采用平面内存结构模式,Win32下的全局和局部内存函数除了名字不同外,其他完全相同。任一函数都可以用来分配任意大小的内存(仅仅受可用物理内存的限制)。用法可以和Win16下基本一样。</P>
< align=justify>Win32下保留这类函数保证了和Win16的兼容。</P>
<P align=justify></P>
<LI>C语言的标准内存分配函数
<p>
<P align=justify>C语言的标准内存分配函数包括以下函数:</P>
<P align=justify>malloc,calloc,realloc,free,等。</P>
<P align=justify>这些函数最后都映射成堆API函数,所以,malloc分配的内存是不能移动的。这些函数的调式版本为</P>
<P align=justify>malloc_dbg,calloc_dbg,realloc_dbg,free_dbg,等。</P>
<P align=justify></P>
<LI>Win32的虚拟内存分配函数
<p></LI></OL>
<P align=justify>虚拟内存API是其他API的基础。虚拟内存API以页为最小分配单位,X86上页长度为4KB,可以用GetSystemInfo函数提取页长度。虚拟内存分配函数包括以下函数:</P>
<UL>
<P align=justify>
<LI>LPVOID VirtualAlloc(LPVOID lpvAddress,
<p></LI></UL>
<DIR>
<P align=justify>DWORD cbSize,</P>
<P align=justify>DWORD fdwAllocationType,</P>
<P align=justify>DWORD fdwProtect);</P></DIR>
<P align=justify>该函数用来分配一定范围的虚拟页。参数1指定起始地址;参数2指定分配内存的长度;参数3指定分配方式,取值MEM_COMMINT或者MEM_RESERVE;参数4指定控制访问本次分配的内存的标识,取值为PAGE_READONLY、PAGE_READWRITE或者PAGE_NOACCESS。</P>
<UL>
<P align=justify>
<LI>LPVOID VirtualAllocEx(HANDLE process,
<p></LI></UL>
<DIR>
<P align=justify>LPVOID lpvAddress,</P>
<P align=justify>DWORD cbSize,</P>
<P align=justify>DWORD fdwAllocationType,</P>
<P align=justify>DWORD fdwProtect);</P></DIR>
<P align=justify>该函数功能类似于VirtualAlloc,但是允许指定进程process。VirtaulFree、VirtualProtect、VirtualQuery都有对应的扩展函数。</P>
<UL>
<P align=justify>
<LI>BOOL VirtualFree(LPVOID lpvAddress,
<p></LI></UL>
<DIR>
<P align=justify>DWORD dwSize,</P>
<P align=justify>DWORD dwFreeType);</P></DIR>
<P align=justify>该函数用来回收或者释放分配的虚拟内存。参数1指定希望回收或者释放内存的基地址;如果是回收,参数2可以指向虚拟地址范围内的任何地方,如果是释放,参数2必须是VirtualAlloc返回的地址;参数3指定是否释放或者回收内存,取值为MEM_DECOMMINT或者MEM_RELEASE。</P>
<UL>
<P align=justify>
<LI>BOOL VirtualProtect(LPVOID lpvAddress,
<p></LI></UL>
<DIR>
<P align=justify>DWORD cbSize,</P>
<P align=justify>DWORD fdwNewProtect,</P>
<P align=justify>PDWORD pfdwOldProtect);</P></DIR>
<P align=justify>该函数用来把已经分配的页改变成保护页。参数1指定分配页的基地址;参数2指定保护页的长度;参数3指定页的保护属性,取值PAGE_READ、PAGE_WRITE、PAGE_READWRITE等等;参数4用来返回原来的保护属性。</P>
<UL>
<P align=justify>
<LI>DWORD VirtualQuery(LPCVOID lpAddress,
<p></LI></UL>
<DIR>
<P align=justify>PMEMORY_BASIC_INFORMATION lpBuffer,</P>
<P align=justify>DWORD dwLength</P>
<P align=justify>);</P></DIR>
<P align=justify>该函数用来查询内存中指定页的特性。参数1指向希望查询的虚拟地址;参数2是指向内存基本信息结构的指针;参数3指定查询的长度。</P>
<P align=justify></P>
<UL>
<P align=justify>
<LI>BOOL VirtualLock(LPVOID lpAddress,DWORD dwSize);
<p></LI></UL>
<P align=justify>该函数用来锁定内存,锁定的内存页不能交换到页文件。参数1指定要锁定内存的起始地址;参数2指定锁定的长度。</P>
<UL>
<P align=justify>
<LI>BOOL VirtualUnLock(LPVOID lpAddress,DWORD dwSize);
<p></LI></UL>
<P align=justify>参数1指定要解锁的内存的起始地址;参数2指定要解锁的内存的长度。</P>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>C++的new 和 delete操作符</B>
<p></LI></OL></OL></OL>
<P align=justify>MFC定义了两种作用范围的new和delete操作符。对于new,不论哪种,参数1类型必须是size_t,且返回void类型指针。</P>
<OL>
<P align=justify>
<LI>全局范围内的new和delete操作符
<p>
<P align=justify>原型如下:</P>
<P align=justify>void _cdecl :perator new(size_t nSize);</P>
<P align=justify>void __cdecl operator delete(void* p);</P>
<P align=justify>调试版本:</P>
<P align=justify>void* __cdecl operator new(size_t nSize, int nType, </P>
<P align=justify>LPCSTR lpszFileName, int nLine)</P>
<P align=justify></P>
<LI>类定义的new和delete操作符
<p></LI></OL>
<P align=justify>原型如下:</P>
<P align=justify>void* PASCAL classname:perator new(size_t nSize);</P>
<P align=justify>void PASCAL classname:perator delete(void* p);</P>
<P align=justify>类的operator new操作符是类的静态成员函数,对该类的对象来说将覆盖全局的operator new。全局的operator new用来给内部类型对象(如int)、没有定义operator new操作符的类的对象分配内存。</P>
<P align=justify>new操作符被映射成malloc或者malloc_dbg,delete被映射成free或者free_dbg。</P>
<OL>
<OL>
<P align=justify>
<LI><B>调试手段</B>
<p>
<P align=justify>MFC应用程序可以使用C运行库的调试手段,也可以使用MFC提供的调试手段。两种调试手段分别论述如下。</P>
<OL>
<P align=justify>
<LI><B>C运行库提供和支持的调试功能</B>
<p></LI></OL></LI></OL></OL>
<P align=justify>C运行库提供和支持的调试功能如下:</P>
<OL>
<P align=justify>
<LI>调试信息报告函数
<p>
<P align=justify>用来报告应用程序的调试版本运行时的警告和出错信息。包括:</P>
<P align=justify>_CrtDbgReport 用来报告调试信息;</P>
<P align=justify>_CrtSetReportMode 设置是否警告、出错或者断言信息;</P>
<P align=justify>_CrtSetReportFile 设置是否把调试信息写入到一个文件。</P>
<P align=justify></P>
<LI>条件验证或者断言宏:
<p>
<P align=justify>断言宏主要有:</P>
<P align=justify>assert 检验某个条件是否满足,不满足终止程序执行。</P>
<P align=justify>验证函数主要有:</P>
<P align=justify>_CrtIsValidHeapPointer 验证某个指针是否在本地堆中;</P>
<P align=justify>_CrtIsValidPointer 验证指定范围的内存是否可以读写;</P>
<P align=justify>_CrtIsMemoryBlock 验证某个内存块是否在本地堆中。</P>
<P align=justify></P>
<LI>内存(堆)调试:
<p></LI></OL>
<DIR>
<P align=justify>malloc_dbg 分配内存时保存有关内存分配的信息,如在什么文件、哪一行分配的内存等。有一系列用来提供内存诊断的函数:</P></DIR>
<P align=justify><B>_</B>CrtMemCheckpoint 保存内存快照在一个_CrtMemState结构中;</P>
<P align=justify>_CrtMemDifference 比较两个_CrtMemState;</P>
<P align=justify>_CrtMemDumpStatistics 转储输出一_CrtMemState结构的内容;</P>
<P align=justify>_CrtMemDumpAllObjectsSince 输出上次快照或程序开始执行以来在堆中分配的所有对象的信息;</P>
<P align=justify>_CrtDumpMemoryLeaks 检测程序执行以来的内存漏洞,如果有漏洞则输出所有分配的对象。</P>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>MFC提供的调试手段</B>
<p></LI></OL></OL></OL>
<P align=justify>MFC在C运行库提供和支持的调试功能基础上,设计了一些类、函数等来协助调试。</P>
<OL>
<P align=justify>
<LI>MFC的TRACE、ASSERT
<p>
<P align=justify>ASSERT</P>
<P align=justify>使用ASSERT断言判定程序是否可以继续执行。</P>
<P align=justify>TRACE</P>
<P align=justify>使用TRACE宏显示或者打印调试信息。TRACE是通过函数AfxTrace实现的。由于AfxTrace函数使用了cdecl调用约定,故可以接受个数不定的参数,如同printf函数一样。它的定义和实现如下:</P>
<P align=justify>void AFX_CDECL AfxTrace(LPCTSTR lpszFormat, ...)</P>
<P align=justify>{</P>
<P align=justify>#ifdef _DEBUG // all AfxTrace output is controlled by afxTraceEnabled</P>
<P align=justify>if (!afxTraceEnabled)</P>
<P align=justify>return;</P>
<P align=justify>#endif</P>
<P align=justify></P>
<P align=justify>//处理个数不定的参数</P>
<P align=justify>va_list args;</P>
<P align=justify>va_start(args, lpszFormat);</P>
<P align=justify></P>
<P align=justify>int nBuf;</P>
<P align=justify>TCHAR szBuffer[512];</P>
<P align=justify></P>
<P align=justify>nBuf = _vstprintf(szBuffer, lpszFormat, args);</P>
<P align=justify>ASSERT(nBuf &lt; _countof(szBuffer));</P>
<P align=justify></P>
<P align=justify>if ((afxTraceFlags &amp; traceMultiApp) &amp;&amp; (AfxGetApp() != NULL))</P>
<P align=justify>afxDump &lt;&lt; AfxGetApp()-&gt;m_pszExeName &lt;&lt; ": ";</P>
<P align=justify>afxDump &lt;&lt; szBuffer;</P>
<P align=justify></P>
<P align=justify>va_end(args);</P>
<P align=justify>}</P>
<P align=justify>#endif //_DEBUG</P>
<P align=justify></P>
<P align=justify>在程序源码中,可以控制是否显示跟踪信息,显示什么跟踪信息。如果全局变量afxTraceEnabled为TRUE,则TRACE宏可以输出;否则,没有TRACE信息被输出。如果通过afxTraceFlags指定了跟踪什么消息,则输出有关跟踪信息,例如为了指定“Multilple Application Debug”,令AfxTraceFlags|=traceMultiApp。可以跟踪的信息有:</P>
<P align=justify>enum AfxTraceFlags</P>
<P align=justify>{</P>
<P align=justify>traceMultiApp = 1, // multi-app debugging</P>
<P align=justify>traceAppMsg = 2, // main message pump trace (includes DDE)</P>
<P align=justify>traceWinMsg = 4, // Windows message tracing</P>
<P align=justify>traceCmdRouting = 8, // Windows command routing trace </P>
<P align=justify>//(set 4+8 for control notifications)</P>
<P align=justify>traceOle = 16, // special OLE callback trace</P>
<P align=justify>traceDatabase = 32, // special database trace</P>
<P align=justify>traceInternet = 64 // special Internet client trace</P>
<P align=justify>};</P>
<P align=justify>这样,应用程序可以在需要的地方指定afxTraceEnabled的值打开或者关闭TRACE开关,指定AfxTraceFlags的值过滤跟踪信息。</P>
<P align=justify>Visual C++提供了一个TRACE工具,也可以用来完成上述功能。</P>
<P align=justify></P>
<P align=justify>为了显示消息信息,MFC内部定义了一个AFX_MAP_MESSAG类型的数组allMessages,储存了Windows消息和消息名映射对。例如:</P>
<P align=justify>allMessages[1].nMsg = WM_CREATE,</P>
<P align=justify>allMessages[1].lpszMsg = “WM_CREATE”</P>
<P align=justify>MFC内部还使用函数_AfxTraceMsg显示跟踪消息,它可以接收一个字符串和一个MSG指针,然后,把该字符串和MSG的各个域的信息组合成一个大的字符串并使用AfxTrace显示出来。</P>
<P align=justify>allMessages和函数_AfxTraceMsg的详细实现可以参见AfxTrace.cpp。</P>
<P align=justify></P>
<LI>MFC对象内容转储
<p>
<P align=justify>对象内容转储是CObject类提供的功能,所有从它派生的类都可以通过覆盖虚拟函数DUMP来支持该功能。在讲述CObject类时曾提到过。</P>
<P align=justify>虚拟函数Dump的定义:</P>
<P align=justify>class ClassName : public CObject</P>
<P align=justify>{</P>
<P align=justify>public:</P>
<P align=justify>#ifdef _DEBUG</P>
<P align=justify>virtual void Dump( CDumpContext&amp; dc ) const;</P>
<P align=justify>#endif</P>
<P align=justify>…</P>
<P align=justify>};</P>
<P align=justify>在使用Dump时,必须给它提供一个CDumpContext类型的参数,该参数指定的对象将负责输出调试信息。为此,MFC提供了一个预定义的全局CDumpContext对象afxDump,它把调试信息输送给调试器的调试窗口。从前面AfxTrace的实现可以知道,MFC使用了afxDump输出跟踪信息到调试窗口。</P>
<P align=justify>CDumpContext类没有基类,它提供了以文本形式输出诊断信息的功能。</P>
<P align=justify>例如:</P>
<P align=justify>CPerson* pMyPerson = new CPerson;</P>
<P align=justify>// set some fields of the CPerson object...</P>
<P align=justify>//...</P>
<P align=justify>// now dump the contents</P>
<P align=justify>#ifdef _DEBUG</P>
<P align=justify>pMyPerson-&gt;Dump( afxDump );</P>
<P align=justify>#endif</P>
<P align=justify></P>
<LI>MFC对象有效性检测
<p></LI></OL>
<P align=justify>对象有效性检测是CObject类提供的功能,所有从它派生的类都可以通过覆盖虚拟函数AssertValid来支持该功能。在讲述CObject类时曾提到过。</P>
<P align=justify>虚拟函数AssertValid的定义:</P>
<P align=justify>class ClassName : public CObject</P>
<P align=justify>{</P>
<P align=justify>public:</P>
<P align=justify>#ifdef _DEBUG</P>
<P align=justify>virtual void AssertValid( ) const;</P>
<P align=justify>#endif</P>
<P align=justify>… </P>
<P align=justify>};</P>
<P align=justify>使用ASSERT_VALID宏判断一个对象是否有效,该对象的类必须覆盖了AssertValid函数。形式为:ASSERT_VALID(pObject)。</P>
<P align=justify>另外,MFC提供了一些函数来判断地址是否有效,如:</P>
<P align=justify>AfxIsMemoryBlock,AfxIsString,AfxIsValidAddress。</P>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>内存诊断</B>
<p></LI></OL></OL></OL>
<P align=justify>MFC使用DEBUG_NEW来跟踪内存分配时的执行的源码文件和行数。</P>
<P align=justify>把#define new DEBUG_NEW插入到每一个源文件中,这样,调试版本就使用_malloc_dbg来分配内存。MFC Appwizard在创建框架文件时已经作了这样的处理。</P>
<OL>
<P align=justify>
<LI>AfxDoForAllObjects
<p>
<P align=justify>MFC提供了函数AfxDoForAllObjects来追踪动态分配的内存对象,函数原型如下:</P>
<P align=justify>void AfxDoForAllObjects( void (*pfn)(CObject* pObject, </P>
<P align=justify>void* pContext), void* pContext ); </P>
<P align=justify>其中:</P>
<P align=justify>参数1是一个函数指针,AfxDoForAllObjects对每个对象调用该指针表示的函数。</P>
<P align=justify>参数2将传递给参数1指定的函数。</P>
<P align=justify>AfxDoForAllObjects可以检测到所有使用new分配的CObject对象或者CObject类派生的对象,但全局对象、嵌入对象和栈中分配的对象除外。</P>
<P align=justify></P>
<LI>内存漏洞检测
<p></LI></OL>
<P align=justify>仅仅用于new的DEBUG版本分配的内存。</P>
<P align=justify>完成内存漏洞检测,需要如下系列步骤:</P>
<UL>
<P align=justify>
<LI>调用AfxEnableMemoryTracking(TRUE/FALSE)打开/关闭内存诊断。在调试版本下,缺省是打开的;关闭内存诊断可以加快程序执行速度,减少诊断输出。
<p>
<P align=justify></P>
<LI>使用MFC全局变量afxMemDF更精确地指定诊断输出的特征,缺省值是allocMemDF,可以取如下值或者这些值相或:
<p></LI></UL>
<P align=justify>afxMemDF,delayFreeMemDF,checkAlwaysMemDF</P>
<P align=justify>其中:allocMemDF表示可以进行内存诊断输出;delayFreeMemDF表示是否是在应用程序结束时才调用free或者delete,这样导致程序最大可能的分配内存;checkAlwaysMemDF表示每一次分配或者释放内存之后都调用函数AfxCheckMemory进行内存检测(AfxCheckMemory检查堆中所有通过new分配的内存(不含malloc))。</P>
<P align=justify>这一步是可选步骤,非必须。</P>
<UL>
<P align=justify>
<LI>创建一个CMemState类型的变量oldMemState,调用CMemState的成员函数CheckPoint获得初次内存快照。
<p>
<P align=justify></P>
<LI>执行了系列内存分配或者释放之后,创建另一个CMemState类型变量newMemState,调用CMemState的成员函数CheckPoint获得新的内存快照。
<p>
<P align=justify></P>
<LI>创建第三个CMemState类型变量difMemState,调用CMemState的成员函数Difference比较oldMemState和newMemState,结果保存在变量difMemState中。如果没有不同,则返回FALSE,否则返回TRUE。
<p>
<P align=justify></P>
<LI>如果不同,则调用成员函数DumpStatistics输出比较结果。
<p></LI></UL>
<P align=justify>例如:</P>
<P align=justify>// Declare the variables needed</P>
<P align=justify>#ifdef _DEBUG</P>
<DIR>
<P align=justify>CMemoryState oldMemState, newMemState, diffMemState;</P>
<P align=justify>oldMemState.Checkpoint();</P></DIR>
<P align=justify>#endif</P>
<P align=justify></P>
<P align=justify>// do your memory allocations and deallocations...</P>
<P align=justify>CString s = "This is a frame variable";</P>
<P align=justify>// the next object is a heap object</P>
<P align=justify>CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );</P>
<P align=justify></P>
<P align=justify>#ifdef _DEBUG</P>
<DIR>
<P align=justify>newMemState.Checkpoint();</P>
<P align=justify>if( diffMemState.Difference( oldMemState, newMemState ) )</P>
<P align=justify>{</P>
<DIR>
<P align=justify>TRACE( "Memory leaked!\n" );</P>
<P align=justify>diffMemState.DumpStatistics();</P>
<P align=justify>//or diffMemState.DumpAllObjectsSince();</P></DIR>
<P align=justify>}</P></DIR>
<P align=justify>#endif</P>
<P align=justify>MFC在应用程序(调试版)结束时,自动进行内存漏洞检测,如果存在漏洞,则输出漏洞的有关信息。</P>
 楼主| 发表于 2006-12-8 10:26:30 | 显示全部楼层
<OL start=11>
< align=justify>
<LI><B>MFC下的文件类</B>
<p>
<OL>
< align=justify>
<LI><B>文件操作的方法</B>
<p>
< align=justify>使用Visual C++编程,有如下方法进行文件操作:</P>
< align=justify>(1)使用标准C运行库函数,包括fopen、fclose、fseek等。</P>
< align=justify>(2)使用Win16下的文件和目录操作函数,如lopen、lclose、lseek等。不过,在Win32下,这些函数主要是为了和Win16向后兼容。</P>
< align=justify>(3)使用Win32下的文件和目录操作函数,如CreateFile,CopyFile,DeleteFile,FindNextFile,等等。</P>
< align=justify>Win32下,打开和创建文件都由CreateFile完成,成功的话,得到一个Win32下的句柄,这不同于“C”的fopen返回的句柄。在Win16下,该句柄和C运行库文件操作函数相容。但在Win32下,“C”的文件操作函数不能使用该句柄,如果需要的话,可以使用函数_open_osfhandle从Win32句柄得到一个“C”文件函数可以使用的文件句柄。</P>
< align=justify>关闭文件使用Win32的CloseHandle。</P>
< align=justify>在Win32下,CreateFile可以操作的对象除了磁盘文件外,还包括设备文件如通讯端口、管道、控制台输入、邮件槽等等。</P>
< align=justify>(4)使用CFile和其派生类进行文件操作。CFile从CObject派生,其派生类包括操作文本文件的CStdioFile,操作内存文件的CmemFile,等等。</P>
<P align=justify>CFile是建立在Win32的文件操作体系的基础上,它封装了部分Win32文件操作函数。</P>
<P align=justify></P>
<P align=justify>最好是使用CFile类(或派生类)的对象来操作文件,必要的话,可以从这些类派生自己的文件操作类。统一使用CFile的界面可以得到好的移植性。</P>
<P align=justify></P>
<LI><B>MFC的文件类</B>
<p>
<P align=justify>MFC用一些类来封装文件访问的Win32 API。以CFile为基础,从CFile派生出几个类,如CStdioFile,CMemFile,MFC内部使用的CMiororFile,等等。</P>
<OL>
<P align=justify>
<LI><B>CFile的结构</B>
<p>
<OL>
<P align=justify>
<LI><B>CFile定义的枚举类型</B>
<p></LI></OL></LI></OL></LI></OL></LI></OL>
<P align=justify>CFile类定义了一些和文件操作相关的枚举类型,主要有四种:OpenFlags,Attribute,SeekPosition,hFileNull。下面,分别解释这些枚举类型。</P>
<OL>
<P align=justify>
<LI>OpenFlags
<p>
<P align=justify>OpenFlags定义了13种文件访问和共享模式:</P>
<P align=justify>enum OpenFlags {</P>
<P align=justify>//第一(从右,下同)至第二位,打开文件时访问模式,读/写/读写</P>
<P align=justify>modeRead = 0x0000,</P>
<P align=justify>modeWrite = 0x0001,</P>
<P align=justify>modeReadWrite = 0x0002,</P>
<P align=justify>shareCompat = 0x0000, //32位MFC中没用</P>
<P align=justify>//第五到第七位,打开文件时的共享模式</P>
<P align=justify>shareExclusive = 0x0010,//独占方式,禁止其他进程读写</P>
<P align=justify>shareDenyWrite = 0x0020,//禁止其他进程写</P>
<P align=justify>shareDenyRead = 0x0030,//禁止其他进程读</P>
<P align=justify>shareDenyNone = 0x0040,//允许其他进程写</P>
<P align=justify>//第八位,打开文件时的文件继承方式</P>
<P align=justify>modeNoInherit = 0x0080,//不允许子进程继承</P>
<P align=justify>//第十三、十四位,是否创建新文件和创建方式</P>
<P align=justify>modeCreate = 0x1000,//创建新文件,文件长度0</P>
<P align=justify>modeNoTruncate = 0x2000,//创建新文件时如文件已存在则打开</P>
<P align=justify>//第十五、十六位,文件以二进制或者文本方式打开,在派生类CStdioFile中用</P>
<P align=justify>typeText = 0x4000,</P>
<P align=justify>typeBinary = (int)0x8000</P>
<P align=justify>};</P>
<P align=justify></P>
<LI>Attribute
<p>
<P align=justify>Attribute定义了文件属性:正常、只读、隐含、系统文件,文件或者目录等。</P>
<P align=justify>enum Attribute {</P>
<P align=justify>normal = 0x00,</P>
<P align=justify>readOnly = 0x01,</P>
<P align=justify>hidden = 0x02,</P>
<P align=justify>system = 0x04,</P>
<P align=justify>volume = 0x08,</P>
<P align=justify>directory = 0x10,</P>
<P align=justify>archive = 0x20</P>
<P align=justify>}</P>
<P align=justify></P>
<LI>SeekPosition
<p>
<P align=justify>SeekPosition定义了三种文件位置:头、尾、当前:</P>
<P align=justify>enum SeekPosition{</P>
<P align=justify>begin = 0x0,</P>
<P align=justify>current = 0x1,</P>
<P align=justify>end = 0x2</P>
<P align=justify>};</P>
<P align=justify></P>
<LI>hFileNull
<p></LI></OL>
<P align=justify>hFileNull定义了空文件句柄</P>
<P align=justify>enum { hFileNull = -1 };</P>
<OL>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>CFile的其他一些成员变量</B>
<p>
<P align=justify>CFile除了定义枚举类型,还定义了一些成员变量。例如:</P>
<P align=justify>UINT m_hFile</P>
<P align=justify>该成员变量是public访问属性,保存::CreateFile返回的操作系统的文件句柄。MFC重载了运算符号HFILE来返回m_hFile,这样在使用HFILE类型变量的地方可以使用CFile对象。</P>
<P align=justify></P>
<P align=justify>BOOL m_bCloseOnDelete;</P>
<P align=justify>CString m_strFileName;</P>
<P align=justify>这两个成员变量是protected访问属性。m_bCloseOnDelete用来指示是否在关闭文件时删除CFile对象;m_strFileName用来保存文件名。</P>
<P align=justify></P>
<LI><B>CFile的成员函数</B>
<p></LI></OL></OL></OL></OL>
<P align=justify>CFile的成员函数实现了对Win32文件操作函数的封装,完成以下动作:打开、创建、关闭文件,文件指针定位,文件的锁定与解锁,文件状态的读取和修改,等等。其中,用到了m_hFile文件句柄的一般是虚拟函数,和此无关的一般是静态成员函数。一般地,成员函数被映射到对应的Win32函数,如表11-1所示。</P>
<P align=center>表11-1 CFile函数对Win32文件函数的封装</P>
<TABLE cellSpacing=1 cellPadding=7 width=497 border=1>

<TR>
<TD vAlign=top width="6%">
<P align=justify>虚拟 </P></TD>
<TD vAlign=top width="5%">
<P align=justify>静态 </P></TD>
<TD vAlign=top width="39%">
<P align=justify>成员函数 </P></TD>
<TD vAlign=top width="50%">
<P align=justify>对应的Win32函数 </P></TD></TR>
<TR>
<TD vAlign=top colSpan=4>
<P align=justify>文件的创建、打开、关闭 </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>Abort </P></TD>
<TD vAlign=top width="50%">
<P align=justify>CloseHandle </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>Duplicate </P></TD>
<TD vAlign=top width="50%">
<P align=justify>DuplicateHandle </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>Open </P></TD>
<TD vAlign=top width="50%">
<P align=justify>CreateFile </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>Close </P></TD>
<TD vAlign=top width="50%">
<P align=justify>CloseHandle </P></TD></TR>
<TR>
<TD vAlign=top colSpan=4>
<P align=justify>文件的读写 </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>Read </P></TD>
<TD vAlign=top width="50%">
<P align=justify>ReadFile </P></TD></TR>
<TR>
<TD vAlign=top width="6%"> </TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>ReadHuge(向后兼容) </P></TD>
<TD vAlign=top width="50%">
<P align=justify>调用Read成员函数 </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>Write </P></TD>
<TD vAlign=top width="50%">
<P align=justify>WriteFile </P></TD></TR>
<TR>
<TD vAlign=top width="6%"> </TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>WriteHuage(向后兼容) </P></TD>
<TD vAlign=top width="50%">
<P align=justify>调用Write成员函数 </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>Flush </P></TD>
<TD vAlign=top width="50%">
<P align=justify>FlushFileBuffers </P></TD></TR>
<TR>
<TD vAlign=top colSpan=4>
<P align=justify>文件定位 </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>Seek </P></TD>
<TD vAlign=top width="50%">
<P align=justify>SetFilePointer </P></TD></TR>
<TR>
<TD vAlign=top width="6%"> </TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>SeekToBegin </P></TD>
<TD vAlign=top width="50%">
<P align=justify>调用Seek成员函数 </P></TD></TR>
<TR>
<TD vAlign=top width="6%"> </TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>SeekToEnd </P></TD>
<TD vAlign=top width="50%">
<P align=justify>调用Seek成员函数 </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>GetLength </P></TD>
<TD vAlign=top width="50%">
<P align=justify>调用Seek成员函数 </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>SetLength </P></TD>
<TD vAlign=top width="50%">
<P align=justify>SetEndOfFile </P></TD></TR>
<TR>
<TD vAlign=top colSpan=4>
<P align=justify>文件的锁定/解锁 </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>LockRange </P></TD>
<TD vAlign=top width="50%">
<P align=justify>LockFile </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>UnlockRange </P></TD>
<TD vAlign=top width="50%">
<P align=justify>UnlockFile </P></TD></TR>
<TR>
<TD vAlign=top colSpan=4>
<P align=justify>文件状态操作函数 </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>GetPosition </P></TD>
<TD vAlign=top width="50%">
<P align=justify>SetFilePointer </P></TD></TR>
<TR>
<TD vAlign=top width="6%"> </TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>GetStatus(CFileStatus&amp;) </P></TD>
<TD vAlign=top width="50%">
<P align=justify>GetFileTime,GetFileSize等 </P></TD></TR>
<TR>
<TD vAlign=top width="6%"> </TD>
<TD vAlign=top width="5%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="39%">
<P align=justify>GetStatus(LPSTR lpszFileName CFileStatus&amp;) </P></TD>
<TD vAlign=top width="50%">
<P align=justify>FindFirstFile </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>GetFileName </P></TD>
<TD vAlign=top width="50%">
<P align=justify>不是简单地映射到某个函数 </P></TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>GetFileTitle </P></TD>
<TD vAlign=top width="50%"> </TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>GetFilePath </P></TD>
<TD vAlign=top width="50%"> </TD></TR>
<TR>
<TD vAlign=top width="6%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="5%"> </TD>
<TD vAlign=top width="39%">
<P align=justify>SetFilePath </P></TD>
<TD vAlign=top width="50%"> </TD></TR>
<TR>
<TD vAlign=top width="6%"> </TD>
<TD vAlign=top width="5%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="39%">
<P align=justify>SetStatus </P></TD>
<TD vAlign=top width="50%"> </TD></TR>
<TR>
<TD vAlign=top colSpan=4>
<P align=justify>改名和删除 </P></TD></TR>
<TR>
<TD vAlign=top width="6%"> </TD>
<TD vAlign=top width="5%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="39%">
<P align=justify>Rename </P></TD>
<TD vAlign=top width="50%">
<P align=justify>MoveFile </P></TD></TR>
<TR>
<TD vAlign=top width="6%"> </TD>
<TD vAlign=top width="5%">
<P align=justify>√ </P></TD>
<TD vAlign=top width="39%">
<P align=justify>Remove </P></TD>
<TD vAlign=top width="50%">
<P align=justify>DeleteFile </P></TD></TR></TABLE>
<P align=center> </P>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>CFile的部分实现</B>
<p></LI></OL></OL></OL>
<P align=justify>这里主要讨论CFile对象的构造函数和文件的打开/创建的过程。</P>
<OL>
<P align=justify>
<LI>构造函数
<p></LI></OL>
<P align=justify>CFile有如下几个构造函数:</P>
<UL>
<P align=justify>
<LI>CFile()
<p></LI></UL>
<P align=justify>缺省构造函数,仅仅构造一个CFile对象,还必须使用Open成员函数来打开文件。</P>
<UL>
<P align=justify>
<LI>CFile(int hFile)
<p></LI></UL>
<P align=justify>已经打开了一个文件hFile,在此基础上构造一个CFile对象来给它打包。HFile将被赋值给CFile的成员变量m_hFile。</P>
<UL>
<P align=justify>
<LI>CFile(LPCTSTR lpszFileName, UINT nOpenFlags)
<p></LI></UL>
<P align=justify>指定一个文件名和文件打开方式,构造CFile对象,调用Open打开/创建文件,把文件句柄保存到m_hFile。</P>
<OL>
<P align=justify>
<LI>打开/创建文件
<p></LI></OL>
<P align=justify>Open的原型如下:</P>
<P align=justify>BOOL CFile::Open(LPCTSTR lpszFileName, UINT nOpenFlags,</P>
<P align=justify>CFileException* pException)</P>
<P align=justify>Open调用Win32函数::CreateFile打开文件,并把文件句柄保存到成员变量m_hFile中。</P>
<P align=justify>CreateFile函数的原型如下:</P>
<P align=justify>HANDLE CreateFile(</P>
<P align=justify>LPCTSTR lpFileName,// pointer to name of the file </P>
<P align=justify>DWORD dwDesiredAccess,// access (read-write) mode </P>
<P align=justify>DWORD dwShareMode,// share mode </P>
<P align=justify>LPSECURITY_ATTRIBUTES lpSecurityAttributes, //pointer to security descriptor </P>
<P align=justify>DWORD dwCreationDistribution,// how to create </P>
<P align=justify>DWORD dwFlagsAndAttributes,// file attributes </P>
<P align=justify>HANDLE hTemplateFile// handle to file with attributes to copy</P>
<P align=justify>);</P>
<P align=justify>显然,Open必须把自己的两个参数lpszFileName和nOpenFlags映射到CreateFile的七个参数上。</P>
<P align=justify>从OpenFlags的定义可以看出,(nOpenFlags &amp; 3)表示了读写标识,映射成变量dwAccess,可以取值为Win32的GENERIC_READ、GENERIC_WRITE、GENERIC_READ|GENERIC_WRITE。</P>
<P align=justify>(nOpenFlags &amp; 0x70)表示了共享模式,映射成变量dwShareMode,可以取值为Win32的FILE_SHARE_READ、FILE_SHARE_WRITE、FILE_SHARE_WRITE|FILE_SHARE_READ。</P>
<P align=justify>Open定义了一个局部的SECURITY_ATTRIBUTES变量sa,(nOpenFlags &amp; 0x80)被赋值给sa.bInheritHandle。</P>
<P align=justify>(nOpenFlags &amp; modeCreate)表示了创建方式,映射成变量dwCreateFlag,可以取值为Win32的OPEN_ALWAYS、CREATE_ALWAYS、OPEN_EXISTING。</P>
<P align=justify>在生成了上述参数之后,先调用::CreateFile:</P>
<P align=justify>HANDLE hFile =::CreateFile(lpszFileName,</P>
<DIR>
<P align=justify>dwAccess, dwShareMode, &amp;sa,</P>
<P align=justify>dwCreateFlag, FILE_ATTRIBUTE_NORMAL, NULL);</P></DIR>
<P align=justify>然后,hFile被赋值给成员变量m_hFile,m_bCloseOnDelete被设置为TRUE。</P>
<P align=justify></P>
<P align=justify>由上可以看出,CFile打开(创建)一个文件时大大简化了:: CreateFile函数的复杂性,即只需要指定一个文件名、一个打开文件的参数即可。若该参数指定为0,则表示以只读方式打开一个存在的文件,独占使用,不允许子进程继承。</P>
<P align=justify>在CFile对象使用时,如果它是在堆中分配的,则应该销毁它;如果在栈中分配的,则CFile对象将被自动销毁。销毁时析构函数被调用,析构函数是虚拟函数。若m_bCloseOnDelete为真且m_hFile非空,则析构函数调用Close关闭文件。</P>
<P align=justify>至于其他CFile成员函数的实现,这里不作分析了。</P>
 楼主| 发表于 2006-12-8 10:28:07 | 显示全部楼层
<OL>
<OL>
<OL>
< align=justify>
<LI><B>CFile的派生类</B>
<p></LI></OL></OL></OL>
< align=justify>这里主要简要地介绍CStdioFile和CmemFile及CFileFind。</P>
<OL>
< align=justify>
<LI>CStdioFile
<>
< align=justify>CStdioFile对文本文件进行操作。</P>
< align=justify>CStdioFile定义了新的成员变量m_pStream,类型是FILE*。在打开或者创建文件时,使用_open_osfhandle从m_hFile(Win32文件句柄)得到一个“C”的FILE类型的文件指针,然后,在文件操作中,使用“C”的文件操作函数。例如,读文件使用_fread,而不是::ReadFile,写文件使用了_fwrite,而不是::WriteFile,等等。m_hFile是CFile的成员变量。</P>
< align=justify>另外,CStdioFile不支持CFile的Dumplicate、LockRange、UnlockRange操作,但是实现了两个新的操作ReadString和WriteString。</P>
< align=justify></P>
<LI>CMemFile
<>
< align=justify>CMemFile把一块内存当作一个文件来操作,所以,它没有打开文件的操作,而是设计了Attach和Detach用来分配或者释放一块内存。相应地,它提供了Alloc、Free虚拟函数来操作内存文件,它覆盖了Read、Write来读写内存文件。</P>
<P align=justify></P>
<LI>CFileFind
<p></LI></OL>
<P align=justify>为了方便文件查找,MFC把有关功能归结成为一个类CFileFind。CFileFind派生于CObject类。首先,它使用FindFile和FineNextFile包装了Win32函数::FindFirstFile和::FindNextFile;其次,它提供了许多函数用来获取文件的状态或者属性。</P>
<P align=justify>使用CFileStatus结构来描述文件的属性,其定义如下:</P>
<P align=justify>struct CFileStatus</P>
<P align=justify>{</P>
<P align=justify>CTime m_ctime; // 文件创建时间</P>
<P align=justify>CTime m_mtime; // 文件最近一次修改时间</P>
<P align=justify>CTime m_atime; // 文件最近一次访问时间</P>
<P align=justify>LONG m_size; // 文件大小</P>
<P align=justify>BYTE m_attribute; // 文件属性</P>
<P align=justify>BYTE _m_padding; // 没有实际含义,用来增加一个字节</P>
<P align=justify>TCHAR m_szFullName[_MAX_PATH]; //绝对路径</P>
<P align=justify></P>
<P align=justify>#ifdef _DEBUG</P>
<P align=justify>//实现Dump虚拟函数,输出文件属性</P>
<DIR>
<P align=justify>void Dump(CDumpContext&amp; dc) const;</P></DIR>
<P align=justify>#endif</P>
<P align=justify>};</P>
<P align=justify>例如:</P>
<P align=justify>CFileStatus status;</P>
<P align=justify>pFile-&gt;GetStatus(status);</P>
<P align=justify>#ifdef _DEBUG</P>
<DIR>
<P align=justify>status.dump(afxDump);</P></DIR>
<P align=justify>#endif</P>
<OL start=12>
<P align=justify>
<LI><B>对话框和对话框类CDialog</B>
<P>
<P align=justify>对话框经常被使用,因为对话框可以从模板创建,而对话框模板是可以使用资源编辑器方便地进行编辑的。</P>
<OL>
<P align=justify>
<LI><B>模式和无模式对话框</B>
<P>
<P align=justify>对话框分两种类型,模式对话框和无模式对话框。</P>
<OL>
<P align=justify>
<LI><B>模式对话框</B>
<P>
<P align=justify>一个模式对话框是一个有系统菜单、标题栏、边线等的弹出式窗口。在创建对话框时指定WS_POPUP, WS_SYSMENU, WS_CAPTION和 DS_MODALFRAME风格。即使没有指定WS_VISIBLE风格,模式对话框也会被显示。</P>
<P align=justify>创建对话框窗口时,将发送WM_INITDIALOG消息(如果指定对话框的DS_SETFONT风格,还有WM_SETFONT消息)给对话框过程。</P>
<P align=justify>对话框过程(Dialog box procedure)不是对话框窗口的窗口过程(Window procedure)。在Win32里,对话框的窗口过程由Windows系统提供,用户在创建对话框窗口时提供一个对话框过程由窗口过程调用。</P>
<P align=justify>对话框窗口被创建之后,Windows使得它成为一个激活的窗口,它保持激活直到对话框过程调用::EndDialog函数结束对话框的运行或者Windows激活另一个应用程序为止,在激活时,用户或者应用程序不可以激活它的所属窗口(Owner window)。</P>
<P align=justify>从某个窗口创建一个模式对话框时,Windows自动地禁止使用(Disable)这个窗口和它的所有子窗口,直到该模式对话框被关闭和销毁。虽然对话框过程可以Enable所属窗口,但是这样做就失去了模式对话框的作用,所以不鼓励这样做。</P>
<P align=justify>Windows创建模式对话框时,给当前捕获鼠标输入的窗口(如果有的话)发送消息WM_CANCLEMODE。收到该消息后,应用程序应该终止鼠标捕获(Release the mouse capture)以便于用户能把鼠标移到模式对话框;否则由于Owner窗口被禁止,程序将失去鼠标输入。</P>
<P align=justify>为了处理模式对话框的消息,Windows开始对话框自身的消息循环,暂时控制整个应用程序的消息队列。如果Windows收到一个非对话框消息时,则它把消息派发给适当的窗口处理;如果收到了WM_QUIT消息,则把该消息放回应用程序的消息队列里,这样应用程序的主消息循环最终能处理这个消息。</P>
<P align=justify>当应用程序的消息队列为空时,Windows发送WM_ENTERIDLE消息给Owner窗口。在对话框运行时,程序可以使用这个消息进行后台处理,当然应该注意经常让出控制给模式对话框,以便它能接收用户输入。如果不希望模式对话框发送WM_ENTERIDlE消息,则在创建模式对话框时指定DS_NOIDLEMSG风格。</P>
<P align=justify>一个应用程序通过调用::EndDialog函数来销毁一个模式对话框。一般情况下,当用户从系统菜单里选择了关闭(Close)命令或者按下了确认(OK)或取消(CANCLE)按钮,::EndDialog被对话框过程所调用。调用::EndDialog时,指定其参数nResult的值,Windows将在销毁对话框窗口后返回这个值,一般,程序通过返回值判断对话框窗口是否完成了任务或者被用户取消。</P>
<P align=justify></P>
<LI><B>无模式对话框</B>
<p></LI></OL>
<P align=justify>一个无模式对话框是一个有系统菜单、标题栏、边线等的弹出式窗口。在创建对话框模板时指定WS_POPUP、WS_CAPTION、WS_BORDER和WS_SYSMENU风格。如果没有指定WS_VISIBLE风格,无模式对话框不会自动地显示出来。</P>
<P align=justify>一个无模式对话框既不会禁止所属窗口,也不会给它发送消息。当创建一个模式对话框时,Windows使它成为活动窗口,但用户或者程序可以随时改变和设置活动窗口。如果对话框失去激活,那么即使所属窗口是活动的,在Z轴顺序上,它仍然在所属窗口之上。</P>
<P align=justify>应用程序负责获取和派发输入消息给对话框。大部分应用程序使用主消息循环来处理,但是为了用户可以使用键盘在控制窗口之间移动或者选择控制窗口,应用程序应该调用::IsDialogMessage函数。</P>
<P align=justify>这里,顺便解释::IsDialogMessage函数。虽然该函数是为无模式对话框设计的,但是任何包含了控制子窗口的窗口都可以调用它,用来实现类似于对话框的键盘选择操作。</P>
<P align=justify>当::IsDialogMessage处理一个消息时,它检查键盘消息并把它们转换成相应对话框的选择命令。例如,当Tab 键被压下时,下一个或下一组控制被选中,当Down Arrow键按下后,一组控制中的下一个控制被选择。</P>
<P align=justify>::IsDialogMessage完成了所有必要的消息转换和消息派发,所以该函数处理的消息一定不要传递给TranslateMessage和DispatchMessage处理。</P>
<P align=justify>一个无模式对话框不能像模式对话框那样返回一个值给应用程序。但是对话框过程可以使用::SendMessage给所属窗口传递信息。</P>
<P align=justify>在应用程序结束之前,它必须销毁所有的无模式对话框。使用:estroyWindow销毁一个无模式对话框,不是使用::EndDiaLog。一般来说,对话框过程响应用户输入,如用户选择了“取消”按钮,则调用:estroyWindow;如果用户没有有关动作,则应用程序必须调用:estroyWindow。</P>
<P align=justify></P>
<LI><B>对话框的MFC实现</B>
<P>
<P align=justify>在MFC中,对话框窗口的功能主要由CWnd和CDialog两个类实现。</P>
<OL>
<P align=justify>
<LI><B>CDialog的设计和实现</B>
<P>
<P align=justify>MFC通过CDialog来封装对话框的功能。CDialog从CWnd继承了窗口类的功能(包括CWnd实现的有关功能),并添加了新的成员变量和函数来处理对话框。</P>
<OL>
<P align=justify>
<LI><B>CDialog的成员变量</B>
<P>
<P align=justify>CDialog的成员变量有:</P>
<P align=justify>protected:</P>
<P align=justify>UINT m_nIDHelp; // Help ID (0 for none, see HID_BASE_RESOURCE)</P>
<P align=justify></P>
<P align=justify>LPCTSTR m_lpszTemplateName; // name or MAKEINTRESOURCE</P>
<P align=justify>HGLOBAL m_hDialogTemplate; // indirect (m_lpDialogTemplate == NULL)</P>
<P align=justify>// indirect if (m_lpszTemplateName == NULL)</P>
<P align=justify>LPCDLGTEMPLATE m_lpDialogTemplate;</P>
<P align=justify>void* m_lpDialogInit; // DLGINIT resource data</P>
<P align=justify>CWnd* m_pParentWnd; // parent/owner window</P>
<P align=justify>HWND m_hWndTop; // top level parent window (may be disabled)</P>
<P align=justify>成员变量保存了创建对话框的模板资源、对话框父窗口对象、顶层窗口句柄等信息。三个关于模板资源的成员变量m_lpszTemplateName、m_hDialogTemplate、m_lpDialogTemplate对应了三种模板资源,但在创建对话框时,只要一个模板资源就可以了,可以使用其中的任意一类。</P>
<P align=justify></P>
<LI><B>CDialog的成员函数:</B>
<p></LI></OL></LI></OL></LI></OL></LI></OL>
<OL>
<P align=justify>
<LI>构造函数:
<P>
<P align=justify>CDialog( LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL );</P>
<P align=justify>CDialog( UINT nIDTemplate, CWnd* pParentWnd = NULL );</P>
<P align=justify>CDialog( );</P>
<P align=justify>CDialog重载了三个构造函数。其中,第三个是缺省构造函数;第一个和第二个构造函数从指定的对话框模板资源创建,pParentWnd指定了父窗口或所属窗口,若空则设置父窗口为应用程序主窗口。</P>
<P align=justify></P>
<LI>初始化函数
<P>
<P align=justify>BOOL Create( LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL );</P>
<P align=justify>BOOL Create( UINT nIDTemplate, CWnd* pParentWnd = NULL );</P>
<P align=justify>BOOL CreateIndirect( LPCDLGTEMPLATE lpDialogTemplate, CWnd* pParentWnd = NULL );</P>
<P align=justify>BOOL CreateIndirect( HGLOBAL hDialogTemplate, CWnd* pParentWnd = NULL );</P>
<P align=justify>BOOL InitModalIndirect( LPCDLGTEMPLATE lpDialogTemplate, CWnd* pParentWnd = NULL );</P>
<P align=justify>BOOL InitModalIndirect( HGLOBAL hDialogTemplate, CWnd* pParentWnd = NULL );</P>
<P>Create用来根据模板创建无模式对话框;CreateInDirect用来根据内存中的模板创建无模式对话框;InitModalIndirect用来根据内存中的模板创建模式对话框。它们都提供了两个重载版本。</P>
<P align=justify></P>
<LI>对话框操作函数
<P>
<P align=justify>void MapDialogRect( LPRECT lpRect ) const;</P>
<P align=justify>void NextDlgCtrl( ) const;</P>
<P align=justify>void PrevDlgCtrl( ) const;</P>
<P align=justify>void GotoDlgCtrl( CWnd* pWndCtrl );</P>
<P align=justify>void SetDefID( UINT nID );</P>
<P align=justify>void SetHelpID( UINT nIDR );</P>
<P align=justify>void EndDialog( int nResult );</P>
<P align=justify></P>
<LI>虚拟函数
<p></LI></OL>
<P align=justify>virtual int DoModal( );</P>
<P align=justify>virtual BOOL OnInitDialog( );</P>
<P align=justify>virtual void OnSetFont( CFont* pFont );</P>
<P align=justify>virtual void OnOK( );</P>
<P align=justify>virtual void OnCancel( );</P>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>MFC模式对话框的实现</B>
<P>
<P>从前面的介绍可以知道,Win32 SDK编程下的模式对话框使用了Windows提供给对话框窗口的窗口过程和自己的对话框过程,对话框过程将被窗口过程调用。但在MFC下,所有的窗口类都使用了同一个窗口过程,CDialog也不例外。CDialog对象在创建Windows对话框时,采用了类似于CWnd的创建函数过程,采用子类化的手段将Windows提供给对话框的窗口过程取代为AfxWndProc或者AfxBaseWndProc,同时提供了对话框过程AfxDlgProc。那么,这些“过程”是如何实现或者协调的呢?下文将予以分析。</P>
<OL>
<P align=justify>
<LI><B>MFC对话框过程</B>
<P>
<P>MFC对话框过程AfxDlgProc的原型和实现如下:</P>
<P align=justify>BOOL CALLBACK AfxDlgProc(HWND hWnd,</P>
<P align=justify>UINT message, PARAM, LPARAM)</P>
<P align=justify>{</P>
<P align=justify>if (message == WM_INITDIALOG)</P>
<P align=justify>{</P>
<P align=justify>//处理WM_INITDIALOG消息</P>
<P align=justify>CDialog* pDlg = DYNAMIC_DOWNCAST(CDialog, </P>
<P align=justify>CWnd::FromHandlePermanent(hWnd));</P>
<P align=justify>if (pDlg != NULL)</P>
<P align=justify>return pDlg-&gt;OnInitDialog();</P>
<P align=justify>else</P>
<P align=justify>return 1;</P>
<P align=justify>}</P>
<P align=justify>return 0;</P>
<P align=justify>}</P>
<P>由上可以看出,MFC的对话框函数AfxDlgProc仅处理消息WM_INITDIALOG,其他都留给对话框窗口过程处理。因此,它不同于SDK编程的对话框过程。程序员在SDK的对话框过程处理消息和事件,实现自己的对话框功能。</P>
<P>AfxDlgProc处理WM_INITDIALOG消息时调用虚拟函数OnInitDialog,给程序员一个机会处理对话框的初始化</P></LI></OL></LI></OL></OL></OL>
 楼主| 发表于 2006-12-8 10:29:57 | 显示全部楼层
<LI>
<OL>
<LI><B>模式对话框窗口过程</B>
<LI>
<LI>本小节讨论对话框的窗口过程。
<LI>AfxWndProc是所有的MFC窗口类使用的窗口过程,它取代了模式对话框原来的窗口过程(Windows提供),那么,MFC如何完成Win32下对话框窗口的功能呢?
<LI>考查模式对话框的创建过程。CDialog:oModal用来创建模式对话框窗口并执行有关任务,和DoModal相关的是MFC内部使用的成员函数CDialog:reModal和CDialog:ostModal。下面分别讨论它们的实现。
<LI>
<DIV align=justify>HWND CDialog:reModal()</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>// cannot call DoModal on a dialog already constructed as modeless</DIV>
<LI>
<DIV align=justify>ASSERT(m_hWnd == NULL);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>// allow OLE servers to disable themselves</DIV>
<LI>
<DIV align=justify>AfxGetApp()-&gt;EnableModeless(FALSE);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>// 得到父窗口</DIV>
<LI>
<DIV align=justify>CWnd* pWnd = CWnd::GetSafeOwner(m_pParentWnd, &amp;m_hWndTop);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>// 如同CWnd处理其他窗口的创建,设置一个窗口创建HOOK</DIV>
<LI>
<DIV align=justify>AfxHookWindowCreate(this);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//返回父窗口的句柄</DIV>
<LI>
<DIV align=justify>return pWnd-&gt;GetSafeHwnd();</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>void CDialog:ostModal()</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>//取消窗口创建前链接的HOOK</DIV>
<LI>
<DIV align=justify>AfxUnhookWindowCreate(); // just in case</DIV>
<LI>
<DIV align=justify>//MFC对话框对象和对应的Windows对话框窗口分离</DIV>
<LI>
<DIV align=justify>Detach(); // just in case</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>// m_hWndTop是当前对话框的父窗口或所属窗口,则恢复它</DIV>
<LI>
<DIV align=justify>if (::IsWindow(m_hWndTop))</DIV>
<LI>
<DIV align=justify>::EnableWindow(m_hWndTop, TRUE);</DIV>
<LI>
<DIV align=justify>m_hWndTop = NULL;</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>AfxGetApp()-&gt;EnableModeless(TRUE);</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>int CDialog:oModal()</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>// can be constructed with a resource template or InitModalIndirect</DIV>
<LI>
<DIV align=justify>ASSERT(m_lpszTemplateName != NULL ||</DIV>
<LI>
<DIV align=justify>m_hDialogTemplate != NULL || m_lpDialogTemplate != NULL);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//加载对话框资源</DIV>
<LI>
<DIV align=justify>LPCDLGTEMPLATE lpDialogTemplate = m_lpDialogTemplate;</DIV>
<LI>
<DIV align=justify>HGLOBAL hDialogTemplate = m_hDialogTemplate;</DIV>
<LI>
<DIV align=justify>HINSTANCE hInst = AfxGetResourceHandle();</DIV>
<LI>
<DIV align=justify>//查找资源(见9.5.2节),找到了就加载它</DIV>
<LI>
<DIV align=justify>if (m_lpszTemplateName != NULL)</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>hInst = AfxFindResourceHandle(m_lpszTemplateName, RT_DIALOG);</DIV>
<LI>
<DIV align=justify>HRSRC hResource = </DIV>
<LI>
<DIV align=justify>::FindResource(hInst, m_lpszTemplateName, RT_DIALOG);</DIV>
<LI>
<DIV align=justify>hDialogTemplate = LoadResource(hInst, hResource);</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//锁定加载的资源</DIV>
<LI>
<DIV align=justify>if (hDialogTemplate != NULL)</DIV>
<LI>
<DIV align=justify>lpDialogTemplate = (LPCDLGTEMPLATE)LockResource(hDialogTemplate);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>// return -1 in case of failure to load the dialog template resource</DIV>
<LI>
<DIV align=justify>if (lpDialogTemplate == NULL)</DIV>
<LI>
<DIV align=justify>return -1;</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//创建对话框前禁止父窗口,为此要调用PreModal得到父窗口句柄</DIV>
<LI>
<DIV align=justify>HWND hWndParent = PreModal();</DIV>
<LI>
<DIV align=justify>AfxUnhookWindowCreate();</DIV>
<LI>
<DIV align=justify>CWnd* pParentWnd = CWnd::FromHandle(hWndParent);</DIV>
<LI>
<DIV align=justify>BOOL bEnableParent = FALSE;</DIV>
<LI>
<DIV align=justify>if (hWndParent != NULL &amp;&amp; ::IsWindowEnabled(hWndParent))</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>::EnableWindow(hWndParent, FALSE);</DIV>
<LI>
<DIV align=justify>bEnableParent = TRUE;</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//创建对话框,注意是无模式对话框</DIV>
<LI>
<DIV align=justify>TRY</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>//链接一个HOOK到HOOK链以处理窗口创建,</DIV>
<LI>
<DIV align=justify>//如同4.4.1节描述的CWnd类窗口创建一样</DIV>
<LI>
<DIV align=justify>AfxHookWindowCreate(this);</DIV>
<LI>
<DIV align=justify>//CreateDlgIndirect间接调用::CreateDlgIndirect,</DIV>
<LI>
<DIV align=justify>//最终调用了::CreateWindowEX来创建对话框窗口。</DIV>
<LI>
<DIV align=justify>//HOOK过程_AfxCbtFilterHook用子类化的方法</DIV>
<LI>
<DIV align=justify>//取代原来的窗口过程为AfxWndProc。</DIV>
<LI>
<DIV align=justify>if (CreateDlgIndirect(lpDialogTemplate, CWnd::FromHandle(hWndParent), hInst))</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>if (m_nFlags &amp; WF_CONTINUEMODAL)</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>// enter modal loop</DIV>
<LI>
<DIV align=justify>DWORD dwFlags = MLF_SHOWONIDLE;</DIV>
<LI>
<DIV align=justify>//RunModalLoop接管整个应用程序的消息处理</DIV>
<LI>
<DIV align=justify>if (GetStyle() &amp; DS_NOIDLEMSG)</DIV>
<LI>
<DIV align=justify>dwFlags |= MLF_NOIDLEMSG;</DIV>
<LI>
<DIV align=justify>VERIFY(RunModalLoop(dwFlags) == m_nModalResult);</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>// hide the window before enabling the parent, etc.</DIV>
<LI>
<DIV align=justify>if (m_hWnd != NULL)</DIV>
<LI>
<DIV align=justify>SetWindowPos(NULL, 0, 0, 0, 0, SWP_HIDEWINDOW|</DIV>
<LI>
<DIV align=justify>SWP_NOSIZE|SWP_NOMOVE|</DIV>
<LI>
<DIV align=justify>SWP_NOACTIVATE|SWP_NOZORDER);</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify>CATCH_ALL(e)</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>DELETE_EXCEPTION(e);</DIV>
<LI>
<DIV align=justify>m_nModalResult = -1;</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify>END_CATCH_ALL</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//Enable并且激活父窗口</DIV>
<LI>
<DIV align=justify>if (bEnableParent)</DIV>
<LI>
<DIV align=justify>::EnableWindow(hWndParent, TRUE);</DIV>
<LI>
<DIV align=justify>if (hWndParent != NULL &amp;&amp; ::GetActiveWindow() == m_hWnd)</DIV>
<LI>
<DIV align=justify>::SetActiveWindow(hWndParent);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//::EndDialog仅仅关闭了窗口,现在销毁窗口</DIV>
<LI>
<DIV align=justify>DestroyWindow();</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>ostModal();</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>// 必要的话,解锁/释放资源</DIV>
<LI>
<DIV align=justify>if (m_lpszTemplateName != NULL || m_hDialogTemplate != NULL)</DIV>
<LI>
<DIV align=justify>UnlockResource(hDialogTemplate);</DIV>
<LI>
<DIV align=justify>if (m_lpszTemplateName != NULL)</DIV>
<LI>
<DIV align=justify>FreeResource(hDialogTemplate);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>return m_nModalResult;</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>从DoModal的实现可以看出:
<LI>它首先Disable对话框窗口的父窗口;然后使用::CreateIndrectDialog创建对话框窗口,使用子类化的方法用AfxWndProc(或者AfxBaseProc)替换了原来的窗口过程,并把原来的窗口过程保存在CWnd的成员变量m_pfnSuper中。原来的窗口过程就是:ialogBox等创建对话框窗口时指定的,是Windows内部提供的对话框“窗口类”的窗口过程。取代(Subclass)原来“窗口类”的窗口过程的方法如同 4.4.1节描述的CWnd::Create。
<LI>在::CreateIndirectDialog创建对话框窗口后,会发送WM_INITDIALOG消息给对话框的对话框过程(必要的话,还有WM_SETFONT消息)。但是MFC取代了原来的对话框窗口过程,这两个消息如何送给对话框过程呢?处理方法如下节所描述。</LI></OL>
<DIV align=justify>
<OL>
<LI>
<><B>使用原对话框窗口过程作消息的缺省处理</B> </P>
<>
<>对话框的消息处理过程和其他窗口并没有什么不同。这里主要分析的是如何把一些消息传递给对话框原窗口过程处理。下面,通过解释MFC对WM_INITDIALOG消息的处理来解释MFC窗口过程和原对话框窗口过程的关系及其协调作用。</P>
<>MFC提供了WM_INITDIALOG消息的处理函数CDialog::HandleInitDialog,WM_INITDIALOG消息按照标准Windows的处理送给HandleInitDialog处理。</P>
<>HandleInitDialog调用缺省处理过程Default,导致CWnd的Default函数被调用。CWnd:efault的实现如下:</P>
< align=justify>LRESULT CWnd:efault()</P>
< align=justify>{</P>
< align=justify>// call DefWindowProc with the last message</P>
< align=justify>_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();</P>
<P align=justify>return DefWindowProc(pThreadState-&gt;m_lastSentMsg.message,</P>
<P align=justify>pThreadState-&gt;m_lastSentMsg.wParam,</P>
<P align=justify>pThreadState-&gt;m_lastSentMsg.lParam);</P>
<P align=justify>}</P>
<P>顺便指出,从Default的实现可以看出线程状态的一个用途:它把本线程最新收到和处理的消息记录在成员变量m_lastSentMsg中。</P>
<P>在Default的实现中,CWnd的DefWindowsProc被调用,其实现如下:</P>
<P align=justify>LRESULT CWnd:efWindowProc(UINT nMsg, </P>
<P align=justify>WPARAM wParam, LPARAM lParam)</P>
<P align=justify>{</P>
<P align=justify>//若“窗口超类(SuperClass)”的窗口过程m_pfnSuper非空,则调用它</P>
<P align=justify>if (m_pfnSuper != NULL)</P>
<P align=justify>return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);</P>
<P align=justify></P>
<P align=justify>//在MFC中,GetSuperWndProcAddr的作用就是返回m_pfnSuper,为什么还</P>
<P align=justify>//要再次调用呢?因为虽然该函数现在是Obsolete,但原来曾经是有用的。如</P>
<P align=justify>//果返回非空,就调用该窗口过程进行处理,否则,由Windows进行缺省处理。</P>
<P align=justify>WNDPROC pfnWndProc;</P>
<P align=justify>if ((pfnWndProc = *GetSuperWndProcAddr()) == NULL)</P>
<P align=justify>return :efWindowProc(m_hWnd, nMsg, wParam, lParam);</P>
<P align=justify>else</P>
<P align=justify>return ::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam);</P>
<P align=justify>}</P>
<P align=justify>综合上述分析,HandleInitDialog最终调用了窗口过程m_pfnSuper,即Windows提供给“对话框窗口类”的窗口过程,于是该窗口过程调用了对话框过程AfxDlgProc,导致虚拟函数OnInitDialog被调用。</P>
<P align=justify>顺便提一下,CWnd::AfxCallWndProc在处理WM_INITDIALOG消息之前和之后都会有一些特别的处理,如尝试把对话框放到屏幕中间。具体实现这里略。</P>
<P align=justify>OnInitDialog的MFC缺省实现主要完成三件事情:</P>
<P align=justify>调用ExecInitDialog初始化对话框中的控制;调用UpdateData初始化对话框控制中的数据;确定是否显示帮助按钮。所以,程序员覆盖该函数时,一定要调用基类的实现。</P>
<P align=justify></P>
<P align=justify>MFC采用子类化的方法取代了对话框的窗口过程,实现了12.1节描述的模式对话框窗口的一些特性,原来SDK下对话框过程要处理的东西大部分转移给MFC窗口过程处理,如处理控制窗口的控制通知消息等。如果不能处理或者必须借助于原来的窗口过程的,则通过缺省处理函数Default传递给原来的窗口过程处理,如同这里对WM_INITDIALOG的处理一样。</P>
<P align=justify></P>
<LI><B>Dialog命令消息和控制通知消息的处理</B>
<P>
<P align=justify>通过覆盖CWnd的命令消息发送函数OnCmdMsg,CDialog实现了自己的命令消息发送路径。在4.4.3.3节,曾经分析了CDialog::OnCmdMsg函数,这里给出其具体实现:</P>
<P align=justify>BOOL CDialog::OnCmdMsg(UINT nID, int nCode, void* pExtra,</P>
<P align=justify>AFX_CMDHANDLERINFO* pHandlerInfo)</P>
<P align=justify>{</P>
<P align=justify>//首先,让对话框窗口自己或者基类处理</P>
<P align=justify>if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))</P>
<P align=justify>return TRUE;</P>
<P align=justify>//如果还未处理,且是控制通知消息或者状态更新消息或者系统命令</P>
<P align=justify>//则停止进一步的发送</P>
<P align=justify>if ((nCode != CN_COMMAND &amp;&amp; nCode != CN_UPDATE_COMMAND_UI) ||</P>
<P align=justify>!IS_COMMAND_ID(nID) || nID &gt;= 0xf000)</P>
<P align=justify>{</P>
<P align=justify>return FALSE; // not routed any further</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>//尝试给父窗口处理</P>
<P align=justify>CWnd* pOwner = GetParent();</P>
<P align=justify>if (pOwner != NULL)</P>
<P align=justify>{</P>
<P align=justify>#ifdef _DEBUG</P>
<P align=justify>if (afxTraceFlags &amp; traceCmdRouting)</P>
<P align=justify>TRACE1("Routing command id 0x%04X to owner window.\n", nID);</P>
<P align=justify>#endif</P>
<P align=justify>ASSERT(pOwner != this);</P>
<P align=justify>if (pOwner-&gt;OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))</P>
<P align=justify>return TRUE;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>// 最后,给当前线程对象处理</P>
<P align=justify>CWinThread* pThread = AfxGetThread();</P>
<P align=justify>if (pThread != NULL)</P>
<P align=justify>{</P>
<P align=justify>#ifdef _DEBUG</P>
<P align=justify>if (afxTraceFlags &amp; traceCmdRouting)</P>
<P align=justify>TRACE1("Routing command id 0x%04X to app.\n", nID);</P>
<P align=justify>#endif</P>
<P align=justify>if (pThread-&gt;OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))</P>
<P align=justify>return TRUE;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>#ifdef _DEBUG</P>
<P align=justify>if (afxTraceFlags &amp; traceCmdRouting)</P>
<P align=justify>{</P>
<P align=justify>TRACE2("IGNORING command id 0x%04X sent to %hs dialog.\n", nID,</P>
<P align=justify>GetRuntimeClass()-&gt;m_lpszClassName);</P>
<P align=justify>}</P>
<P align=justify>#endif</P>
<P align=justify>return FALSE;</P>
<P align=justify>}</P>
<P align=justify>从上述实现可以看出,CDialog处理命令消息遵循如下顺序:</P>
<P align=justify>对话框自身→父窗口→线程对象</P>
<P align=justify>例如,模式对话框产生的WM_ENTERIDLE消息就发送给父窗口处理。</P>
<P align=justify>从实现中还看到,MFC根据TRACE过滤标识afxTraceFlags的值,把有关命令消息的派发显示到调试窗口。</P>
<P align=justify>CDialog::OnCmdMsg不仅适用于模式对话框,也适用于无模式对话框。</P>
<P align=justify></P></LI></OL></DIV></LI>
 楼主| 发表于 2006-12-8 10:30:45 | 显示全部楼层
<OL>
<LI><B>消息预处理和Dialog消息</B>
<LI>
<LI>
<DIV align=justify>另外,对话框窗口的消息处理还有一个特点,就是增加了对Dialog消息的处理,如同在介绍::IsDialogMessage函数时所述。如果是Dialog消息,MFC框架将不会让它进入下一步的消息循环。为此,MFC覆盖了CDialog的虚拟函数PreTranslateMessage,该函数的实现如下:</DIV>
<LI>
<DIV align=justify>BOOL CDialog:reTranslateMessage(MSG* pMsg)</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>// 用于无模式或者模式对话框的处理</DIV>
<LI>
<DIV align=justify>ASSERT(m_hWnd != NULL);</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//过滤tooltip messages</DIV>
<LI>
<DIV align=justify>if (CWnd:reTranslateMessage(pMsg))</DIV>
<LI>
<DIV align=justify>return TRUE;</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//在Shift+F1帮助模式下,不转换Dialog messages</DIV>
<LI>
<DIV align=justify>CFrameWnd* pFrameWnd = GetTopLevelFrame();</DIV>
<LI>
<DIV align=justify>if (pFrameWnd != NULL &amp;&amp; pFrameWnd-&gt;m_bHelpMode)</DIV>
<LI>
<DIV align=justify>return FALSE;</DIV>
<LI>
<DIV align=justify></DIV>
<LI>
<DIV align=justify>//处理Escape键按下的消息</DIV>
<LI>
<DIV align=justify>if (pMsg-&gt;message == WM_KEYDOWN &amp;&amp;</DIV>
<LI>
<DIV align=justify>(pMsg-&gt;wParam == VK_ESCAPE || pMsg-&gt;wParam == VK_CANCEL) &amp;&amp;</DIV>
<LI>
<DIV align=justify>(::GetWindowLong(pMsg-&gt;hwnd, GWL_STYLE) &amp; ES_MULTILINE) &amp;&amp;</DIV>
<LI>
<DIV align=justify>_AfxCompareClassName(pMsg-&gt;hwnd, _T("Edit")))</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>HWND hItem = ::GetDlgItem(m_hWnd, IDCANCEL);</DIV>
<LI>
<DIV align=justify>if (hItem == NULL || ::IsWindowEnabled(hItem))</DIV>
<LI>
<DIV align=justify>{</DIV>
<LI>
<DIV align=justify>SendMessage(WM_COMMAND, IDCANCEL, 0);</DIV>
<LI>
<DIV align=justify>return TRUE;</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify>// 过滤来自控制该对话框子窗口的送给该对话框的Dialog消息</DIV>
<LI>
<DIV align=justify>return PreTranslateInput(pMsg);</DIV>
<LI>
<DIV align=justify>}</DIV>
<LI>
<DIV align=justify>从其实现可以看出,如果是Tooltip消息或者Dialog消息,这些消息将在PreTranslateMessage中被处理,不会进入消息发送的处理。</DIV>
<LI>
<DIV align=justify>reTranslateInput是CWnd的成员函数,它调用::IsDialogMessage函数来处理Dialog消息。</DIV>
<LI>
<DIV align=justify>reTranslateMessage的实现不仅用于模式对话框,而且用于无模式对话框。<BR>
<OL>
<LI>
<><B>模式对话框的消息循环</B> </P>
<p></LI>
<LI>
< align=justify>从DoModal的实现可以看出,DoModal调用CreateDlgIndirect创建的是无模式对话框,MFC如何来接管和控制应用程序的消息队列,实现一个模式对话框的功能呢?</P>
< align=justify>CDialog调用了RunModalLoop来实现模式窗口的消息循环。RunModalLoop是CWnd的成员函数,它和相关函数的实现如下:</P>
< align=justify>int CWnd::RunModalLoop(DWORD dwFlags)</P>
< align=justify>{</P>
< align=justify>ASSERT(::IsWindow(m_hWnd)); //窗口必须已经创建且不在模式状态 ASSERT(!(m_nFlags &amp; WF_MODALLOOP)); </P>
< align=justify></P>
< align=justify>// 以下变量用于Idle处理</P>
<P align=justify>BOOL bIdle = TRUE;</P>
<P align=justify>LONG lIdleCount = 0;</P>
<P align=justify>BOOL bShowIdle = (dwFlags &amp; MLF_SHOWONIDLE) &amp;&amp; </P>
<P align=justify>!(GetStyle() &amp; WS_VISIBLE);</P>
<P align=justify>HWND hWndParent = ::GetParent(m_hWnd);</P>
<P align=justify>m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);</P>
<P align=justify>MSG* pMsg = &amp;AfxGetThread()-&gt;m_msgCur;</P>
<P align=justify></P>
<P align=justify>//获取和派发消息直到模式状态结束</P>
<P align=justify>for (;;)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(ContinueModal());</P>
<P align=justify></P>
<P align=justify>//第一阶段,判断是否可以进行Idle处理</P>
<P align=justify>while (bIdle &amp;&amp;!:eekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))</P>
<P align=justify>{</P>
<P align=justify>ASSERT(ContinueModal());</P>
<P align=justify></P>
<P align=justify>//必要的话,当Idle时显示对话框窗口</P>
<P align=justify>if (bShowIdle)</P>
<P align=justify>{</P>
<P align=justify>ShowWindow(SW_SHOWNORMAL);</P>
<P align=justify>UpdateWindow();</P>
<P align=justify>bShowIdle = FALSE;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>// 进行Idle处理</P>
<P align=justify>//必要的话发送WM_ENTERIDLE消息给父窗口</P>
<P align=justify>if (!(dwFlags &amp; MLF_NOIDLEMSG) &amp;&amp;hWndParent != NULL &amp;&amp; lIdleCount == 0)</P>
<P align=justify>{</P>
<P align=justify>::SendMessage(hWndParent, WM_ENTERIDLE, </P>
<P align=justify>MSGF_DIALOGBOX, (LPARAM)m_hWnd);</P>
<P align=justify>}</P>
<P align=justify>//必要的话发送WM_KICKIDLE消息给父窗口</P>
<P align=justify>if ((dwFlags &amp; MLF_NOKICKIDLE) ||</P>
<P align=justify>!SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))</P>
<P align=justify>{</P>
<P align=justify>//终止Idle处理</P>
<P align=justify>bIdle = FALSE;</P>
<P align=justify>}</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>//第二阶段,发送消息</P>
<P align=justify>do</P>
<P align=justify>{</P>
<P align=justify>ASSERT(ContinueModal());</P>
<P align=justify></P>
<P align=justify>// 若是WM_QUIT消息,则发送该消息到消息队列,返回;否则发送消息。</P>
<P align=justify>if (!AfxGetThread()-&gt;PumpMessage())</P>
<P align=justify>{</P>
<P align=justify>AfxPostQuitMessage(0);</P>
<P align=justify>return -1;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>//必要的话,显示对话框窗口</P>
<P align=justify>if (bShowIdle &amp;&amp;</P>
<P align=justify>(pMsg-&gt;message == 0x118 || pMsg-&gt;message == WM_SYSKEYDOWN))</P>
<P align=justify>{</P>
<P align=justify>ShowWindow(SW_SHOWNORMAL);</P>
<P align=justify>UpdateWindow();</P>
<P align=justify>bShowIdle = FALSE;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>if (!ContinueModal())</P>
<P align=justify>goto ExitModal;</P>
<P align=justify></P>
<P align=justify>//在派发了“正常 ”消息后,重新开始Idle处理</P>
<P align=justify>if (AfxGetThread()-&gt;IsIdleMessage(pMsg))</P>
<P align=justify>{</P>
<P align=justify>bIdle = TRUE;</P>
<P align=justify>lIdleCount = 0;</P>
<P align=justify>}</P>
<P align=justify>} while (:eekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>ExitModal:</P>
<P align=justify>m_nFlags &amp;= ~(WF_MODALLOOP|WF_CONTINUEMODAL);</P>
<P align=justify>return m_nModalResult;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>BOOL CWnd::ContinueModal()</P>
<P align=justify>{</P>
<P align=justify>return m_nFlags &amp; WF_CONTINUEMODAL;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>void CWnd::EndModalLoop(int nResult)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(::IsWindow(m_hWnd));</P>
<P align=justify></P>
<P align=justify>// this result will be returned from CWnd::RunModalLoop</P>
<P align=justify>m_nModalResult = nResult;</P>
<P align=justify></P>
<P align=justify>// make sure a message goes through to exit the modal loop</P>
<P align=justify>if (m_nFlags &amp; WF_CONTINUEMODAL)</P>
<P align=justify>{</P>
<P align=justify>m_nFlags &amp;= ~WF_CONTINUEMODAL;</P>
<P align=justify>PostMessage(WM_NULL);</P>
<P align=justify>}</P>
<P align=justify>}</P>
<P align=justify>和CWinThread::Run的处理过程比较,RunModalLoop也分两个阶段进行处理。不同之处在于,这里不同于Run的Idle处理,RunModalLoop是给父窗口发送WM_ENTERIDLE消息(如果需要的话);另外,当前对话框的父窗口被Disabled,是不接收用户消息的。</P>
<P align=justify>RunModalLoop是一个实现自己的消息循环的示例,消息循环的条件是模式化状态没有结束。实现线程自己的消息循环见8.5.6节。</P>
<P align=justify>当用户按下按钮“取消”、“确定”时,将导致RunModalLoop退出消息循环,结束对话框模式状态,并调用::EndDialog关闭窗口。有关关闭对话框的处理如下:</P>
<P align=justify>void CDialog::EndDialog(int nResult)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(::IsWindow(m_hWnd));</P>
<P align=justify></P>
<P align=justify>if (m_nFlags &amp; (WF_MODALLOOP|WF_CONTINUEMODAL))</P>
<P align=justify>EndModalLoop(nResult);</P>
<P align=justify></P>
<P align=justify>::EndDialog(m_hWnd, nResult);</P>
<P align=justify>}</P>
<P align=justify>void CDialog::OnOK()</P>
<P align=justify>{</P>
<P align=justify>if (!UpdateData(TRUE)) {</P>
<P align=justify>TRACE0("UpdateData failed during dialog termination.\n");</P>
<P align=justify>// the UpdateData routine will set focus to correct item</P>
<P align=justify>return;</P>
<P align=justify>}</P>
<P align=justify>EndDialog(IDOK);</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>void CDialog::OnCancel()</P>
<P align=justify>{</P>
<P align=justify>EndDialog(IDCANCEL);</P>
<P align=justify>}</P>
<P align=justify>上述函数OnOk、OnCancle、EndDialog都可以用来关闭对话框窗口。其中:</P>
<P align=justify>OnOk首先进行数据交换,获取对话框中各个控制子窗口的数据,然后调用EndDialog结束对话框。</P>
<P align=justify>OnCancle直接EndDialog结束对话框。</P>
<P align=justify>EndDialog首先修改m_nFlag的值,表示结束模式循环,然后调用::EndDialog关闭对话框窗口。<BR><STRONG>对话框的数据交换</STRONG> </P>
<p>
<P align=justify>对话框数据交换指以下两种动作,或者是把内存数据写入对应的控制窗口,或者是从控制窗口读取数据并保存到内存变量中。MFC为了简化这些操作,以CDataExchange类和一些数据交换函数为基础,提供了一套数据交换和校验的机制。</P>
<OL>
<P align=justify>
<LI><B>数据交换的方法</B>
<p>
<P align=justify>首先,定义保存数据的内存变量──给对话框添加成员变量,每个控制窗口可以对应一个成员变量,或者是控制窗口类型,或者是控制窗口表示的数据的类型。例如,对于对话框的一个编辑控制窗口,可以定义一个CEdit类型的成员变量,或者一个CString类型的成员变量。</P>
<P align=justify>其次,覆盖对话框的虚拟函数DoDataExchange,实现数据交换和验证。</P>
<P align=justify>ClassWizard可以协助程序员自动地添加成员变量,修改DoDataExchange。例如,一个对话框有两个控制窗口,其中的一个编辑框表示姓名,ID是IDC_NAME,另一个编辑框表示年龄,ID是IDC_AGE,ClassWizard添加如下的成员变量:</P>
<P align=justify>// Dialog Data</P>
<P align=justify>//{{AFX_DATA(CExDialog)</P>
<P align=justify>enum { IDD = IDD_DIALOG2 };</P>
<P align=justify>CEdit m_name;</P>
<P align=justify>int m_iAge;</P>
<P align=justify>//}}AFX_DATA</P>
<P align=justify>使用ClassWizard添加成员变量中,一个定义为CEdit,另一个定义为int。这些定义被“//{{AFX_DATA”和“//}}AFX_DATA”引用,表示是ClassWizard添加的,程序员不必修改它们。</P>
<P align=justify>相应的DoDataExchange的实现如下:</P>
<P align=justify>void CExDialog:oDataExchange(CDataExchange* pDX)</P>
<P align=justify>{</P>
<P align=justify>CDialog:oDataExchange(pDX);</P>
<P align=justify>//{{AFX_DATA_MAP(CFtpDialog)</P>
<P align=justify>DDX_Control(pDX, IDC_NAME, m_name);</P>
<P align=justify>DDX_Text(pDX, IDC_AGE, m_nAge);</P>
<P align=justify>DDV_MinMaxInt(pDX, m_nAge, 1, 100); </P>
<P align=justify>//}}AFX_DATA_MAP</P>
<P align=justify>}</P>
<P align=justify>DDX_ Control表示把IDC_NAME子窗口的内容传输到窗口m_name,或者相反。</P>
<P align=justify>DDX_ Text表示把IDC_AGE子窗口的内容按整数类型保存到m_nAge,或者相反。</P>
<P align=justify>DDV_MinMaxInt表示m_nAge应该在1和100之间取值。</P>
<P align=justify></P>
<LI><B>CDataExchange</B>
<p>
<P align=justify>上文中提到DDX_Xxxxx数据交换函数可以进行双向的数据交换,那么它们如何知道数据传输的方向呢?这通过DDX_Xxxxx函数的第一个参数pDX(也就是DoDataEx change的参数pDX)所指的CDataExchange对象来决定,pDX指向一个CdataExchange对象。CDataExchange定义如下:</P>
<P align=justify>class CDataExchange</P>
<P align=justify>{</P>
<P align=justify>// Attributes</P>
<P align=justify>public:</P>
<P align=justify>BOOL m_bSaveAndValidate; // TRUE 则 保存和验证数据</P>
<P align=justify>CWnd* m_pDlgWnd; // 指向一个对话框</P>
<P align=justify></P>
<P align=justify>// Operations (for implementors of DDX and DDV procs)</P>
<P align=justify>HWND PrepareCtrl(int nIDC); //返回指定ID的控制窗口的句柄</P>
<P align=justify>HWND PrepareEditCtrl(int nIDC); //返回指定ID的编辑控制窗口句柄</P>
<P align=justify>void Fail(); // 用来扔出例外</P>
<P align=justify></P>
<P align=justify>#ifndef _AFX_NO_OCC_SUPPORT //OLE控制</P>
<P align=justify>CWnd* PrepareOleCtrl(int nIDC); // 用于对话框中的OLE控制窗口</P>
<P align=justify>#endif</P>
<P align=justify></P>
<P align=justify>// Implementation</P>
<P align=justify>CDataExchange(CWnd* pDlgWnd, BOOL bSaveAndValidate);</P>
<P align=justify></P>
<P align=justify>HWND m_hWndLastControl; // last control used (for validation)</P>
<P align=justify>BOOL m_bEditLastControl; // last control was an edit item</P>
<P align=justify>};</P>
<P align=justify>DoDataExchange类似于Serialize函数,CDataExchange类似于CArchive。CDataExchange使用成员变量m_pDlgWnd保存要进行数据交换的对话框,使用成员变量m_bSaveAndValidate指示数据传输的方向,如果该变量真,则从控制窗口读取数据到成员变量,如果假,则从成员变量写数据到控制窗口。</P>
<P align=justify>在构造一个CDataExchange对象时,将保存有关信息在对象的成员变量中。构造函数如下:</P>
<P align=justify>CDataExchange::CDataExchange(CWnd* pDlgWnd, BOOL bSaveAndValidate)</P>
<P align=justify>{</P>
<P align=justify>ASSERT_VALID(pDlgWnd);</P>
<P align=justify>m_bSaveAndValidate = bSaveAndValidate;</P>
<P align=justify>m_pDlgWnd = pDlgWnd;</P>
<P align=justify>m_hWndLastControl = NULL;</P>
<P align=justify>}</P>
<P align=justify>构造函数参数指定了进行数据交换的对话框pDlgWnd和数据传输方向bSaveAndValidate。</P></LI></OL></LI></OL></DIV></LI></OL>
 楼主| 发表于 2006-12-8 10:32:33 | 显示全部楼层
<OL>
<LI>
<><B>数据交换和验证函数</B> </P>
<p></LI></OL>
< align=justify>在进行数据交换或者验证时,首先使用PrePareCtrl或者PrePareEditCtrl得到控制窗口的句柄,然后使用::GetWindowsText从控制窗口读取数据,或者使用::SetWindowsText写入数据到控制窗口。下面讨论几个例子:</P>
<UL>
< align=justify>
<LI>static void AFX_CDECL DDX_TextWithFormat(CDataExchange* pDX,
<p></LI></UL>
<DIR>
< align=justify>int nIDC,LPCTSTR lpszFormat, UINT nIDPrompt, ...)</P></DIR>
< align=justify>{</P>
< align=justify>va_list pData; //用来处理个数可以变化的参数</P>
< align=justify>va_start(pData, nIDPrompt);//得到参数</P>
< align=justify></P>
< align=justify>//得到编辑框的句柄</P>
< align=justify>HWND hWndCtrl = pDX-&gt;PrepareEditCtrl(nIDC);</P>
<P align=justify>TCHAR szT[32];</P>
<P align=justify>if (pDX-&gt;m_bSaveAndValidate) //TRUE,从编辑框读出数据</P>
<P align=justify>{</P>
<DIR>
<P align=justify>// the following works for %d, %u, %ld, %lu</P>
<P align=justify>//从编辑框得到内容</P>
<P align=justify>::GetWindowText(hWndCtrl, szT, _countof(szT));</P>
<P align=justify>//转换编辑框内容为指定的格式,支持“ %d, %u, %ld, %lu”</P>
<P align=justify>if (!AfxSimpleScanf(szT, lpszFormat, pData))</P>
<P align=justify>{</P>
<DIR>
<P align=justify>AfxMessageBox(nIDPrompt);</P>
<P align=justify>pDX-&gt;Fail(); //数据交换失败</P></DIR>
<P align=justify>}</P></DIR>
<P align=justify>}</P>
<P align=justify>else //FALSE,写入数据到编辑框</P>
<P align=justify>{</P>
<P align=justify>//把要写的内容转换成指定格式</P>
<DIR>
<P align=justify>wvsprintf(szT, lpszFormat, pData);//不支持浮点运算</P>
<P align=justify>//设置编辑框的内容</P>
<P align=justify>AfxSetWindowText(hWndCtrl, szT);</P></DIR>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>va_end(pData);//结束参数分析</P>
<P align=justify>}</P>
<P align=justify>DDX_TextWithFormat用来按照一定的格式把数据写入或者读出编辑框。首先,它得到编辑框的句柄hWndCtrl,然后,根据传输方向从编辑框读出内容并转换成指定格式(读出时),或者转换内容为指定格式后写入编辑框(写入时)。本函数可以处理个数不定的参数,是多个数据交换和验证函数的基础。</P>
<UL>
<P align=justify>
<LI>void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, long&amp; value)
<p></LI></UL>
<P align=justify>{</P>
<P align=justify>if (pDX-&gt;m_bSaveAndValidate)</P>
<DIR>
<P align=justify>DDX_TextWithFormat(pDX, nIDC, _T("%ld"), AFX_IDP_PARSE_INT, &amp;value);</P></DIR>
<P align=justify>else</P>
<DIR>
<P align=justify>DDX_TextWithFormat(pDX, nIDC, _T("%ld"), AFX_IDP_PARSE_INT, value);</P></DIR>
<P align=justify>}</P>
<P align=justify>上述DDX_TEXT用来在编辑框和long类型的数据成员之间交换数据。MFC提供了DDX_TEXT的多个重载函数处理编辑框和不同类型的数据成员之间的数据交换。</P>
<UL>
<P align=justify>
<LI>void AFXAPI DDX_LBString(CDataExchange* pDX, int nIDC,CString&amp; value)
<p></LI></UL>
<P align=justify>{</P>
<P align=justify>//得到列表框句柄</P>
<P align=justify>HWND hWndCtrl = pDX-&gt;PrepareCtrl(nIDC);</P>
<P align=justify>if (pDX-&gt;m_bSaveAndValidate)//TRUE,读取数据</P>
<P align=justify>{</P>
<DIR>
<P align=justify>//确定列表框当前被选择的条目</P>
<P align=justify>int nIndex = (int)::SendMessage(hWndCtrl, LB_GETCURSEL, 0, 0L);</P>
<P align=justify>if (nIndex != -1) //列表框有一个条目被选中</P>
<P align=justify>{</P>
<DIR>
<P align=justify>//得到当前条目的长度</P>
<P align=justify>int nLen = (int)::SendMessage(hWndCtrl, LB_GETTEXTLEN, nIndex, 0L);</P>
<P align=justify>//读取当前条目的内容到value中</P>
<P align=justify>::SendMessage(hWndCtrl, LB_GETTEXT, nIndex,</P>
<DIR>
<DIR>
<P align=justify>(LPARAM)(LPVOID)value.GetBufferSetLength(nLen));</P></DIR></DIR></DIR>
<P align=justify>}</P>
<P align=justify>else //当前列表框没有条目被选中</P>
<P align=justify>{</P>
<DIR>
<P align=justify>value.Empty();</P></DIR>
<P align=justify>}</P>
<P align=justify>value.ReleaseBuffer();</P></DIR>
<P align=justify>}</P>
<P align=justify>else//FALSE,写内容到列表框</P>
<P align=justify>{</P>
<DIR>
<P align=justify>// 把value字符串写入当前选中的条目</P>
<P align=justify>if (::SendMessage(hWndCtrl, LB_SELECTSTRING, </P>
<DIR>
<P align=justify>(WPARAM)-1,(LPARAM)(LPCTSTR)value) == LB_ERR)</P></DIR>
<P align=justify>{</P>
<DIR>
<P align=justify>// no selection match</P>
<P align=justify>TRACE0("Warning: no listbox item selected.\n");</P></DIR>
<P align=justify>}</P></DIR>
<P align=justify>}</P>
<P align=justify>}</P>
<P align=justify>DDX_LBString用来在列表框和CString类型的成员数据之间交换数据。首先,得到列表框的句柄,然后,调用Win32的列表框操作函数读取或者修改列表框的内容。</P>
<UL>
<P align=justify>
<LI>下面的DDX_Control用于得到一个有效的控制类型窗口对象(MFC对象)。
<p></LI></UL>
<P align=justify>void AFXAPI DDX_Control(CDataExchange* pDX, int nIDC, CWnd&amp; rControl)</P>
<P align=justify>{</P>
<P align=justify>if (rControl.m_hWnd == NULL) // 还没有子类化</P>
<P align=justify>{</P>
<DIR>
<P align=justify>ASSERT(!pDX-&gt;m_bSaveAndValidate);</P>
<P align=justify>//得到控制窗口句柄</P>
<P align=justify>HWND hWndCtrl = pDX-&gt;PrepareCtrl(nIDC);</P>
<P align=justify>//把hWndCtrl窗口和MFC窗口对象rControl捆绑在一起</P>
<P align=justify>if (!rControl.SubclassWindow(hWndCtrl))</P>
<P align=justify>{</P>
<DIR>
<P align=justify>ASSERT(FALSE); //不允许两次子类化</P>
<P align=justify>AfxThrowNotSupportedException();</P></DIR>
<P align=justify>}</P>
<P align=justify>#ifndef _AFX_NO_OCC_SUPPORT//OLE控制相关的操作</P>
<DIR>
<P align=justify>else</P></DIR>
<P align=justify>{</P>
<DIR>
<P align=justify>// If the control has reparented itself (e.g., invisible control),</P>
<P align=justify>// make sure that the CWnd gets properly wired to its control site.</P>
<P align=justify>if (pDX-&gt;m_pDlgWnd-&gt;m_hWnd != ::GetParent(rControl.m_hWnd))</P>
<DIR>
<DIR>
<P align=justify>rControl.AttachControlSite(pDX-&gt;m_pDlgWnd);</P></DIR></DIR>
<P align=justify>}</P>
<P align=justify>#endif //!_AFX_NO_OCC_SUPPORT</P>
<P align=justify>#endif //!_AFX_NO_OCC_SUPPORT</P>
<P align=justify></P>
<P align=justify>}</P>
<P align=justify>}</P>
<P align=justify>DDX_Control用来把控制窗口(Windows窗口)和一个对话框成员(MFC窗口对象)捆绑在一起,这个过程是通过SubclassWindow函数完成的。这样,程序员就可以通过成员变量来操作控制窗口,读、写、修改控制窗口的内容。</P>
<P align=justify>MFC还提供了许多其他数据交换函数(“DDX_”为前缀)和数据验证函数(“DDV_”为前缀)。DDV函数和DDX函数类似,这里不再多述。</P>
<P align=justify>程序员可以创建自己的数据交换和验证函数并使用它们,可以手工加入这些函数到DoDataExchange中,如果要Classwizard使用这些函数,可以修改DDX.CLW文件,在DDX、DDV函数入口中加入自己创建的函数。</P></DIR></DIR>
 楼主| 发表于 2006-12-8 10:32:58 | 显示全部楼层
<OL>
<OL>
<OL>
<OL>
< align=justify>
<LI><B>UpdateData函数</B>
<p></LI></OL></OL></OL></OL>
< align=justify>有了数据交换类和数据交换函数,怎么来使用它们呢?MFC设计了UpdateData函数来完成上述数据交换和验证的处理。</P>
< align=justify>首先,UpdateData创建CDataExchange对象,然后调用DoDataExchange函数。其实现如下:</P>
< align=justify>BOOL CWnd::UpdateData(BOOL bSaveAndValidate)</P>
< align=justify>{</P>
< align=justify>ASSERT(::IsWindow(m_hWnd)); // calling UpdateData before DoModal?</P>
< align=justify></P>
< align=justify>//创建CDataChange对象</P>
< align=justify>CDataExchange dx(this, bSaveAndValidate);</P>
< align=justify></P>
<P align=justify>//防止在UpdateData期间派发通知消息给该窗口</P>
<P align=justify>_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();</P>
<P align=justify>HWND hWndOldLockout = pThreadState-&gt;m_hLockoutNotifyWindow;</P>
<P align=justify>ASSERT(hWndOldLockout != m_hWnd); // must not recurse</P>
<P align=justify>pThreadState-&gt;m_hLockoutNotifyWindow = m_hWnd;</P>
<P align=justify></P>
<P align=justify>BOOL bOK = FALSE; // assume failure</P>
<P align=justify>TRY</P>
<P align=justify>{</P>
<DIR>
<P align=justify>//数据交换</P>
<P align=justify>DoDataExchange(&amp;dx);</P>
<P align=justify>bOK = TRUE; // it worked</P></DIR>
<P align=justify>}</P>
<P align=justify>CATCH(CUserException, e)//例外</P>
<P align=justify>{</P>
<DIR>
<P align=justify>// validation failed - user already alerted, fall through</P>
<P align=justify>ASSERT(bOK == FALSE);</P>
<P align=justify>// Note: DELETE_EXCEPTION_(e) not required</P></DIR>
<P align=justify>}</P>
<DIR>
<P align=justify>AND_CATCH_ALL(e)</P></DIR>
<P align=justify>{</P>
<DIR>
<P align=justify>// validation failed due to OOM or other resource failure</P>
<P align=justify>e-&gt;ReportError(MB_ICONEXCLAMATION, FX_IDP_INTERNAL_FAILURE);</P>
<P align=justify>ASSERT(!bOK);</P>
<P align=justify>DELETE_EXCEPTION(e);</P></DIR>
<P align=justify>}</P>
<P align=justify>END_CATCH_ALL</P>
<P align=justify></P>
<P align=justify>//恢复原来的值</P>
<P align=justify>pThreadState-&gt;m_hLockoutNotifyWindow = hWndOldLockout;</P>
<P align=justify>return bOK;</P>
<P align=justify>}</P>
<P align=justify>UpdataDate根据参数创建CDataExchange对象dx,如果参数为TRUE,dx用来写数据,否则dx用来读数据;然后调用DoDataExchange进行数据交换。在数据交换期间,为了防止当前窗口接收和处理命令通知消息,在当前线程的线程状态中记录该窗口的句柄,用来防止给该窗口发送通知消息。</P>
<P align=justify>使用MFC的数据交换和验证机制,大大简化了程序员的工作。通常在OnInitDialog中,MFC调用UpdateData(FALSE)把数据送给控制窗口显示;在OnOk中,调用UpdateData(TRUE)从控制窗口中读取数据。</P>
<OL>
<OL>
<P align=justify>
<LI><B>无模式对话框</B>
<p>
<P>CFormView是MFC使用无模式对话框的一个典型例子。CFormView是基于对话框模板创建的视,它的直接基类是CSrcollView,CSrcollView的直接基类才是CView。所以,这里先对CScorllView作一个简要的介绍。</P>
<OL>
<P align=justify>
<LI><B>CScrollView</B>
<p></LI></OL></LI></OL></OL>
<P align=justify>CScrollView继承了CView的特性,并且增加了如下的功能:</P>
<P align=justify>(1)管理映射模式、窗口尺寸、视口尺寸(Map mode、Window and Viewport size)。Window and Viewport size用来完成页面空间到设备空间的转换。</P>
<P align=justify>(2)自动管理滚动条,响应滚动条消息。</P>
<P align=justify>为了实现这些功能,CScrollView覆盖CView或者CWnd的一些虚拟函数和消息处理函数,添加了一些新的函数,当然也设计了新的成员变量。</P>
<UL>
<P align=justify>
<LI>CscrollView新的成员变量
<p></LI></UL>
<P align=justify>protected:</P>
<P align=justify>int m_nMapMode;</P>
<P align=justify>CSize m_totalLog; // total size in logical units (no rounding)</P>
<P align=justify>CSize m_totalDev; // total size in device units</P>
<P align=justify>CSize m_pageDev; // per page scroll size in device units</P>
<P align=justify>CSize m_lineDev; // per line scroll size in device units</P>
<P align=justify></P>
<P align=justify>BOOL m_bCenter; // Center output if larger than total size</P>
<P align=justify>BOOL m_bInsideUpdate; // internal state for OnSize callback</P>
<UL>
<P align=justify>
<LI>CScrollView新的成员函数,用来完成和滚动操作、滚动条等有关的功能
<p></LI></UL>
<P align=justify>void SetScaleToFitSize(SIZE sizeTotal);</P>
<P align=justify>void SetScrollSizes(int nMapMode, SIZE sizeTotal,</P>
<P align=justify>const SIZE&amp; sizePage = sizeDefault,</P>
<P align=justify>const SIZE&amp; sizeLine = sizeDefault);</P>
<P align=justify>这两个函数中的尺寸大小按逻辑单位计算。</P>
<P align=justify>SetScaleToFitSize设置视口尺寸为当前的窗口尺寸,这样,在没有滚动条时,逻辑视的内容被放大或者缩小到正好窗口大小。</P>
<P align=justify>SetScrollSizes设置窗口的映射模式,窗口尺寸,页和行尺寸。sizeDefualt被定义为(0,0)。</P>
<UL>
<P align=justify>
<LI>下面几个函数用来实现滚动或者得到滚动条相关的信息
<p></LI></UL>
<P align=justify>void ScrollToPosition(POINT pt); // set upper left position</P>
<P align=justify>void FillOutsideRect(CDC* pDC, CBrush* pBrush);</P>
<P align=justify>void ResizeParentToFit(BOOL bShrinkOnly = TRUE);</P>
<P align=justify>CPoint GetScrollPosition() const; // upper corner of scrolling</P>
<P align=justify>CSize GetTotalSize() const; // logical size</P>
<UL>
<P align=justify>
<LI>下面两个函数使用了设备坐标单位
<p></LI></UL>
<P align=justify>CPoint GetDeviceScrollPosition() const;</P>
<P align=justify>void GetDeviceScrollSizes(int&amp; nMapMode, SIZE&amp; sizeTotal,</P>
<P align=justify>SIZE&amp; sizePage, SIZE&amp; sizeLine) const;</P>
<P align=justify></P>
<UL>
<P align=justify>
<LI>覆盖的消息处理函数
<p></LI></UL>
<P align=justify>处理WM_SIZE的OnSize;</P>
<P align=justify>处理WM_HSCROLL的OnHScroll;</P>
<P align=justify>处理WM_VSCROLL的OnVScroll;</P>
<P align=justify></P>
<UL>
<P align=justify>
<LI>覆盖的虚拟函数
<p></LI></UL>
<P align=justify>CWnd的CalcWindowRect</P>
<P align=justify>CView的OnPrepareDC、OnScroll、OnScrollBy</P>
<UL>
<P align=justify>
<LI>用于DEBUG的Dump和AssertValid
<p></LI></UL>
<P align=justify>这里,覆盖的消息处理函数和虚拟函数共同完成对滚动条、滚动消息的处理。</P>
<P align=justify>在CSrcollView的实现涉及到许多和Windows映射模式、坐标转换等相关的函数的使用。这里,不作具体讨论。</P>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>CFormView</B>
<p></LI></OL></OL></OL>
<P align=justify>CFormView派生于CSrcollView,本身没有增加新的函数,但覆盖了一些基类的虚拟函数,增加了几个成员变量(以下列出的不包含OLE处理)。</P>
<OL>
<P align=justify>
<LI>增加的成员变量
<p>
<P align=justify>LPCTSTR m_lpszTemplateName;</P>
<P align=justify>CCreateContext* m_pCreateContext;</P>
<P align=justify>HWND m_hWndFocus; // last window to have focus</P>
<P align=justify>m_lpszTemplateName用来保存创建视图的对话框模板的名称,_pCreateContext用来保存创建上下文,m_hWndFocus用来保存最近一次拥有焦点的控制窗口。在构造CFormView对象时,构造函数把有关信息保存到成员变量中,如下所示:</P>
<P align=justify>CFormView::CFormView(LPCTSTR lpszTemplateName)</P>
<P align=justify>{</P>
<P align=justify>m_lpszTemplateName = lpszTemplateName;</P>
<P align=justify>m_pCreateContext = NULL;</P>
<P align=justify>m_hWndFocus = NULL; // focus window is font</P>
<P align=justify>}</P>
<P align=justify></P>
<LI>覆盖的虚拟函数
<p>
<P align=justify>virtual void OnDraw(CDC* pDC); // MFC缺省处理空</P>
<P align=justify>virtual BOOL Create(LPCTSTR, LPCTSTR, DWORD,</P>
<P align=justify>const RECT&amp;, CWnd*, UINT, CCreateContext*);</P>
<P align=justify>virtual BOOL PreTranslateMessage(MSG* pMsg);</P>
<P align=justify>virtual void OnActivateView(BOOL, CView*, CView*);</P>
<P align=justify>virtual void OnActivateFrame(UINT, CFrameWnd*);</P>
<P align=justify>创建基于对话框的视窗口,不同于创建普通视窗口(前者调用CWnd::CreateEx,后者调用CWnd::CreateDlg),故需要覆盖Create虚拟函数。</P>
<P align=justify>覆盖PreTranslateMessage是为了过滤对话框消息,把一些消息让CFormView对象来处理。</P>
<P align=justify></P></LI></OL>
 楼主| 发表于 2006-12-8 10:33:10 | 显示全部楼层
<OL>
<LI>
<>覆盖了两个消息处理函数: </P>
<p></LI></OL>
< align=justify>afx_msg int OnCreate(LPCREATESTRUCT lpcs);</P>
< align=justify>afx_msg void OnSetFocus(CWnd* pOldWnd);</P>
< align=justify>下面,分析几个函数作。Create函数解释了MFC如何使用一个对话框作为视的方法,PreTranslateMessage显示了CFormView不同于CDialog的实现。</P>
<OL>
<OL>
<OL>
<OL>
< align=justify>
<LI><B>CFormView的创建</B>
<p>
< align=justify>设计CFormView的创建函数,必须考虑两个问题:</P>
< align=justify>首先,CFormView是一个视,其创建函数必须是一个虚拟函数,原型必须和CWnd::Create(LPSTR…pContext)函数一致,见图5-13视的创建。其次,CFormView使用了对话框创建函数和对话框“窗口类”来创建视,但必须作一些处理使得该窗口具备视的特征。</P>
< align=justify>Create的实现如下:</P>
< align=justify>BOOL CFormView::Create(LPCTSTR /*lpszClassName*/,</P>
< align=justify>LPCTSTR /*lpszWindowName*/,</P>
<P align=justify>DWORD dwRequestedStyle, const RECT&amp; rect, CWnd* pParentWnd, UINT nID,</P>
<P align=justify>CCreateContext* pContext)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(pParentWnd != NULL);</P>
<P align=justify>ASSERT(m_lpszTemplateName != NULL);</P>
<P align=justify></P>
<P align=justify>m_pCreateContext = pContext; // save state for later OnCreate</P>
<P align=justify></P>
<P align=justify>#ifdef _DEBUG</P>
<P align=justify>// dialog template must exist and be invisible with WS_CHILD set</P>
<P align=justify>if (!_AfxCheckDialogTemplate(m_lpszTemplateName, TRUE))</P>
<P align=justify>{</P>
<P align=justify>ASSERT(FALSE); // invalid dialog template name</P>
<P align=justify>PostNcDestroy(); // cleanup if Create fails too soon</P>
<P align=justify>return FALSE;</P>
<P align=justify>}</P>
<P align=justify>#endif //_DEBUG</P>
<P align=justify></P>
<P align=justify>//若common control window类还没有注册,则注册</P>
<P align=justify>VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG));</P>
<P align=justify></P>
<P align=justify>// call PreCreateWindow to get prefered extended style</P>
<P align=justify>CREATESTRUCT cs; memset(&amp;cs, 0, sizeof(CREATESTRUCT));</P>
<P align=justify>if (dwRequestedStyle == 0)</P>
<P align=justify>dwRequestedStyle = AFX_WS_DEFAULT_VIEW;</P>
<P align=justify>cs.style = dwRequestedStyle;</P>
<P align=justify>if (!PreCreateWindow(cs))</P>
<P align=justify>return FALSE;</P>
<P align=justify></P>
<P align=justify>//::CreateDialogIndirect间接被调用来创建一个无模式对话框</P>
<P align=justify>if (!CreateDlg(m_lpszTemplateName, pParentWnd))</P>
<P align=justify>return FALSE;</P>
<P align=justify>//创建对话框时,OnCreate被调用,m_pCreateContext的作用结束了</P>
<P align=justify>m_pCreateContext = NULL;</P>
<P align=justify></P>
<P align=justify>// we use the style from the template - but make sure that</P>
<P align=justify>// the WS_BORDER bit is correct</P>
<P align=justify>// the WS_BORDER bit will be whatever is in dwRequestedStyle</P>
<P align=justify>ModifyStyle(WS_BORDER|WS_CAPTION, cs.style &amp; (WS_BORDER|WS_CAPTION));</P>
<P align=justify>ModifyStyleEx(WS_EX_CLIENTEDGE, cs.dwExStyle &amp; WS_EX_CLIENTEDGE);</P>
<P align=justify></P>
<P align=justify>SetDlgCtrlID(nID);</P>
<P align=justify></P>
<P align=justify>CRect rectTemplate;</P>
<P align=justify>GetWindowRect(rectTemplate);</P>
<P align=justify>SetScrollSizes(MM_TEXT, rectTemplate.Size());</P>
<P align=justify></P>
<P align=justify>// initialize controls etc</P>
<P align=justify>if (!ExecuteDlgInit(m_lpszTemplateName))</P>
<P align=justify>return FALSE;</P>
<P align=justify></P>
<P align=justify>// force the size requested</P>
<P align=justify>SetWindowPos(NULL, rect.left, rect.top,</P>
<P align=justify>rect.right - rect.left, rect.bottom - rect.top,</P>
<P align=justify>SWP_NOZORDER|SWP_NOACTIVATE);</P>
<P align=justify></P>
<P align=justify>// make visible if requested</P>
<P align=justify>if (dwRequestedStyle &amp; WS_VISIBLE)</P>
<P align=justify>ShowWindow(SW_NORMAL);</P>
<P align=justify></P>
<P align=justify>return TRUE;</P>
<P align=justify>}</P>
<P align=justify>从Create的实现过程可以看出,CreateDialog在创建对话框时使用了Windows预定义的对话框“窗口类”,PreCreateWindow返回的cs在创建对话框窗口时并没有得到体现,所以在CFormView::Create调用PreCreateWindow让程序员修改“窗口类”的风格之后,还要调用ModifyStyle和ModifyStyleEx来按PreCreateWindow返回的cs的值修改窗口风格。</P>
<P align=justify>回顾视窗口的创建过程,Create函数被CFrameWnd::CreateView所调用,参数nID取值AFX_IDW_PANE_FIRST。由于CreateDlg设置对话框窗口的ID为对话框模板的ID,所以需要调用函数SetDlgCtrlID(nID)设置视窗口ID为nID(即AFX_IDW_PANE_FIRST)。</P>
<P align=justify>由于CFormView是从CScrollView继承,所以调用SetScrollSize设置映射模式,窗口尺寸等。</P>
<P align=justify>完成上述动作之后,初始化对话框的控制子窗口。</P>
<P align=justify>最后,必要的话,显示视窗口。</P>
<P align=justify>这样,一个无模式对话框被创建,它被用作当前MDI窗口或者MDI子窗口的视。如同CDialog的消息处理一样,必要时,消息或者事件将传递给视原来的窗口过程(无模式对话框的原窗口过程)处理,其他的消息处理和通常视一样。</P>
<P align=justify></P>
<P align=justify>由于是调用对话框创建函数创建视窗口,所以不能向::CreateWindowEX传递创建上下文指针,于是把它保存到成员变量m_pCreateContext中,在OnCreate时使用。OnCreate的实现如下:</P>
<P align=justify>int CFormView::OnCreate(LPCREATESTRUCT lpcs)</P>
<P align=justify>{</P>
<P align=justify>//既然不能通过CreateDialog使用参数传递的方法得到创建上下文</P>
<P align=justify>//参数,则使用一个成员变量来传递</P>
<P align=justify>return CScrollView::OnCreate(lpcs);</P>
<P align=justify>}</P>
<P align=justify></P>
<LI><B>CFormView的消息预处理</B>
<p>
<P align=justify>现在,讨论CFormView 的PreTranslateMessage函数。CDialog覆盖函数PreTranslateMessage的主要目的是处理Tooltip消息、Escape键盘消息和Dialog消息。CFormView覆盖该函数的目的是处理Tooltip消息和Dialog消息。CFormView和CDialog不同之处在于CFormView是一个视,故在把键盘消息当Dialog消息处理之前,必须优先让其父窗口检查按下的键是否是快捷键。PreTranslateMessage函数实现如下:</P>
<P align=justify>BOOL CFormView:reTranslateMessage(MSG* pMsg)</P>
<P align=justify>{</P>
<P align=justify>ASSERT(pMsg != NULL);</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 (CView:reTranslateMessage(pMsg))</P>
<P align=justify>return TRUE;</P>
<P align=justify></P>
<P align=justify>//SHIFT+F1上下文帮助模式下,不处理Dialog消息</P>
<P align=justify>CFrameWnd* pFrameWnd = GetTopLevelFrame();</P>
<P align=justify>if (pFrameWnd != NULL &amp;&amp; pFrameWnd-&gt;m_bHelpMode)</P>
<P align=justify>return FALSE;</P>
<P align=justify></P>
<P align=justify>//既然IsDialogMessage将把窗口快捷键解释成Dialog消息</P>
<P align=justify>//所以在此先调用所有父边框窗口的消息预处理函数</P>
<P align=justify>pFrameWnd = GetParentFrame(); // start with first parent frame</P>
<P align=justify>while (pFrameWnd != NULL)</P>
<P align=justify>{</P>
<P align=justify>// allow owner &amp; frames to translate before IsDialogMessage does</P>
<P align=justify>if (pFrameWnd-&gt;PreTranslateMessage(pMsg))</P>
<P align=justify>return TRUE;</P>
<P align=justify></P>
<P align=justify>// try parent frames until there are no parent frames</P>
<P align=justify>pFrameWnd = pFrameWnd-&gt;GetParentFrame();</P>
<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>由于CFormView是一个视,不是模式对话框,所以它首先要把消息给父窗口(MDI子窗口或者MDI窗口)预处理,如果它们不能处理,则调用PreTranslateInput来过滤Dialog消息。</P>
<P align=justify></P>
<LI><B>CFormView的输入焦点</B>
<p></LI></OL></OL></OL></OL>
<P align=justify>CFormView另一个特性是:在和用户交互中,如果用户离开视窗口,则必须保存CFormView视的哪个控制子窗口拥有输入焦点,以便在重新激活视窗口时,原来的那个窗口重新获得输入焦点。所以,CFormView覆盖了虚拟函数OnActivateView和OnActiveFrame,以便在视窗口失去激活时把它的当前输入焦点保存到成员变量m_hWndFocus中。</P>
<P align=justify>为了在适当时候恢复输入焦点,CFormView覆盖了消息处理函数OnSetFocus,以便在视获得输入焦点时把输入焦点传递给m_hWndFocus(如果非空)。</P>
<P align=justify>至此,MFC实现对话框的处理分析完毕。</P>
<P align=justify>在后面要讨论的工具条等控制窗口,类似于对话框也具备由Windows提供的窗口过程,MFC在SDK的特定控制窗口创建函数的基础上,提供了MFC的窗口创建函数,使用MFC的窗口过程取代了它们原来的窗口过程,然后在必要的时候调用Default把有关消息和事件传递给原来的窗口过程处理。</P>
 楼主| 发表于 2006-12-8 10:34:16 | 显示全部楼层
<OL start=13>
< align=justify>
<LI><B>MFC工具条和状态栏</B>
<p>
<OL>
< align=justify>
<LI><B>Windows控制窗口</B>
<p></LI></OL></LI></OL>
< align=justify>Windows (Windows95或者以上版本) 提供了系列通用控制窗口,其中包括工具条(ToolBar)、状态栏(StatusBar)、工具条提示窗口(ToolTip)。</P>
< align=justify>Windows在一个DLL加载时注册个控制窗口的“窗口类”。例如,工具条的“窗口类”是“ToolbarWindow32”,状态栏的“窗口类”是“msctls_statusbar32”,工具条提示窗口的“窗口类”是“tooltips_class32”。为了保证该DLL被加载,使用控制“窗口类”前,应该首先调用函数InitCommonControl。MFC在窗口注册函数AfxDeferRegisterClass中实现了这一点。见2.2.1节MFC下窗口的注册。</P>
< align=justify>创建通用控制窗口,可以使用专门的创建函数,如创建工具条的函数::CreateToolBarEx,创建状态栏的函数::CreateStatusBarEx。也可以调用窗口创建函数::CreateWindowEx,但是需要指定预定义的“窗口类”,必要的话还要其他步骤,如使用“ToolbarWindow32”“窗口类”创建工具栏后,还需要在工具栏中添加或者插入按钮。</P>
< align=justify>一般,通用控制可以指定控制窗口风格(Style)。例如,具备风格CCS_TOP,表示该控制窗口放到父窗口客户区的顶部,具备CCS_BOTTOM,表示该控制窗口在客户区的底部。具体的控制窗口类可以有特别的适合于自己的风格,例如,TTS_ALWAYSTIP表示只要光标落在工具栏的按钮上,ToolTip窗口不论激活与否都会显示出来。</P>
< align=justify>每一控制窗口类都有自己的窗口过程来处理自己的窗口消息,实现特定的功能。控制窗口类的窗口过程由Windows提供。</P>
<UL>
< align=justify>
<LI>工具条
<p></LI></UL>
< align=justify>工具条的窗口过程处理了必要的消息,提供了标准工具条的功能,例如,工具条对客户化特征提供内在的支持,用户可以通过一个客户化对话框来添加、修改、删除或者重新安排工具条按钮。这些特征是否可以被用户所用或者用到什么地步是可以由程序控制的。</P>
< align=justify>工具条的窗口过程将自动设置工具条的尺寸大小和位置,如果指定了控制窗口风格CCS_TOP或者CCS_BOTTOM,则窗口过程把工具条放到父窗口客户区的顶部或者底部。窗口过程任何时候只要收到WM_SIZE或者TB_AUTOSIZE消息就自动地调整工具条的大小和位置。</P>
<P align=justify>工具条的按钮被选中后,会产生一个命令消息,它的窗口过程把该消息送给父窗口的窗口过程处理。</P>
<P align=justify>工具条中的按钮并不以子窗口的形式出现,而是以字符或者位图按钮的方式显示,每个按钮大小相同,缺省是24*22个像素。每个按钮都有一个索引,索引编号从0开始。每个按钮包括如下属性:</P>
<P align=justify>按钮的字符串索引,位图索引,风格,状态,命令ID</P>
<P align=justify>按钮可以有两种风格TBSTYLE_BUTTON和TBSTYLE_CHECK,前者像一个标准按钮那样响应用户的按击,后者响应每一次按击,在按下和跳起两种状态之间切换。按钮响应用户的动作,给父窗口发送一个包含了该按钮对应命令ID的命令消息。一般一个按钮的命令ID对应一个菜单项。</P>
<P align=justify>工具条维护两个列表,分别用来存放工具条按钮使用的字符串或者位图,列表中的位图或者字符串从0开始编号,编号和按钮的索引相对应。</P>
<P align=justify>工具条可以是Dockable(泊位)或者Floatable(漂浮)的。</P>
<P align=justify>工具条可以有TBSTYLE_TOOLTIPS风格,如果具有这种风格,则创建和管理一个Tooltip控制,这是一个小的弹出式窗口,用来显示描述按钮的文本,平时该窗口隐藏,当鼠标落到按钮上面并停留约一秒后才弹出,在鼠标附近显示。</P>
<P align=justify>由于Tooltip窗口平时是隐藏的,所以不能接收鼠标消息来决定何时显示本窗口。这样,接收鼠标的窗口必须把鼠标消息送给Tooltip窗口,这是通过给Tooptip窗口发送消息TTM_RELAYEVENT来实现的。</P>
<UL>
<P align=justify>
<LI>状态栏
<p></LI></UL>
<P align=justify>状态栏类似于工具条,有自己的窗口过程,可以泊位、漂浮。不过,习惯上状态栏都位于屏幕底部。每个状态条分成若干格(Status bar panes),每格从0开始编号,编号作为格的索引。每一个格,如同工具条的按钮一样,并不是一个Windows窗口。</P>
<OL>
<OL>
<P align=justify>
<LI><B>MFC的工具条和状态栏类</B>
<p></LI></OL></OL>
<P align=justify>MFC使用CToolBarCtrl、CStatusBarCtrl和CToolTipCtrl窗口类分别对工具条、状态栏、Tooltip控制窗口进行了封装。</P>
<P align=justify>但是,直接使用这些类还不是很方便。MFC提供了CToolBar、CStatusBar来处理状态栏和工具条,CToolBar、CStatusBar功能更强大,灵活。这两个类都派生于CControlBar。</P>
<P align=justify>在MFC下,建议这些控制条子窗口ID介于AFX_IDW_TOOLBARFIRST(0xE800)和AFX_IDW_CONTROLBAR_LAST(0Xe8FF)之间。这256个ID中,前32个又有其特殊性,用于MFC的打印预览中。</P>
<P align=justify>CControlBar派生于CWnd类,是控制条窗口类的基类,它派生出CToolBar、CStatusBar、CDockBar、CDialogBar、COleResizeBar类。CControlBar实现了以下功能:</P>
<UL>
<P align=justify>
<LI>和父窗口(边框窗口)的顶部或者底部或者其他边对齐。
<p>
<P align=justify></P>
<LI>可以包含子条目,这些条目或者是基于HWND的子窗口,或者是基于非HWND的条目。负责分配条目数组。
<p>
<P align=justify></P>
<LI>支持CBRS_TOP(缺省,控制条放在顶部),CBRS_BOTTOM(放在底部),CBRS_NOALIGN(父窗口大小变化时不重新放置控制条)等几种控制风格。
<p>
<P align=justify></P>
<LI>支持派生类的实现。几个派生类有一定的共性,或者其中两个有一定的共性,这样CControlBar实现的函数一部分只适用于某个派生类,一部分适用于两个或者多个派生类,还有一部分适用于所有的派生类。所谓适用,这里指派生类直接继承了CControlBar的实现,或者覆盖了其实现但是建立在扩展其实现的基础上。类似地,CControlBar的成员变量也不是为所有派生类所共同适用的。
<p></LI></UL>
<P align=justify>CStatusBar和CControlBar一方面建立在CControlBar的基础之上,另一方面以Windows的通用控制状态栏和工具条为基础。它们继承了CControlBar类的特性,但是所封装的窗口句柄是相应的Windows控制窗口的句柄,如同CFormView继承了CSrcollView的视类特性,但是其窗口句柄是无模式对话框窗口句柄一样。</P>
<P align=justify></P>
<P align=justify>典型地,如果在使用AppWizard生成应用程序时,指定了要求工具条和状态栏的支持,则在主边框窗口的OnCreate函数中包含一段如下的代码,用来创建工具条、状态栏和设置一些特性。</P>
<P align=justify>//创建工具栏</P>
<P align=justify>if (!m_wndToolBar.Create(this) ||!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))</P>
<P align=justify>{</P>
<DIR>
<P align=justify>TRACE0("Failed to create toolbar\n");</P>
<P align=justify>return -1; // fail to create</P></DIR>
<P align=justify>}</P>
<P align=justify>//创建状态栏</P>
<P align=justify>if (!m_wndStatusBar.Create(this) ||</P>
<DIR>
<P align=justify>!m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT)))</P></DIR>
<P align=justify>{</P>
<DIR>
<P align=justify>TRACE0("Failed to create status bar\n");</P>
<P align=justify>return -1; // fail to create</P></DIR>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>// TODO: Remove this if you don't want tool tips or a resizeable toolbar</P>
<P align=justify>//对工具栏设置Tooltip特征</P>
<P align=justify>m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |</P>
<DIR>
<P align=justify>CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);</P>
<P align=justify></P></DIR>
<P align=justify>//使得工具栏可以泊位在边框窗口</P>
<P align=justify>// TODO: Delete these three lines if you don't want the toolbar to</P>
<P align=justify>// be dockable</P>
<P align=justify>m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);</P>
<P align=justify>EnableDocking(CBRS_ALIGN_ANY);</P>
<P align=justify>DockControlBar(&amp;m_wndToolBar);</P>
<P align=justify></P>
<P align=justify>工具条除了Tooltip,Resizeable,Dockable特性外,还可以是Floatable。应用程序可以使用CFrameWnd::SaveBarState保存边框窗口的控制条的有关信息到INI文件或者Windows Register库,使用LoadBarSate从INI文件或者Register库中读取有关信息并恢复各个控制条的设置。</P>
<P align=justify>下文,将讨论工具条等的创建、销毁,从中分析CControlBar和派生类的关系,讨论CControlBar如何实现共性,如何支持派生类的特定要求,派生类又如何实现自己的特定需求等。</P>
<OL>
<OL>
<OL>
<P align=justify>
<LI><B>控制窗口的创建</B>
<p>
<P align=justify>创建工具条、状态条、对话框工具栏的方法是不同的,所以必须给每个派生类CToolBar、CStatusBar、CDialogBar设计和实现自己的窗口创建函数Create。但是,它们是也是有共性的,共性由CControlBar的PreCreateWindow处理。在窗口创建之后,各个派生类都要进行的处理(共性)由CControlBar的OnCreate完成,特别的处理通过派生类的OnNcCreate完成。</P>
<OL>
<P align=justify>
<LI><B>PreCreateWindow</B>
<p>
<P align=justify>首先,讨论CControlBar 类的PreCreateWindow的实现。</P>
<P align=justify>BOOL CControlBar:reCreateWindow(CREATESTRUCT&amp; cs)</P>
<P align=justify>{</P>
<P align=justify>if (!CWnd:reCreateWindow(cs))</P>
<P align=justify>return FALSE;</P>
<P align=justify></P>
<P align=justify>//修改窗口风格,强制适用clipsliblings,以防重复绘制</P>
<P align=justify>cs.style |= WS_CLIPSIBLINGS;</P>
<P align=justify></P>
<P align=justify>//default border style translation for Win4</P>
<P align=justify>//(you can turn off this translation by setting CBRS_BORDER_3D)</P>
<P align=justify>if (afxData.bWin4 &amp;&amp; (m_dwStyle &amp; CBRS_BORDER_3D) == 0)</P>
<P align=justify>{</P>
<P align=justify>DWORD dwNewStyle = 0;</P>
<P align=justify>switch (m_dwStyle &amp; (CBRS_BORDER_ANY|CBRS_ALIGN_ANY))</P>
<P align=justify>{</P>
<P align=justify>case CBRS_LEFT: //控制条在边框窗口的左边显示</P>
<P align=justify>dwNewStyle = CBRS_BORDER_TOP|CBRS_BORDER_BOTTOM;</P>
<P align=justify>break;</P>
<P align=justify>case CBRS_TOP://控制条在边框窗口的顶部显示</P>
<P align=justify>dwNewStyle = CBRS_BORDER_TOP;</P>
<P align=justify>break;</P>
<P align=justify>case CBRS_RIGHT://控制条在边框窗口的右边显示</P>
<P align=justify>dwNewStyle = CBRS_BORDER_TOP|CBRS_BORDER_BOTTOM;</P>
<P align=justify>break;</P>
<P align=justify>case CBRS_BOTTOM://控制条在边框窗口的底部显示</P>
<P align=justify>dwNewStyle = CBRS_BORDER_BOTTOM;</P>
<P align=justify>break;</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>// set new style if it matched one of the predefined border types</P>
<P align=justify>if (dwNewStyle != 0)</P>
<P align=justify>{</P>
<P align=justify>m_dwStyle &amp;= ~(CBRS_BORDER_ANY);</P>
<P align=justify>m_dwStyle |= (dwNewStyle | CBRS_BORDER_3D);</P>
<P align=justify>}</P>
<P align=justify>}</P>
<P align=justify>return TRUE;</P>
<P align=justify>}</P>
<P align=justify>其中,afxData是一个全局变量,MFC用它来记录系统信息,如版本信息等。这里afxData.bWin4表示Windows版本是否高于4.0。</P>
<P align=justify>CToolBar的PreCreateWindow函数修改了窗口风格,也修改状态栏、工具栏等的CBRS_风格。CBRS_风格的改变不会影响窗口风格。因为这些CBRS_风格被保存在成员变量m_dwStyle中。</P>
<P align=justify>除了上述在程序中用到的影响工具条、状态栏等显示位置的CBRS_风格外,还有和泊位相关的CBRS_风格,CBRS_ALIGN_LEFT、CBRS_ALIGN_RIGHT、CBRS_ALIGN_BOTTOM、CBRS_ALIGN_TOP、CBRS_ALIGN_ANY,分别表示工具条可以在停泊在边框窗口的左边、右边、底部、顶部或者所有这些位置;和漂浮相关的CBRS_风格CBRS_FLOAT_MULTI,表示多个工具条可以漂浮在一个微型边框窗口中;和Tooltips相关的CBRS_风格CBRS_TOOLTIPS和CBRS_FLYBY。</P>
<P align=justify>派生类如果没有特别的要求,可以不覆盖PreCreateWindow函数。CStatusBar因为有更具体和特殊的风格要求,所以它覆盖了PreCreateWindow。CStatusBar的覆盖实现调用了CControlBar的实现。</P>
<P align=justify>派生类也可以在覆盖实现中修改PreCreateWindow参数cs,改变窗口风格;修改m_dwStyle,改变CBRS_风格。</P></LI></OL></LI></OL></OL></OL>
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2025-2-6 09:45

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

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