文档和视图
在文档/视图应用程序中,应用程序的数据由文档对象代表,而数据的视图由视图对象代表。MFC的CDocument类是文档对象的基类,CView类是视图对象的基类。应用程序的主窗口,其操作功能在MFC的CFrameWnd和CMDIFrameWnd类中实现,已经不再以消息处理为工作焦点了,而是主要用作了视图、工具栏以及其他用户界面对象的容器。
MFC支持两种类型的文档/视图应用程序。单文档界面(SDI)应用程序只支持打开一个文档。多文档界面(MDI)应用程序允许同时打开两个以上文档,还支持给定文档的多个视图。
框架窗口是应用程序的顶层窗口,通常是WS_OVERLAPPEDWINDOW样式的窗口,带有可缩放边框、标题栏、系统菜单以及最小化、最大化和关闭按钮。视图是子窗口,大小与框架窗口相适应,在实际中作为框架窗口的客户区。应用程序的数据保存在文档对象中,数据的可视表示保存在视图中。
对于SDI应用程序,框架窗口类是从CFrameWnd派生来的,文档类是从CDocument派生的,而试图类是从CView或相关类如CScrollView派生来的。
应用程序对象提供消息循环给框架窗口和视图提取消息。视图对象将鼠标和键盘输入转换为处理保存在文档中的数据的命令,文档对象提供了视图所需要的用来输出的数据。
文档/视图体系结构通过将数据封装在独立的文档对象中并为程序的屏幕输出提供视图对象,从而增强了模块化程序设计的方法。文档/视图应用程序从来不会为框架窗口获取客户区hPaintDC并在其中绘制输出,相反它绘制输出到视图中。看上去好像是在框架窗口中绘制,实际上所有输出都到了视图。如果愿意,可以给框架窗口绘制内容,但你是不会看到输出结果的,因为SDI框架窗口的客户区完全被视图遮盖了。
SDI程序的InitInstance函数
CSingleDocTemplate类创建了一个SDI文档模板。SDI文档模板时SDI文档/视图应用程序最主要的成分。它表示了用来管理应用程序数据的文档类、包含数据视图的框架窗口类,以及用来绘制可视数据表示的视图类。文档模板还保存了资源ID,主结构用它来加载菜单、加速键以及其他形成应用程序用户界面的资源。RUNTIME_CLASS宏对于所指定的类返回指向CRunTimeClass结构的指针,这就使得主结构可以在运行时创建这些类的对象了。文档模板创建以后,使用AddDocTemplate函数将它添加到由应用程序对象保存的文档模板列表中。用此方法注册的每个模板都定义了一个应用程序支持的文档类型。SDI应用程序只注册一个文档类型,而MDI应用程序可以注册并且有时也注册多个类型。
ProcessShellCommand调用CWinApp::OnFileNew来启动应用程序。如果文件名没有在命令行上输入就使用空文档,如果指定了文档名称,就使用CWinApp::OpenDocument来加载一个文档。在Windows中,只有窗口才可以接收消息,因此MFC使用了非常复杂的命令传递机制,就是将某种类型的消息按照预定的顺序从一个对象传递到另一个对象,直到处理消息的对象阻断或消息被传递给::DefWindowProc进行默认处理为止。
文档对象
在文档/视图体系结构中,数据被保存在文档对象中。文档对象是在主结构初始化从CDocument派生出的类时创建的。实际上,文档/视图中的文档几乎可以指任何东西,它是程序数据的抽象表示,在数据的保存和给用户提供数据之间划分了清晰地界限。利用视图提供的共用成员函数,来访问文档的数据。所有数据的处理都由文档对象自己完成。
文档数据通常保存在派生文档类的成员变量中,CDocument类中非虚函数,GetFirstViewPosition函数,功能,获取与文档关联的视图列表中第一个视图的位置。和GetNextView函数结合可以遍历文档的所有视图。GetPathName函数返回文档的文件名称和路劲。"C:\Documents\Personal\MyFile.doc"如果文档还未命名,则返回空字符串。GetTitle函数,返回文档的标题。"MyFile"如果文档还未命名,则返回空字符串。IsModified函数,如果文档包含未保存的数据就返回非零值,否则为零。SetModifiedFlag设置或清除文档中已修改的标志,该标志说明文档是否包含没有保存的数据。UpdateAllViews函数调用每个视图的OnUpdate函数来更新与文档关联的所有视图。每次修改文档数据之后都要调用SetModifiedFlag。次函数在文档对象内设置一个标志告诉MFC文档包含未保存的数据,并允许MFC在关闭文档之前提示用户该文档未保存的更改。你可以使用IsModified自己来确定文档是否被改过。UpdateAllViews命令所有与文档关联视图更新它们自己。实际上,是UpdateAllViews调用每个视图的OnUpdate函数,其默认操作是使视图无效实现重绘。在支持文档具有多个视图的应用程序中,每当文档数据被改变后调用UpdateAllViews可以维持不同视图的同步。即使是单视图应用程序也可以UpdateAllViews来刷新基于当前保存在文档中数据的视图。
CDocument可覆盖函数,OnNewDocument函数,在新文档被创建时由主结构调用。覆盖它是为了每次创建新文档时都对文档对象应用专门的初始化。OnOpenDocument函数,在从磁盘上装载文档时由主结构调用。覆盖它是为了每次装载新文档时都对文档对象应用专门的初始化。DeleteContents函数,主结构调用它来删除文档的内容。覆盖它是为了在文档关闭之前释放分配给文档的内存和其他资源。Serialize函数,主结构调用它在文档和磁盘之间串行化输出或输入。覆盖它是为了提供针对文档的串行化程序以便文档可以被装载和保存。
在SDI应用程序中,MFC只实例化文档对象一次(在应用程序启动时),却在文档文件被打开和关闭时反复使用该对象。由于文档对象只创建一次,因此文档类构造函数也只执行一次初始化。但是如果在新文档创建时或已存在的文档从磁盘上装载时,对派生文档类包含的成员变量进行重新初始化,就要使用OnNewDocument和OnOpenDocument。每次新文档创建时,MFC都文档的OnNewDocument函数。当文件从磁盘上装载时,MFC将调用OnOpenDocument。你可以在SDI文档类的构造函数中进行一次初始化。但是如果想随时在文档被创建或打开时进行某种初始化工作,就必须覆盖OnNewDocument或OnOpenDocument。一般来说,MFC应用程序更经常覆盖OnNewDocument而不是OnOpenDocument。因为OnOpenDocument函数间接调用了文档中的Serialize函数,它使用从文档文件中检索到的值初始化文档的永久数据成员。只有非永久数据成员(不用Serialize初始化的)才需要在OnOpenDocument中被初始化。与它相比,OnNewDocument不执行文档数据成员默认初始化。如果给文档类添加了数据成员并希望这些数据成员在新文档创建时再次被初始化,那么就需要覆盖OnNewDocument.在新文档被创建或打开之前,主结构调用文档对象的虚拟DeleteContents函数来删除文档中以存在的数据。因此,SDI应用程序可以覆盖DeleteContents并利用它释放分配给文档的任意资源,还可以执行其他必要的清理工作为重新使用文档对象做准备。MDI应用程序通常也是这样,但是MDI文档对象与SDI文档对象的不同之处在于,它们是在用户打开或关闭文档时各自创建和销毁的。OnCloseDocument函数,在文档关闭时调用。OnSaveDocument,在文档保存时调用。SaveModified,在保存未保存数据的文档被关闭之前调用,用来询问用户是否保存已做的改动。ReportSaveLoadException,串行化过程中有错误发生时调用。
视图对象
鉴于文档对象的唯一任务是管理应用程序的数据,因此视图对象就有两个用途:提供文档的可视化表示,以及将用户输入(特别是鼠标键盘消息)转换为操作文档数据的命令。一个文档可以具有与它联系的多个视图,而一个视图只能属于一个文档。主结构在视图的m_pDocument数据成员中保存了指向相关文档对象的指针,并将该指针提供给GetDocument成员函数使用。
可覆盖函数OnDraw,每次在视图接收到WM_PAINT消息时调用它。OnDraw函数被调用来绘制输出文档的数据。覆盖是为了绘制文档视图。OnInitialUpdate函数,视图第一次附加到文档时被调用。覆盖是为了每次在文档被创建或装载时都初始化视图对象。OnUpdate函数,在文档的数据已经修改或视图需要更新时调用。覆盖是为了实现有效更新功能,只重画视图中需要重画的部分而不重画整个视图。
在SDI应用程序中,视图和文档一样,只构造一次然后重复使用。在SDI中,每当文档被打开或创建时都要调用视图的OnInitialUpdate函数。OnInitialUpdate默认实现要调用OnUpdate,而OnUpdate的默认实现将使视图客户区无效并执行重绘。使用OnInitialUpdate函数来初始化视图类的数据成员,并在单文档基础上执行其他与视图相关的初始化。在文档数据被修改或其他对象(文档对象,视图对象)调用UpdateAllViews时将调用OnUpdate。根本没必要覆盖OnUpdate中默认状态下调用Invalidate的部分。但在实际中,确要经常覆盖OnUpdate来优化操作,只重绘视图中需要更新的部分而不是重绘整个视图。
在多视图应用程序中,任何时刻只有一个视图是活动视图,其他都是非活动视图。通常活动视图具有输入焦点。通过覆盖OnActiveView,视图可以确定何时激活何时无效。如果试图将被激活,第一个参数就是TRUE。CFrameWnd::GetActiveView和CFrameWnd::SetActiveView,框架窗口可以获取和设置活动视图。
框架窗口对象
框架窗口对象:它在屏幕上定义了应用程序的实际工作空间,同时也担当了视图的容器。SDI应用程序只有一个框架窗口CFrameWnd,它被用作应用程序的顶层窗口并用来包含视图。CFrameWnd了解如何使用工具栏、状态栏以及其他用户界面对象。
习惯上,File菜单中的New、Open和Exit命令由应用程序对象处理,其中CWinApp为它们提供了OnFileNew、OnFileOpen和OnAppExit命令处理程序。鼠标和键盘消息通常传给视图,大多数其他消息则传给框架窗口。