MFC文档视图结构学习笔记

文档/视图概述

 为了统一和简化数据处理方法,Microsoft公司在MFC中提出了文档/视图结构的概念,其产品Word就是典型的文档/视图结构应用程序

 MFC通过其文档类和视图类提供了大量有关数据处理的方法

 分为数据的管理和显示,文档用于管理和维护数据,视图用来显示和编辑数据

什么是文档

 文档的概念在MFC应用程序中的适用范围很广,一般说来,文档是能够被逻辑地组合的一系列数据,包括文本、图形、图象和表格数据。

 一个文档代表了用户存储或打开的一个文件单位。文档的主要作用是把对数据的处理从对用户界面的处理中分离出来,集中处理数据,同时提供了一个与其它类交互的接口。

什么是视图

 视图是文档在屏幕上的一个映像,它就像一个观景器,用户通过视图看到文档,也是通过视图来改变文档,视图充当了文档与用户之间的媒介物。

 应用程序通过视图向用户显示文档中的数据,并把用户的输入解释为对文档的操作。

 一个视图总是与一个文档对象相关联,用户通过与文档相关联的视图与文档进行交互。当用户打开一个文档时,应用程序就会创建一个与之相关联的视图。

视图和文档的功能

 视图负责显示和编辑文档数据,但不负责存储。用户对数据的编辑需要依靠窗口上的鼠标与键盘操作才得以完成,这些消息都是由视图类接收后进行处理或通知文档类,如收到窗口刷新消息时调用视图类的成员函数OnDraw()显示文档内容。

 视图还可在打印机上输出。

 文档负责数据的读写操作,数据通常被保存在文档类的成员变量中,文档类通过一个称为序列化的成员函数将成员变量的数据保存到磁盘文件中。MFC应用程序为数据的序列化提供了默认支持。

视图、文档和框架窗口的关系

 一个视图是一个没有边框的窗口,它位于主框架窗口中的客户区。视图是文档对外显示的窗口,但它并不能完全独立,它必须依存在一个框架窗口内。

 一个视图只能拥有一个文档,但一个文档可以同时拥有多个视图。

 视图是文档在屏幕上的一个映像,它就像一个观景器

文档/视图结构的优点

 把数据处理类从用户界面处理类中分离出来,使得每一个类都能集中地执行一项工作。

 Windows程序通常要做的工作分成若干定义好的类,这样有助于应用程序的模块化,程序也易于扩展,编程时只需修改所涉及的类。

 虽然文档/视图结构牵涉到许多类,其中的也关系比较复杂,但MFC AppWizard向导建立的MFC应用程序框架已经把程序的主要结构完成了,模块间的消息传递以及各函数的功能都已确定。

 MFC应用程序框架起到了穿针引线的作用,按照消息处理函数功能的不同,将不同消息的响应分别分布在文档类和视图类中。

在视图类中定义数据

 文档/视图结构并没有完全要求所有数据都属于文档类,视图类也可以有自己的数据。如果在视图类中不定义任何数据,在需要时都从文档类中获取,这样做会影响程序的效率。

 例如,在文本编辑程序中,往往在视图中缓存部分数据,这样可以避免对文档的频繁访问,提高运行效率。

文档与视图之间的相互作用

 包含多个类的MFC文档/视图结构应用程序要管理这些类中的数据,除了考虑在程序的哪一部分拥有数据和在哪一部分显示数据,一个主要的问题是文档数据更改后如何保持视图显示的同步,即文档与视图如何进行交互。

 在文档、视图和应用程序框架之间包含了一系列复杂的相互作用过程,文档与视图的交互是通过类的公有成员变量和成员函数实现的。

文档模板类及其功能

文档模板类(CDocTemplate)将原本独立的文档、视图和框架窗口对象组织在一起。文档模板的很多接口都是由CWinApp应用类调用以提供部分标准菜单命令的默认实现的。单文档模板只支持一种文档模板,多文档界面可以支持、定义多种文档模板支持不同的文档类型,或者仅一种文档模板在一个主框架窗口中创建多个文档实例的视图。文档模板通过CWinApp:: AddDocTemplate加入到应用中。CDocTemplate是从CCmdTarget类派生的,可以在其中处理部分菜单命令,但不能处理一般的窗口消息。

