1.2 MFC应用程序的文档/视图结构
1.2.1 文档/视图结构概述
MFC中的文档/视图结构(Document/View Architecture)是MFC的精髓,它的基本思想是实现模型数据与显示交互的相互分离。文档(Document)负责管理模型,视图(View)负责模型的显示与交互操作。文档/视图结构通过将模型和视图的分开表示,提供了很好的数据层次和表现层次的解耦。在具体实现上,MFC提供了两种典型且实用的基于文档与视图的应用程序框架模板,按照其应用程序生成向导的导引步骤(MFC AppWizard)就可以创建所需的基于文档/视图结构的MFC应用程序框架。在此框架的基础上,设计和插入相关的对象,就可以实现交互式的用户界面、几何模型的管理和操作、图形图像的显示,以及其他各种专业功能。
在MFC的文档/视图结构的应用程序框架中,文档类和视图类是成对出现的。文档用于管理应用程序的模型,而模型则封装了数据并提供对数据的操作接口;视图用于显示文档中的模型,并接收用户请求处理与用户的交互信息。模型是独立的;而视图依赖于模型,它从模型获取数据进行显示,并向模型发送用户请求,根据返回结果刷新视图显示。MFC通过文档类和视图类的划分,使数据的存储和显示既相对独立,又相互关联。
在MFC所提供的框架结构中,文档与视图的关系可以由图1-1简要表示。如图1-1所示,MFC中的视图和文档是由视图类(CView class)和文档类(CDocument class)分别表示的。视图类可以调用其本身的成员函数GetDocument(),获得一个指向文档类的指针,从而能够访问文档类中的数据。例如,在视图类的OnDraw() 函数中,视图类通过调用GetDocument() 函数获得一个指向文档类的指针。然后,通过这个指针获取文档类中的数据,并使用CDC类(负责处理应用程序显示设备接口的MFC类)中的函数将这些数据绘制在视图窗口中。视图可以通过图形、图像、文字、表格等多种方式以视图对象(CView object)来显示实际文档(文档对象CDocument object)中的数据。同时,视图对象也负责接收鼠标、键盘等用户输入信息,并通过这些与用户之间的交互信息来操作和修改文档中的数据。
图1-1 文档与视图的关系
1.2.2 文档与多个视图的关联
使用MFC AppWizard自动创建的MFC应用程序中,为每个文档类的对象创建并关联了一个视图类的对象。实际上,文档和视图的关系可以是一对一的,也可以是一对多的,即一个文档可以关联一个或一个以上的视图,也即可以用多个视图来显示和操作文档中的模型数据。通常,设计不同的视图是为了以不同的方式来显示文档内容,如图形、图表、结构、文字等。然而,当需要用多个视图显示同一个模型时,情况发生了变化:当一个视图修改了模型数据以后,不但本身要刷新显示,其他所有视图也要刷新显示。如果由该视图通知其他视图,它就需要能够找到其他所有视图。每个视图都可能发生修改,这样每个视图都要找到其他所有视图,这种关联过于复杂,不但难以维护,而且不便于增加新的视图。用MFC提供的文档与视图的关联模式,则可以很好地解决上述问题,从而实现由模型通知视图更新,而模型不依赖于具体的视图,而且不同视图之间保持相互独立。
如图1-1所示,一个文档对象就同时关联了多个视图对象。当在一个视图对象中对文档数据作了修改后,可以调用文档类的UpdateAllViews() 函数来更新所有与文档相关联的视图类对象的显示,以此来保持所有视图相对于同一文档数据变化的同步显示。一个文档关联多个视图为应用程序提供了很多的方便。例如,程序中可以使用两个视图,分别以表格和图形的方式来显示文档中的数据,图形显示用户直观的感觉,而用户可以通过表格来访问和操作文档中的具体数据,这种方式在CAD应用程序中较为常用。在CAD程序中,使用一个大的三维图形窗口显示几何模型的同时,通常还会使用一些其他的视图以不同的方式来显示文档数据,例如使用一个树型结构来显示模型的组成结构、属性信息、创建过程等。这样的多视图显示的例子可以参见图1-2,该图显示的是本书中使用的CAD示例软件——STLViewer的主界面。在图形用户界面(Graphic User Interface,GUI)中,左侧浮动窗口是一个树型结构的视图(CTreeView)类的对象,用于显示几何模型的结构与属性;右侧的视图窗口用于模型的OpenGL三维图形绘制和交互操作。这两个视图对象都与同一文档对象(存储几何模型的信息)相关联。在本书第9章还将进一步介绍单文档多视图关系的实现及有关内容。
图1-2 STLViewer的主界面(单文档多视图结构)
1.2.3 文档模板及主要组成类
MFC中,文档/视图的结构关系是由文档模板(Document Template)定义的。文档模板用于存放与应用程序文档、视图和框架窗口有关的信息。MFC类库提供两种文档模板类,即用于单文档(Single Document Interface,SDI)应用程序的CSingleDocTemplate和用于多文档(Multiple Document Interface,MDI)应用程序的CMultiDocTemplate。CSingleDocTemplate每次只能创建并管理一个文档,而CMultiDocTemplate可以创建并管理多个文档。图1-3和图1-4分别显示了基于MFC单文档和多文档的模板结构。
文档模板的创建和维护是在MFC的应用程序类CWinApp的对象中实现的。在类CWinApp调用成员函数InitInstance() 进行初始化时,必须为应用程序创建一个文档模板对象,并使用函数AddDocTemplate() 向新创建的应用程序加载这个模板。在应用程序对象中,也可以创建并加载多个文档模板。下面列出的代码是在应用程序类CSTLViewerApp(CWinApp的派生类)的成员函数InitInstance()中创建并加载一个单文档模板的例子。
图1-3 MFC的单文档的模板序结构
图1-4 MFC的多文档的模板结构
BOOL CSTLViewerApp::InitInstance() { …… CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(CSTLViewerDoc), RUNTIME_CLASS(CMainFrame), //main SDI frame window RUNTIME_CLASS(CSTLViewerView)); AddDocTemplate(pDocTemplate); …… }
由文档模板定义的MFC文档和视图程序框架包括四个主要的类:
● 文档类CSTLViewerDoc。
● 视图类CSTLViewerView。
● 主框架类CMainFrame。
● 应用程序类CSTLViewerApp。
应用程序的主要功能的实现代码分配在这四个主要的类中。MFC AppWizard为每个类自动生成了源文件。以此为基础,开发人员进一步在这些类中插入需要的代码和对象,就可以实现软件的具体功能。下面将对这四个类的主要功能分别加以介绍。
1.应用程序类
应用程序类由MFC提供的用程序基类CWinApp所派生,负责从总体上实现对程序的管理,管理从程序开始到结束的全过程,例如程序的初始化、运行,以及进行最后的程序清除工作。MFC的文档/视图结构、程序的主窗口都在应用程序类中定义和创建。基于MFC框架生成的Windows应用程序必须有且仅有一个从CWinApp派生的类的对象,在创建窗口之前先构造该对象。
2.文档类
文档类是由MFC提供的文档基类CDocument所派生,负责存放应用程序的数据,并管理程序和磁盘文档文件之间的存储与读取。在建立一个CAD系统时,几何模型的数据通常是文档中最主要的数据,应该存放在文档类中。文档类把数据处理从界面中分离出来,同时提供一个与其他对象交互的接口。
3.视图类
视图类是由MFC基类CView所派生的,负责显示文档类中的数据,显示的设备可以是计算机屏幕,也可以是打印机或其他设备。视图类还负责处理用户的交互输入,它可以处理多种类型的输入命令,如键盘输入、鼠标输入、菜单及工具栏命令等。在设计一个CAD系统时,屏幕上的图形绘制、打印机的绘图等功能都需要在视图类中开发完成。
不仅如此,视图类还可通过GetDocument()函数来获取文档指针,用之读取和操作文档中的数据,从而实现对文档数据的修改。如前提及,当文档中数据发生变化时,可通过调用CDocument::UpdateAllViews()函数来更新视图的显示内容。
特别值得说明的是,CView是个虚拟类,虚拟类中由于包含了纯虚函数(例如CView::OnDraw()就是一个纯虚函数),它本身不能直接用于声明对象,但在CView中封装了许多对视图进行操作的成员函数,可以被其派生类使用。
4.主框架类
主框架窗口也就是应用程序的主窗口,是由主框架类管理。如图1-2、图1-3所示,单文档应用程序和多文档应用程序的主框架类的MFC基类是不同的。对单文档应用程序(SDI),主框架窗口的基类为CFrameWnd类;而在多文档应用程序(MDI)的情况下,其框架窗口所继承的类为CMDIFrameWnd类,文档框架窗口所继承的类则为CMDIChildWnd类,每个文档都有一个文档框架窗口。在用户界面上,一个文档框架窗口至少含有一个视图以显示该文档数据。
主框架窗口还负责管理用户界面对象,如菜单、工具条、状态栏等。每个框架窗口对应管理一个可选的加速键表,改变加速键表的内容就可以自动转换键盘的加速键,这样可以方便地定义调用菜单命令的加速键。
主框架窗口同时管理所有的视图窗口,并跟踪当前活动的视图。当框架窗口含有多个视图窗口时,当前视图就是最近使用的视图。当视图活动改变时,边框窗口通过调用CView的成员函数OnActiveView()来通知当前视图。例如,在如图1-2所示的应用程序界面中,主框架窗口就同时管理了三个视图,分别是左边的树型视图、右边的OpenGL图形显示视图和下边的信息输出视图。