第6章创建和使用对话框1第8章文档和视图精讲MFC应用程序的核心是文档/视图结构。在前面章节的学习中,已经接触了不少文档/视图结构的应用程序,本章将详细分析其结构和原理,并进一步学习使用复杂的文档结构、构造更加丰富的视图。8.1文档/视图概述使用MFC的AppWizard可以创建三种类型的应用程序:(1)单文档界面的应用程序(SDI:SingleDocumentInterface)(2)多文档界面的应用程序(MDI:MultipleDocumentsInterface)(3)基于对话框的应用程序(Dialogbased)基于对话框的应用程序框架非常简单,由应用程序类、对话框类构成。通过应用程序类的InitInstance()函数,构造一个模式对话框对象;调用DoModal()函数,让Windows对话框处理程序象通常情况一样接受和分配消息;用户退出对话框后,程序也就结束了。我们已经知道SDI应用程序由应用程序类(CWinApp)、框架窗口类(CFrameWnd)、文档类(CDocument)、视图类(CView)和文档模板类(CSingleDocTemplate)共同作用。MDI应用程序与SDI应用程序的主要差别在于:MDI有CMDIFrameWnd和CMDIChildWnd两个框架窗口类,前一个派生CMainFrame类,负责菜单等界面元素的主框架窗口管理;后一个派生CChildFrame类,负责相应的文档及其视图的子框架维护。而SDI由框架窗口类CFrameWnd派生CMainFrame类。一个文档可以有多个视图,但一个视图只能对应一个确定的文档。因此,MDI应用程序需要解决的问题是多个文档的数据管理方法。在MDI应用程序中,文档模板只支持主窗口。每打开一个新文档时,都调用文档类的成员函数OnNewDocument(),建立一个由CMDIChildWnd派生的新的MDI子窗口,在子窗口中保存已打开的文档,所有这些细节都由MFC库来处理。8.1.1文档和视图的关系文档/视图结构的最大特点就是:把数据操作和数据表示分离开来,与数据库管理系统提供的数据库与视图的关系一致。图8-1说明了文档及其视图之间的关系。所有对数据的修改由文档对象来完成,用视图调用这个对象的方法来访问和更新数据。VC++6简明教程2图8-1文档和视图的关系在MFC应用程序框架中,文档和视图的关系主要体现在:文档类和视图类对象的相互作用和相互访问上。如前面章节所述,关系图示如下:图8-2文档和视图的相互访问对图示中的函数介绍如下:1.CView::GetDocument()返回文档类的指针,通过该指针在视图类中访问并更新文档数据。2.CDocument::UpdateAllViews(CView*pSender,LPARAMlHint=0,Cobject*pHint=NULL)该函数通知与文档相连的所有或部分视图,更新窗口内容。在MFC应用程序框架中,由于文档和视图的一对多关系,当用户在一个视图中修改文档后,本视图将发生改变,相应地,与文档相连的其他视图也应与更新后的文档内容保持一致。这时,本视图便可以调用该函数向其他视图窗口发出WM_PAINT消息,通知它们更新。pSender为修改文档并发出更新通知的视图的指针,当pSender为NULL时通知与文档相连的所有视图更新,当pSender不为NULL时,通知除pSender代表的视图以外的与文档相连的所有视图更新,得到更新的视图类将调用该类的OnUpdate()函数。lHint和pHint是关于视图更新内容的提示。lHint可自定义含义,pHint是一个CObject指针,能够表示MFC所有的对象,它规定了视图需要更新的区域。经常使用来规定部分刷新区域,从而避免全部区域刷新带来的屏幕闪动。3.CView::OnUpdate()CDocument::UpdateAllViews()文档类视图类CView::GetDocument()CView::OnInitialUpdate()CView::Invalidate()CView::InvalidateRect()A12345020040060012345ABCDABCD15565587982232337310934542265143467447818752421190203数据文档视图第6章创建和使用对话框3该函数是一个虚函数,当应用程序调用CDocument::UpdateAllViews()函数时,应用程序框架会相应地调用它。还可以在应用程序视图类的派生类中,直接调用OnUpdate()函数,OnUpdate()函数访问文档,得到文档的数据,然后更新视图的数据成员或控制来反应这些变化。另外,OnUpdate()函数可以使视图的一部分无效,导致视图的OnDraw()使用文档数据来在窗口中重画。4.CView::OnInitialUpdate()该函数也是一个虚函数,当应用程序启动时,或者用户执行菜单命令File-New或File-Open时,就会调用这个虚函数。如果要初始化视图对象,可在视图类的派生类中重载该函数,添加初始化代码。当应用程序启动时,先调用OnCreate()函数,接着就调用OnInitialUpdate()函数。在通常情况下,视图通过GetDocument()函数获得指向文档对象的指针,并通过该指针访问文档类的成员函数或数据成员获取数据。视图把数据显示于计算机屏幕上,用户通过与视图的交互来查看数据并对数据进行修改,然后视图通过相关联的文档类的成员函数将经过修改的数据传递给文档对象。文档对象获得修改过的数据之后,对其进行必要的修改,最后保存到永久介质(如磁盘文件)中。开发一个简单的文档/视图应用程序(不需要多个视图支持),只需要按下面的步骤操作:(1)在派生的文档类添加公共类型的数据成员,这些数据成员是基本数据存储器。(2)在派生的视图类中重载OnInitialUpdate()函数,更新视图,反映当前的文档的数据。在文档数据初始化之后或从磁盘上读入之后,应用程序框架会自动调用该函数。(3)在派生的视图类中,使用GetDocument()来访问文档对象,使窗口处理程序、命令消息处理程序和OnDraw()函数直接读取和更新文档的数据成员。程序运行时事件发生的次序如图8-3所示,假设工程名为My。图8-3简单文档/视图应用程序事件发生次序8.1.2文档模板类的功能在MFC中,文档类、与文档类相关联的视图类以及为视图类提供显示的框架窗口都是由文档模板应用程序启动构造CMyDocument对象构造CMyView对象创建View窗口调用CMyView::OnCreate(如映射)调用CMyDocument::OnNewDocument()调用CMyView::OnInitialUpdate()初始化View对象使View窗口无效调用CMyView::OnDraw()用户编辑数据CMyView中函数更新CMyDocument的数据成员退出应用程序删除CMyDocument对象删除CMyView对象VC++6简明教程4类创建的。每一种文档类型都有一种文档模板与之相对应,文档模板负责创建和管理该文档类型的所有文档。由AppWizard创建的SDI应用程序的5个基类之间的关系如图8-4所示。图8-4SDI应用程序的5个基类之间的关系模型打开SDI应用程序中应用程序类的InitInstance()成员函数,可以看到如程序清单8-1所示的一段代码,其作用是为程序定义一种文档模板类型。程序清单8-1:动态分配SDI文档模板对象BOOLCEXSDIApp::InitInstance(){。。。。。。//Registertheapplication'sdocumenttemplates.Documenttemplates//serveastheconnectionbetweendocuments,framewindowsandviews.CSingleDocTemplate*pDocTemplate;pDocTemplate=newCSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CEXSDIDoc),RUNTIME_CLASS(CMainFrame),//mainSDIframewindowRUNTIME_CLASS(CEXSDIView);AddDocTemplate(pDocTemplate);。。。。。。}这段代码首先创建了CSingleDocTemplate的一个对象实例,该对象是适用于单文档程序的文档模板,它的构造函数带有四个参数:第一个参数是一个资源ID,标识了用于给框架提供加速键表、菜单和图标的资源。这个ID应用程序使用的框架窗口对它使用的每种资源类型采用同一个资源ID,通常是IDR_MAINFRAME;构造函数的后三个参数都使用了RUNTIME_CLASS宏,RUNTIME_CLASS接受一个类名作为参数,返回指向一个CRuntimeClass结构的指针,从MSDN库中了解到,该结构可以用来在运行过程中获取一个对象的类及其基类的信息,并可以动态创建类的对象实例。CRuntimeClass结构实际上是RUNTIME_CLASS的参数中指定的类的一个静态成员。CSingleDocTemplate对象被AddDocTemplate()函数加入到应用程序对象内部的一个文档模板链表中,多数单文档程序都只支持一种文档类型,因此链表中一般只存有一个文档模板对象。写字板程序是一个能处理多种文档类型的单文档程序,在这种情况下链表中就不止一个文档模板对象了。创建创建创建创建应用程序对象CWinApp文档模板CSingleDocTemplate文档对象CDocument框架窗口CFrameWnd视图对象CView第6章创建和使用对话框5如果没有在命令行中指定要打开的文件名,应用程序类的ProcessShellCommand()函数就会自动生成一个新文档。在生成新文档时,应用程序类将检查文档模板链表,如果链表中只有一个文档模板对象,那么就直接调用该对象的OpenDocumentFile()函数,如果链表中有多个文档模板对象,那么它会弹出一个对话框,让用户选择一种类型,然后调用所选文档模板对象的OpenDocumentFile()函数,这个函数将负责创建文档对象和主框架对象。MDI应用程序的布局要比SDI应用程序的布局稍微复杂一些,图8-5展示了MDI应用程序结构的基本布局。图8-5MDI应用程序的5个基类之间的关系模型如图中所示,MDI应用程序仍使用一个主框架来容纳菜单、工具栏和状态栏。然而,在MDI应用程序中,CMainFrame类是从MFC的CMDIFrameWnd类的,而不是从CMainFrame类派生的。CMDIFrameWnd类有着与CMainFrame类同样的可视特征,但它还实现了Windows对MDI应用程序所要求的MDI框架协议。图中示例框架窗口是CMDIChildWnd的实例,提供了WindowsMDI应用程序客户区中的子窗口,用以存放视图,被包装的视图可以是任何类型,并且可以用于应用程序当前管理的任何打开的文档。同样打开一个MDI应用程序中应用程序类的InitInstance()函数,都可以找到类似程序清单8-2所示的源代码。与SDI应用程序有所区别的是MDI应用程序生成的一个CMultiDocTemplate类的文档模板对象实例,第一个参数资源ID为IDR_EXMDITYPE。程序清单8-2:动态分配MDI文档模板对象BOOLCEXMDIApp::InitInstance(){。。。。。。//Registertheapplication'sdocumenttemplates.Documenttemplates//serveastheconnectionbetweendocuments,framewindowsandviews.CMultiDocTemplate*pDocTemplate;pDoc