文档模板的概念

 在文档/视图结构中,数据以文档类对象的形式存在。文档对象通过视图对象显示,而视图对象又是主框架窗口的一个子窗口,并且涉及文档操作的菜单和工具栏等资源也是建立在主框架窗口上。这样,文档、视图、框架类和所涉及的资源形成了一种固定的联系,这种固定的联系就称为文档模板。也就是说,文档模板描述了相对应每一种类型文档的视图和窗口的风格类型。

 当打开某种类型的文件时,应用程序必须确定那一种文档模板用于解释这种文件。在初始化程序时,必须首先注册文档模板,以便程序利用这个模板来完成主框架窗口、视图、文档对象的创建和资源的装入

框架代码

BOOL CEx_MdiApp::InitInstance()

{        …

         CMultiDocTemplate*pDocTemplate;

         pDocTemplate= new CMultiDocTemplate(

         IDR_EX_MDITYPE,

         RUNTIME_CLASS(CEx_MdiDoc),

         RUNTIME_CLASS(CChildFrame),// MDI文档子窗口

         RUNTIME_CLASS(CEx_MdiView));

         AddDocTemplate(pDocTemplate);

         //创建MDI主框架窗口

         CMainFrame*pMainFrame = new CMainFrame;

         if(!pMainFrame->LoadFrame(IDR_MAINFRAME))

                   returnFALSE;

         m_pMainWnd= pMainFrame;

         …

}

CView类的成员函数

1.        一个视图对象只有一个与之相关联的文档对象。视图对象通过调用成员函数函数GetDocument()返回与视图相关联的文档对象的指针,利用这个指针可以访问文档类及其派生类的公有成员,函数原型:

a)        CDocument* GetDocument( ) const;

2.        派生类中的函数代码:

CMysdiDoc*  CMysdiView::GetDocument()  {  

ASSERT(m_pDocument->

IsKindOf(RUNTIME_CLASS(CMysdiDoc)));

return   (CMysdiDoc*)m_pDocument; 

// m_pDocument是CArchive类的数据成员,

// 指向当前文档对象

}

框架代码介绍

1.        CDocument::UpdateAllViews函数,函数的原型如下。

2.        void UpdateAllViews( CView*pSender, LPARAM lHint = 0L, CObject* pHint = NULL );

3.        CView::OnUpdate函数:应用程序调用了CDocument::UpdateAllViews函数时,应用程序框架就会相应地调用该函数。

4.        virtual void OnUpdate( CView*pSender, LPARAM lHint, CObject* pHint );

5.        CView::OnInitialUpdate函数:应用程序被启动时,或从“文件”菜单中选择了“新建”或“打开”时,CView虚函数都会被自动调用。该函数除了调用无提示参数(lHint = 0, pHint = NULL)的OnUpdate函数之外,没做其他任何事情。可以重载此函数对文档所需信息进行初始化操作。如果应用程序中的文档大小是动态的,那么就可在文档每次改变时调用OnUpdate来更新视图的滚动范围。

6.        CDocument::OnNewDocument函数:在SDI应用程序中,从“文件”菜单中选择“新建”命令时,框架将先构造一个文档对象,然后调用该虚函数。MFCAppWizard为用户的派生文档类自动产生了重载的OnNewDocument函数,如下面的代码:

BOOL CMyDoc::OnNewDocument(){   

if (!CDocument::OnNewDocument())      // 注意一定要保证对基类函数的调用,

return FALSE;  

// Do initialization of new documenthere.  

return TRUE;

}

CDocument类的成员函数

1.        一个文档对象可以有多个与之相关联的视图对象。当文档数据发生改变时,与它关联的每一个视图都必须反映出这些修改(重绘)。

2.        更新与该文档有关的所有视图的方法是调用成员函数CDocument::UpdateAllViews()

a)        函数原型:void UpdateAllViews(CView* pSender,  LPARAM  lHint = 0L, CObject*  pHint=NULL );

b)        参数:第一个参数pSender设为NULL,表示所有与当前文档相关的视图都要重绘;如果使用this指针,代表当前视图,例如:GetDocument()->UpdateAllViews(this)

刷新视图时函数调用过程

 当程序调用CDocument::UpdateAllViews()函数时,实际上是调用了所有相关视图的OnUpdate()函数,以更新相关的视图。

 函数调用过程如下:

         CDocument::UpdateAllViews()

         →CView::OnUpdate()

         →CWnd::Invalidate()// 使整个窗口矩形无效

         →OnPaint()

         →OnDraw()

多文档

1.        MFC基于文档/视图结构的应用程序分为单文档和多文档两种类型,一个多文档应用程序有一个主窗口,但在主窗口中可以同时打开多个子窗口,每一个子窗口对应一个不同的文档。

2.        利用MFC AppWizard[exe]向导可以很方便地建立一个多文档应用程序,只需在MFC AppWizard向导第1步选择Multiple documents程序类型。

3.        SDI和MDI使用不同框架窗口:

a)        SDI的框架窗口是唯一的主框架窗口,窗口类是CMainFrame,由CFrameWnd派生而来。

b)        MDI的框架窗口分为主框架窗口和子框架窗口,区别于SDI,MDI的主框架窗口不包含视图,分别由每个子框架窗口包含一个视图。MDI的主框架窗口类不与某个打开的文档相关联,而只与子框架窗口相关联。

c)        MDI主框架窗口类CMainFrame由CMDIFrameWnd派生而来,MDI子框架窗口类CChildFrame由CMDIChildWnd派生而来。

使用不同的视图

1.  MFC为应用程序提供了多种不同的视图,除了我们平常使用最多的一般视图CView,我们还可以使用其它视图,如滚动视图CScrollView、文本编辑视图CEditView、对话框视图CFormView、列表视图CListView和树型视图CTreeView等,这些视图都是从类CView派生而来。在利用应用程序向导创建一个文档/视图结构的应用程序时,在向导的第6步我们可以为应用程序选择不同的视图。

CFormView类2-1

 CFormView类是一个非常有用的视图类,它具有许多无模式对话框的特点。像CDiolog的派生类一样,CFormView的派生类也和相应的对话框资源相联系,它也支持对话框数据交换和对话框数据确认(DDX和DDV)。

 CFormView是所有表单视(如CRecordView、CDaoRecordView、CHtmlView等)的基类;一个基于表单的应用程序能让用户在程序中创建和使用一个或多个表单。

 创建表单应用程序的基本方法除了在创建SDI/MDI的第六步中选择CFormView作为应用程序视图类的基类外。还可通过相关菜单命令来自动插入一个表单,其步骤如下:

CFormView类2-2

 (1)切换到ClassView标签项,在项目名称上右击鼠标按钮。从弹出的快捷菜单中选择“New Form”命令,或者直接在主菜单中选择“Insert”à“New Form...”菜单命令,弹出如图7.10的“New Form”对话框。

 (2)在“New Form”对话框中,键入表单名称。如果想要表单支持“自动化”特性,则选择“Automation”单选框。在“Document Template Information”栏中,指定和表单并联的文档内容。如果想要更改文件扩展名或文档模板字串资源,则可按击[Change]按钮。

 (3)单击[OK]按钮,这样,一个表单视图派生类的程序框架就被添加到用户程序中;此时,我们就可用对话框编辑器为表单增加一些控件。

CEditView类

1.        CEditView类对象是一种视图,提供窗口编辑控制功能,可以执行简单文本操作。由于CEditView类自动封装上述功能的映射函数,因此只要在文档模板中使用CEditView类,那么应用程序的“编辑”菜单和“文件”菜单里的菜单项都可自动激活。

2.        CEditView仍然摆脱不了所有编辑控件的限制,如:

a)        CEditView不具有所见即所得编辑功能。

b)        CEditView只能将文本作单一字体的显示,不支持特殊格式的字符。

c)        CEditView可容纳的文本总数有限,在32位Windows中最多不超过1M字节。

3.     滚动视图类CScrollView

4.        在使用类CScrollView时,一般情况下,我们使用默认的滚动值,且不需要程序员自己处理滚动消息。编程时可使用CScrollView类的一些常用成员函数:

a)         SetScrollSizes():用于设置整个滚动视图的大小、每一页和每一行的大小;

b)         GetTotalSize():用于获取滚动视图的大小;

c)         GetScrollPosition():用于获取当前可见视图左上角的坐标。

其它视图类

 CRichEditView类:使用了复文本编辑控件,因此它支持混合字体格式和更大数据量的文本。CRichEditView类被设计成与CRichEditDoc和CRichEditCntrItem类一起使用,它们可实现一个完整的ActiveX包容器应用程序。

  CHtmlView 类:是在文档视图结构中提供WebBrowser控件的功能。WebBrowser控件可以浏览网址,也可以作为本地文件和网络文件系统的窗口,它支持超级链接、统一资源定位(URL)导航器并维护历史列表等。

应用程序对象指针的互调2-1

 从文档类中获取视图对象指针:在文档类中有一个与其关联的各视图对象的列表,并可通过CDocument类的成员函数GetFirstViewPosition和GetNextView来定位相应的视图对象。

 GetFirstViewPosition函数用来获得与文档类相关联的视图列表中第一个可见视图的位置,GetNextView函数用来获取指定视图位置的视图类指针,并将此视图位置移动下一个位置,若没有下一个视图,则视图位置为NULL。原型如下:

 virtual POSITION GetFirstViewPosition( ) const;

 virtual CView* GetNextView( POSITION& rPosition ) const;

 例如,使用CDocument::GetFirstViewPosition和GetNextView重绘每个视图

           void CMyDoc::OnRepaintAllViews()

         {        POSITION pos = GetFirstViewPosition();

                    while (pos != NULL)

                    {        CView* pView = GetNextView(pos);

                             pView->UpdateWindow();

                    }  

           }// 实现上述功能也可直接调用UpdateAllViews(NULL);

应用程序对象指针的互调2-2

 从视图类中获取文档对象和主框架对象指针:函数CWnd::GetParentFrame可实现从视图类中获取主框架指针,原型:CFrameWnd* GetParentFrame( ) const;

 在主框架类中获取视图对象指针:CView* CFrameWnd::GetActiveView( ) const;

 在框架类中可直接调用CFrameWnd::GetActiveDocument函数获得当前活动的文档对象指针。

 在同一个应用程序的任何对象中,可通过全局函数AfxGetApp()来获得指向应用程序对象的指针。

序列化

 涉及到数据处理的应用程序一般都要考虑文档数据的永久保存。虽然可利用类CFile来实现文件的读写操功能,但在MFC中序列化(Serialize)使得程序员可以不直接面对一个物理文件而进行文档的读写。序列化实现了文档数据的保存和装入的幕后工作,MFC通过序列化实现应用程序的文档读写功能

 序列化的基本思想:一个类应该能够对自己的成员变量的数据进行读写操作,对象可以通过读操作而重新创建。即对象可以将其当前状态(由其成员变量的值表示)写入永久性存储体(通常是指磁盘)中,以后可以从永久性存储体中读取(载入)对象的状态,从而重建对象。类的对象自己应该具备将状态值写入磁盘或从磁盘中读出的方法(即成员函数),这种对象的保存和恢复的过程称为序列化。

序列化函数Serialize()

 一个可序列化的类必须有一个称作为序列化的成员函数Serialize(),文档的序列化在文档类的成员函数Serialize()中进行。MFC AppWizard应用程序向导在生成应用程序时只创建了文档派生类序列化Serialize()函数的框架,由于不同程序的数据结构各不相同,可序列化的类应该重载Serialize()函数,使其支持对特定数据的序列化。并且,任何需要保存的变量(数据)都应该在文档派生类中声明

序列化函数Serialize()

1.        函数参数ar是一个CArchive类的对象,文档数据的序列化操作通过CArchive类对象作为中介来完成。CArchive类对象由应用程序框架创建,并与用户正在使用的文件关联在一起。CArchive类包含一个类CFile指针的成员变量,当创建一个CArchive类对象时,该对象与一个类CFile或其派生类的对象联系在一起,代表一个已打开的文件。

2.        C++主要通过文件句柄来实现磁盘输入和输出,一个文件句柄与一个磁盘文件相关联。而MFC中物理文件的读写操作是由CFile类及其派生类来完成的,它们对文件句柄进行了封装。CArchive类对象为读写CFile类对象中的可序列化数据提供了一种安全的缓冲机制,它们之间形成了如下关系:

a)        Serialize()函数¬®CArchive类对象¬®CFile类对象¬®磁盘文件

MFC类的序列化必须满足的条件

 类必须直接或间接地从CObject类派生而来,因为是利用CArchive类把用户的CObject类的派生类对象序列化;

 类必须定义一个不带参数的构造函数,当从磁盘文件载入文档时调用该构造函数来创建一个可序列化的对象,使用从文件中读出来的数据填充对象的成员变量;

 在类的头文件中使用DECLARE_SERIAL宏,在类的实现文件中使用IMPLEMENT_SERIAL宏;

 在自定义类中重载序列化成员函数Serialize()

文档类   

1.        向导为项目Mysdi生成了文档类的头文件MysdiDoc.h,该头文件用于定义文档类CMysdiDoc。CMysdiDoc类是MFC的CDocument类的派生类,它主要负责应用程序数据的保存和装载,实现文档的序列化功能

2.        文档类的成员函数

a)        AssertValid()

b)        Dump()

c)        OnNewDocument():当用户执行File菜单中New命令时,MFC应用程序框架会调用函数OnNewDocument()来完成新建文档的工作。

d)        Serialize():负责文档数据的磁盘读写操作。

拆分窗口

 静态切分:对“静态切分”窗口,窗口第一次被创建时,窗格就已经被切分好了,窗格的次序和数目不能再被改变,但可以移动切分条来调整窗格的大小。

 动态切分:对“动态切分”窗口,允许在任何时候对窗口进行切分,既可以通过选择菜单项来对窗口进行切分,也可以通过拖动滚动条中的切分框对窗口进行切分。动态切分窗口中的窗格通常使用的是同一个视图类。切分窗口被创建时,左上窗格通常被初始化成一个特殊的视图。当视图沿着某个方向被切分时,另一个新增加的视图对象被动态创建;当视图沿着两个方向被切分时,新增加的三个视图对象则被动态创建。取消切分时,所有新增加的视图对象被删除,但最先的视图仍被保留,直到切分窗口本身消失。

切分窗口的CSplitterWnd类

1.        成员函数Create用来创建“动态切分”,函数原型:

a)        BOOL Create( CWnd* pParentWnd,int nMaxRows, int nMaxCols, SIZE si*, CCreateContext* pContext, DWORDdwStyle = WS_CHILD | WS_VISIBLE |WS_HSCROLL | WS_VSCROLL | PLS_DYNAMIC_SPLIT,        UINT nID = AFX_IDW_PANE_FIRST );

2.        CreateStatic用来创建“静态切分”的文档窗口,函数原型:

a)        BOOL CreateStatic( CWnd*pParentWnd, int nRows, int nCols, DWORD dwStyle = WS_CHILD | WS_VISIBLE, UINTnID = AFX_IDW_PANE_FIRST );

切分窗口的应用实例2-1   

SDI文档窗口静态分成3 x 2个窗格:

(1)用MFC AppWizard创建一个单文档项目Ex_SplitSDI。

(2)打开框架窗口类MainFrm.h头文件,为CMainFrame类添加一个保护型的切分窗口的数据成员,如下面的定义:

         protected:  // control bar embedded members

                   CStatusBar  m_wndStatusBar;

                   CToolBar    m_wndToolBar;

                   CSplitterWndm_wndSplitter;

(3)用ClassWizard创建一个新的视图类CDemoView(基类为CView)用于与静态切分的窗格相关联。

(4)在类视图中双击CMainFrame类,添加视图类CDemoView的包含文件:#include"DemoView.h“

(5)为CMainFrame类添加OnCreateClient消息函数

切分窗口的应用实例2-2

BOOLCMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)

{       

         CRectrc;

         GetClientRect(rc);

         CSizepaneSize(rc.Width()/2-16,rc.Height()/3-16);

         m_wndSplitter.CreateStatic(this,3,2);

         m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CDemoView),paneSize,pContext);

         m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CDemoView),paneSize,pContext);

         m_wndSplitter.CreateView(1,0,RUNTIME_CLASS(CDemoView),paneSize,pContext);

         m_wndSplitter.CreateView(1,1,RUNTIME_CLASS(CDemoView),paneSize,pContext);

         m_wndSplitter.CreateView(2,0,RUNTIME_CLASS(CDemoView),paneSize,pContext);

         m_wndSplitter.CreateView(2,1,RUNTIME_CLASS(CDemoView),paneSize,pContext);

         returnTRUE;

}

一档多视

1.        MFC对于“一档多视”提供下列三个模式:

a)        (1)在各自MDI文档窗口中包含同一个视图类的多个视图对象。有时,想要应用程序能为同一个文档打开另一个文档窗口,以便同时使用两个文档窗口来查看文档的不同部分内容。用MFC AppWizard创建的MDI应用程序支持这种模式,选择“窗口”菜单的“新建窗口”命令时,系统就会为第一个文档窗口创建一个副本。

b)        (2)在同一个文档窗口中包含同一个视图类的多个视图对象。这种模式实际上是使用“切分窗口”机制使SDI应用程序具有多视的特征。

c)        (3)在单独一个文档窗口中包含不同视图类的多个视图对象。