精华内容
下载资源
问答
  • MFC文档视图结构

    千次阅读 2018-04-01 15:19:23
    文档/视图概述为了统一和...文档用于管理和维护数据,视图用来显示和编辑数据什么文档文档的概念MFC应用程序中的适用范围很广,一般说来,文档是能够被逻辑地组合的一系列数据,包括文本、图形、图象和表格数据...

    文档/视图概述

    1. 为了统一和简化数据处理方法,Microsoft公司在MFC中提出了文档/视图结构的概念,其产品Word就是典型的文档/视图结构应用程序
    2. MFC通过其文档类和视图类提供了大量有关数据处理的方法
    3. 分为数据的管理和显示,文档用于管理和维护数据,视图用来显示和编辑数据

    什么是文档

    1. 文档的概念在MFC应用程序中的适用范围很广,一般说来,文档是能够被逻辑地组合的一系列数据,包括文本、图形、图象和表格数据。
    2. 一个文档代表了用户存储或打开的一个文件单位。文档的主要作用是把对数据的处理从对用户界面的处理中分离出来,集中处理数据,同时提供了一个与其它类交互的接口。

    什么是视图

    1. 视图是文档在屏幕上的一个映像,它就像一个观景器,用户通过视图看到文档,也是通过视图来改变文档,视图充当了文档与用户之间的媒介物。
    2. 应用程序通过视图向用户显示文档中的数据,并把用户的输入解释为对文档的操作。
    3. 一个视图总是与一个文档对象相关联,用户通过与文档相关联的视图与文档进行交互。当用户打开一个文档时,应用程序就会创建一个与之相关联的视图。

    视图和文档的功能

    1. 视图负责显示和编辑文档数据,但不负责存储。用户对数据的编辑需要依靠窗口上的鼠标与键盘操作才得以完成,这些消息都是由视图类接收后进行处理或通知文档类,如收到窗口刷新消息时调用视图类的成员函数OnDraw()显示文档内容。
    2. 视图还可在打印机上输出。
    3. 文档负责数据的读写操作,数据通常被保存在文档类的成员变量中,文档类通过一个称为序列化的成员函数将成员变量的数据保存到磁盘文件中。MFC应用程序为数据的序列化提供了默认支持。

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

    1. 一个视图是一个没有边框的窗口,它位于主框架窗口中的客户区。视图是文档对外显示的窗口,但它并不能完全独立,它必须依存在一个框架窗口内。
    2. 一个视图只能拥有一个文档,但一个文档可以同时拥有多个视图。
    3. 视图是文档在屏幕上的一个映像,它就像一个观景器

    文档/视图结构的优点

    1. 把数据处理类从用户界面处理类中分离出来,使得每一个类都能集中地执行一项工作。
    2. 把Windows程序通常要做的工作分成若干定义好的类,这样有助于应用程序的模块化,程序也易于扩展,编程时只需修改所涉及的类。
    3. 虽然文档/视图结构牵涉到许多类,其中的也关系比较复杂,但MFC AppWizard向导建立的MFC应用程序框架已经把程序的主要结构完成了,模块间的消息传递以及各函数的功能都已确定。
    4. MFC应用程序框架起到了穿针引线的作用,按照消息处理函数功能的不同,将不同消息的响应分别分布在文档类和视图类中。

    在视图类中定义数据

    1. 文档/视图结构并没有完全要求所有数据都属于文档类,视图类也可以有自己的数据。如果在视图类中不定义任何数据,在需要时都从文档类中获取,这样做会影响程序的效率。
    2. 例如,在文本编辑程序中,往往在视图中缓存部分数据,这样可以避免对文档的频繁访问,提高运行效率。

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

    1. 包含多个类的MFC文档/视图结构应用程序要管理这些类中的数据,除了考虑在程序的哪一部分拥有数据和在哪一部分显示数据,一个主要的问题是文档数据更改后如何保持视图显示的同步,即文档与视图如何进行交互。
    2. 在文档、视图和应用程序框架之间包含了一系列复杂的相互作用过程,文档与视图的交互是通过类的公有成员变量和成员函数实现的。

    文档模板类及其功能

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

    文档模板的概念

    1. 在文档/视图结构中,数据以文档类对象的形式存在。文档对象通过视图对象显示,而视图对象又是主框架窗口的一个子窗口,并且涉及文档操作的菜单和工具栏等资源也是建立在主框架窗口上。这样,文档、视图、框架类和所涉及的资源形成了一种固定的联系,这种固定的联系就称为文档模板。也就是说,文档模板描述了相对应每一种类型文档的视图和窗口的风格类型。
    2. 当打开某种类型的文件时,应用程序必须确定那一种文档模板用于解释这种文件。在初始化程序时,必须首先注册文档模板,以便程序利用这个模板来完成主框架窗口、视图、文档对象的创建和资源的装入

    框架代码

    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)

    刷新视图时函数调用过程

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

             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

    1. CFormView类是一个非常有用的视图类,它具有许多无模式对话框的特点。像CDiolog的派生类一样,CFormView的派生类也和相应的对话框资源相联系,它也支持对话框数据交换和对话框数据确认(DDX和DDV)。
    2. CFormView是所有表单视(如CRecordView、CDaoRecordView、CHtmlView等)的基类;一个基于表单的应用程序能让用户在程序中创建和使用一个或多个表单。
    3. 创建表单应用程序的基本方法除了在创建SDI/MDI的第六步中选择CFormView作为应用程序视图类的基类外。还可通过相关菜单命令来自动插入一个表单,其步骤如下:

    CFormView类2-2

    1. (1)切换到ClassView标签项,在项目名称上右击鼠标按钮。从弹出的快捷菜单中选择“New Form”命令,或者直接在主菜单中选择“Insert”à“New Form...”菜单命令,弹出如图7.10的“New Form”对话框。
    2. (2)在“New Form”对话框中,键入表单名称。如果想要表单支持“自动化”特性,则选择“Automation”单选框。在“Document Template Information”栏中,指定和表单并联的文档内容。如果想要更改文件扩展名或文档模板字串资源,则可按击[Change]按钮。
    3. (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():用于获取当前可见视图左上角的坐标。

    其它视图类

    1. CRichEditView类:使用了复文本编辑控件,因此它支持混合字体格式和更大数据量的文本。CRichEditView类被设计成与CRichEditDoc和CRichEditCntrItem类一起使用,它们可实现一个完整的ActiveX包容器应用程序。
    2.  CHtmlView 类:是在文档视图结构中提供WebBrowser控件的功能。WebBrowser控件可以浏览网址,也可以作为本地文件和网络文件系统的窗口,它支持超级链接、统一资源定位(URL)导航器并维护历史列表等。

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

    1. 从文档类中获取视图对象指针:在文档类中有一个与其关联的各视图对象的列表,并可通过CDocument类的成员函数GetFirstViewPosition和GetNextView来定位相应的视图对象。
    2. GetFirstViewPosition函数用来获得与文档类相关联的视图列表中第一个可见视图的位置,GetNextView函数用来获取指定视图位置的视图类指针,并将此视图位置移动下一个位置,若没有下一个视图,则视图位置为NULL。原型如下:
    3. virtual POSITION GetFirstViewPosition( ) const;
    4. virtual CView* GetNextView( POSITION& rPosition ) const;
    5. 例如,使用CDocument::GetFirstViewPosition和GetNextView重绘每个视图

               void CMyDoc::OnRepaintAllViews()

             {        POSITION pos = GetFirstViewPosition();

                        while (pos != NULL)

                        {        CView* pView = GetNextView(pos);

                                 pView->UpdateWindow();

                        }  

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

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

    1. 从视图类中获取文档对象和主框架对象指针:函数CWnd::GetParentFrame可实现从视图类中获取主框架指针,原型:CFrameWnd* GetParentFrame( ) const;
    2. 在主框架类中获取视图对象指针:CView* CFrameWnd::GetActiveView( ) const;
    3. 在框架类中可直接调用CFrameWnd::GetActiveDocument函数获得当前活动的文档对象指针。
    4. 在同一个应用程序的任何对象中,可通过全局函数AfxGetApp()来获得指向应用程序对象的指针。

    序列化

    1. 涉及到数据处理的应用程序一般都要考虑文档数据的永久保存。虽然可利用类CFile来实现文件的读写操功能,但在MFC中序列化(Serialize)使得程序员可以不直接面对一个物理文件而进行文档的读写。序列化实现了文档数据的保存和装入的幕后工作,MFC通过序列化实现应用程序的文档读写功能
    2. 序列化的基本思想:一个类应该能够对自己的成员变量的数据进行读写操作,对象可以通过读操作而重新创建。即对象可以将其当前状态(由其成员变量的值表示)写入永久性存储体(通常是指磁盘)中,以后可以从永久性存储体中读取(载入)对象的状态,从而重建对象。类的对象自己应该具备将状态值写入磁盘或从磁盘中读出的方法(即成员函数),这种对象的保存和恢复的过程称为序列化。

    序列化函数Serialize()

    1. 一个可序列化的类必须有一个称作为序列化的成员函数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类的序列化必须满足的条件

    1. 类必须直接或间接地从CObject类派生而来,因为是利用CArchive类把用户的CObject类的派生类对象序列化;
    2. 类必须定义一个不带参数的构造函数,当从磁盘文件载入文档时调用该构造函数来创建一个可序列化的对象,使用从文件中读出来的数据填充对象的成员变量;
    3. 在类的头文件中使用DECLARE_SERIAL宏,在类的实现文件中使用IMPLEMENT_SERIAL宏;
    4. 在自定义类中重载序列化成员函数Serialize()

    文档类   

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

    2.        文档类的成员函数

    a)        AssertValid()

    b)        Dump()

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

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

    拆分窗口

    1. 静态切分:对“静态切分”窗口,窗口第一次被创建时,窗格就已经被切分好了,窗格的次序和数目不能再被改变,但可以移动切分条来调整窗格的大小。
    2. 动态切分:对“动态切分”窗口,允许在任何时候对窗口进行切分,既可以通过选择菜单项来对窗口进行切分,也可以通过拖动滚动条中的切分框对窗口进行切分。动态切分窗口中的窗格通常使用的是同一个视图类。切分窗口被创建时,左上窗格通常被初始化成一个特殊的视图。当视图沿着某个方向被切分时,另一个新增加的视图对象被动态创建;当视图沿着两个方向被切分时,新增加的三个视图对象则被动态创建。取消切分时,所有新增加的视图对象被删除,但最先的视图仍被保留,直到切分窗口本身消失。

    切分窗口的CSplitterWnd类

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

    a)        BOOL Create( CWnd* pParentWnd,int nMaxRows, int nMaxCols, SIZE sizeMin, 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)在单独一个文档窗口中包含不同视图类的多个视图对象。

    展开全文
  • VC 文档+视图 详细分析

    万次阅读 2015-01-28 18:06:40
    深入浅出MFC文档视图架构之基本概念深入浅出MFC文档视图架构之文档模板深入浅出MFC文档视图架构之文档深入浅出MFC文档视图架构之视图深入浅出MFC文档视图架构之框架深入浅出MFC文档视图架构之相互关系深入浅出MFC...

                      深入浅出MFC文档/视图架构之基本概念


    引言

      MFC引入了"文档/视图"结构的概念,理解这个结构是编写基于MFC编写复杂Visual C++程序的关键。"文档/视图"中主要涉及到四种类:

      (1)文档模板:

    class CDocTemplate; // template for document creation
    class CSingleDocTemplate; // SDI support
    class CMultiDocTemplate; // MDI support

      (2)文档:

    class CDocument; // main document abstraction

      (3)视图:

    // views on a document
    class CView; // a view on a document
    class CScrollView; // a scrolling view

      (4)框架窗口:

    // frame windows
    class CFrameWnd; // standard SDI frame
    class CMDIFrameWnd; // standard MDI frame
    class CMDIChildWnd; // standard MDI child
    class CMiniFrameWnd; // half-height caption frame wnd

      理解了这4个类各自的意义及它们纵横交错的关系也就理解了"文档/视图"结构的基本概念,在此基础上,我们还需要进一步研究"文档/视图"结构的MFC程序消息流动的方向,这样就完全彻底明白了基于"文档/视图"结构MFC程序的"生死因果"。

      出于以上考虑,本文这样组织了各次连载的内容:

      第1次连载进行基本概念的介绍,第2~5次连载分别讲述文档模板、文档、视图和框架窗口四个类的功能和主要函数,连载6则综合阐述四个类之间的关系,接着以连载7讲解消息流动的方向,最后的连载8则以实例剖析连载1~7所讲述的所有内容。

      本文所有的代码基于WIN32平台开发,调试环境为Visual C++6.0。在本文的连载过程中,您可以通过如下方式联系作者(热忱欢迎读者朋友对本文的内容提出质疑或给出修改意见):

      作者email:21cnbao@21cn.com(可以来信提问,笔者将力求予以回信解答);

      另外,对本文的转载请务必注明作者和出处。未经同意,不得用于任何形式的商业目的。

      架构

      MFC"文档/视图"结构被认为是一种架构,关于什么是架构,这是个"仁者见仁,智者见智"的问题。在笔者看来,成其为架构者,必具备如下两个特性:

      (1)它是一种基础性平台,是一个模型。通过这个平台、这个模型,我们在上面进一步修饰,可以得到无穷无尽的新事物。譬如,建筑学上的钢筋混凝土结构、ISO(国际标准化组织)的OSI(开放式系统互连)七层模型。架构只是一种基础性平台,不同于用这个架构造出的实例。钢筋混凝土结构是架构,而用钢筋混凝土结构造出的房子就不能称为架构。

      这个特性强调了架构的外部特征,即架构具有可学习、可再生、可实例化的特点,是所有基于该架构所构造实例的共性,是贯串在它们体内的一根"筋",但各个基于该架构所构造的实例彼此是存在差异的。

      (2)它是一个由内部有联系的事物所组成的一个有机整体。架构中的内部成员不是彼此松散的,并非各自"占山为王",它们歃血为盟,紧密合作,彼此都有明确的责任和分工,因此共同构筑了一个统一的基础性平台、一个统一的模型。譬如,OSI模型从物理层到应用层进行了良好的合作,虽然内部包含了复杂的多个层次,但仍然脉络清晰。

      由此可见,架构的第2个特性是服务于第1个特性的。理解架构,关键是理解以上两个特性。而针对特定的"文档/视图"结构,则需理解如下两个问题:

      (1)学习这个架构,并学会在这个架构上造房子(编写基于"文档/视图"结构的程序);

      (2)理解这个架构内部的工作机理(文档模板、文档、视图和框架窗口四个类是如何联系为一个有机整体的),并在造房子时加以灵活应用(重载相关的类)。

      在这里,我们再引用几位专家(或企业)关于架构的定义以供读者进一步参考:

    The key ideas of a commercial application framework : a generic app on steroids that provides a large amount of general-purpose functionality within a well-planned, welltested, cohesive structure.
    (Application framework is) an extended collection of classes that cooperate to support a complete application architecture or application model, providing more complete application development support than a simple set of class libraries.
    ――MacApp(Apple's C++ application framework)
    An application framework is an integrated object-oriented software system that offers all the application-level classes(documents, views, and commands)needed by a generic application.
    An application framework is meant to be used in its entirety, and fosters both design reuse and code reuse. An application framework embodies a particular philosophy for structuring an application, and in return for a large mass of prebuilt functionality, the programmer gives up control over many architectural-design decisions.
    ――Ray Valdes

      什么是Application Framework?Framework 这个字眼有组织、框架、体制的意思,Application Framework 不仅是一般性的泛称,它其实还是对象导向领域中的一个专有名词。 

      基本上你可以说,Application Framework 是一个完整的程序模型,具备标准应用软件所需的一切基本功能,像是档案存取、打印预视、数据交换...,以及这些功能的使用接口(工具列、状态列、选单、对话盒)。如果更以术语来说,Application Framework 就是由一整组合作无间的"对象"架构起来的大模型。喔不不,当它还没有与你的程序产生火花的时候,它还只是有形无体,应该说是一组合作无间的"类别"架构起来的大模型。

      ――侯捷


      最后,要强调的是,笔者之所以用一个较长的篇幅来连载关于"文档/视图"结构的内容,是因为"文档/视图"结构是MFC中结构最为复杂,体系最为庞大,而又最富有特色的部分,其中涉及到应用、文档模板、文档、视图、SDI窗口、MDI框架窗口、MDI子窗口等多种不同的类,如果不了解这些类及其盘根错节的内部联系的话,就不可能编写出高水平的文档/视图程序。当然,学习"文档/视图"结构的意义还不只于其本身,通过该架构的学习,一步步领略MFC设计者的神功奥妙,也将进一步增强我们自身对庞大程序框架的把握能力。一个优秀的程序员是可以写出一个个优秀函数的程序员,而一个优秀的系统设计师则需从全局把握软件的架构,分析和学习"文档/视图"结构相信将是我们成为系统设计师之旅的一个有利环节。

    深入浅出MFC文档/视图架构之文档模板


      文档模板管理者类CDocManager

      在"文档/视图"架构的MFC程序中,提供了文档模板管理者类CDocManager,由它管理应用程序所包含的文档模板。我们先看看这个类的声明:

    /
    // CDocTemplate manager object 
    class CDocManager : public CObject
    {
     DECLARE_DYNAMIC(CDocManager)
     public:

      // Constructor
      CDocManager();

      //Document functions
      virtual void AddDocTemplate(CDocTemplate* pTemplate);
      virtual POSITION GetFirstDocTemplatePosition() const;
      virtual CDocTemplate* GetNextDocTemplate(POSITION& pos) const;
      virtual void RegisterShellFileTypes(BOOL bCompat);
      void UnregisterShellFileTypes();
      virtual CDocument* OpenDocumentFile(LPCTSTR lpszFileName); // open named file
      virtual BOOL SaveAllModified(); // save before exit
      virtual void CloseAllDocuments(BOOL bEndSession); // close documents before exiting
      virtual int GetOpenDocumentCount();

      // helper for standard commdlg dialogs
      virtual BOOL DoPromptFileName(CString& fileName, UINT nIDSTitle,
      DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate);

      //Commands
      // Advanced: process async DDE request
      virtual BOOL OnDDECommand(LPTSTR lpszCommand);
      virtual void OnFileNew();
      virtual void OnFileOpen();

      // Implementation
     protected:
      CPtrList m_templateList;
      int GetDocumentCount(); // helper to count number of total documents

     public:
      static CPtrList* pStaticList; // for static CDocTemplate objects
      static BOOL bStaticInit; // TRUE during static initialization
      static CDocManager* pStaticDocManager; // for static CDocTemplate objects

     public:
      virtual ~CDocManager();
      #ifdef _DEBUG
       virtual void AssertValid() const;
       virtual void Dump(CDumpContext& dc) const;
      #endif
    };

      从上述代码可以看出,CDocManager类维护一个CPtrList类型的链表m_templateList(即文档模板链表,实际上,MFC的设计者在MFC的实现中大量使用了链表这种数据结构),CPtrList类型定义为:

    class CPtrList : public CObject
    {
     DECLARE_DYNAMIC(CPtrList)

     protected:
      struct CNode
      {
       CNode* pNext; 
       CNode* pPrev;
       void* data;
      };
     public:

      // Construction
      CPtrList(int nBlockSize = 10);

      // Attributes (head and tail)
      // count of elements
      int GetCount() const;
      BOOL IsEmpty() const;

      // peek at head or tail
      void*& GetHead();
      void* GetHead() const;
      void*& GetTail();
      void* GetTail() const;

      // Operations
      // get head or tail (and remove it) - don't call on empty list!
      void* RemoveHead();
      void* RemoveTail();

      // add before head or after tail
      POSITION AddHead(void* newElement);
      POSITION AddTail(void* newElement);

      // add another list of elements before head or after tail
      void AddHead(CPtrList* pNewList);
      void AddTail(CPtrList* pNewList);

      // remove all elements
      void RemoveAll();

      // iteration
      POSITION GetHeadPosition() const;
      POSITION GetTailPosition() const;
      void*& GetNext(POSITION& rPosition); // return *Position++
      void* GetNext(POSITION& rPosition) const; // return *Position++
      void*& GetPrev(POSITION& rPosition); // return *Position--
      void* GetPrev(POSITION& rPosition) const; // return *Position--

      // getting/modifying an element at a given position
      void*& GetAt(POSITION position);
      void* GetAt(POSITION position) const;
      void SetAt(POSITION pos, void* newElement);

      void RemoveAt(POSITION position);

      // inserting before or after a given position
      POSITION InsertBefore(POSITION position, void* newElement);
      POSITION InsertAfter(POSITION position, void* newElement);

      // helper functions (note: O(n) speed)
      POSITION Find(void* searchValue, POSITION startAfter = NULL) const;
      // defaults to starting at the HEAD
      // return NULL if not found
      POSITION FindIndex(int nIndex) const;
      // get the 'nIndex'th element (may return NULL)

      // Implementation
     protected:
      CNode* m_pNodeHead;
      CNode* m_pNodeTail;
      int m_nCount;
      CNode* m_pNodeFree;
      struct CPlex* m_pBlocks;
      int m_nBlockSize;

      CNode* NewNode(CNode*, CNode*);
      void FreeNode(CNode*);

     public:
      ~CPtrList();
      #ifdef _DEBUG
       void Dump(CDumpContext&) const;
       void AssertValid() const;
      #endif
      // local typedefs for class templates
      typedef void* BASE_TYPE;
      typedef void* BASE_ARG_TYPE;
    };
    很显然,CPtrList是对链表结构体
    struct CNode
    {
     CNode* pNext; 
     CNode* pPrev;
     void* data;
    };

      本身及其GetNext、GetPrev、GetAt、SetAt、RemoveAt、InsertBefore、InsertAfter、Find、FindIndex等各种操作的封装。

      作为一个抽象的链表类型,CPtrList并未定义其中节点的具体类型,而以一个void指针(struct CNode 中的void* data)巧妙地实现了链表节点成员具体类型的"模板"化。很显然,在Visual C++6.0开发的年代,C++语言所具有的语法特征"模板"仍然没有得到广泛的应用。
    而CDocManager类的成员函数

    virtual void AddDocTemplate(CDocTemplate* pTemplate);
    virtual POSITION GetFirstDocTemplatePosition() const;
    virtual CDocTemplate* GetNextDocTemplate(POSITION& pos) const;

      则完成对m_TemplateList链表的添加及遍历操作的封装,我们来看看这三个函数的源代码:

    void CDocManager::AddDocTemplate(CDocTemplate* pTemplate)
    {
     if (pTemplate == NULL)
     {
      if (pStaticList != NULL)
      {
       POSITION pos = pStaticList->GetHeadPosition();
       while (pos != NULL)
       {
        CDocTemplate* pTemplate = (CDocTemplate*)pStaticList->GetNext(pos);
        AddDocTemplate(pTemplate);
       }
       delete pStaticList;
       pStaticList = NULL;
      }
      bStaticInit = FALSE;
     }
     else
     {
      ASSERT_VALID(pTemplate);
      ASSERT(m_templateList.Find(pTemplate, NULL) == NULL);// must not be in list
      pTemplate->LoadTemplate();
      m_templateList.AddTail(pTemplate);
     }
    }
    POSITION CDocManager::GetFirstDocTemplatePosition() const
    {
     return m_templateList.GetHeadPosition();
    }
    CDocTemplate* CDocManager::GetNextDocTemplate(POSITION& pos) const
    {
     return (CDocTemplate*)m_templateList.GetNext(pos);
    }




    2.文档模板类CDocTemplate

      文档模板类CDocTemplate是一个抽象基类(这意味着不能直接用它来定义对象而必须用它的派生类),它定义了文档模板的基本处理函数接口。对一个单文档界面程序,需使用单文档模板类CSingleDocTemplate,而对于一个多文档界面程序,需使用多文档模板类CMultipleDocTemplate。我们首先来看看CDocTemplate类的声明: 

    class CDocTemplate : public CCmdTarget
    {
     DECLARE_DYNAMIC(CDocTemplate)

     // Constructors
     protected:
      CDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass);

     public:
      virtual void LoadTemplate();

      // Attributes
     public:
      // setup for OLE containers
      void SetContainerInfo(UINT nIDOleInPlaceContainer);

      // setup for OLE servers
      void SetServerInfo(UINT nIDOleEmbedding, UINT nIDOleInPlaceServer = 0,
    CRuntimeClass* pOleFrameClass = NULL, CRuntimeClass* pOleViewClass = NULL);

      // iterating over open documents
      virtual POSITION GetFirstDocPosition() const = 0;
      virtual CDocument* GetNextDoc(POSITION& rPos) const = 0;

      // Operations
     public:
      virtual void AddDocument(CDocument* pDoc); // must override
      virtual void RemoveDocument(CDocument* pDoc); // must override
      enum DocStringIndex
      {
       windowTitle, // default window title
       docName, // user visible name for default document
       fileNewName, // user visible name for FileNew
       // for file based documents:
       filterName, // user visible name for FileOpen
       filterExt, // user visible extension for FileOpen
       // for file based documents with Shell open support:
       regFileTypeId, // REGEDIT visible registered file type identifier
       regFileTypeName, // Shell visible registered file type name
      };
      virtual BOOL GetDocString(CString& rString,
    enum DocStringIndex index) const; // get one of the info strings
      CFrameWnd* CreateOleFrame(CWnd* pParentWnd, CDocument* pDoc,BOOL bCreateView);

      // Overridables
     public:
      enum Confidence
      {
       noAttempt,
       maybeAttemptForeign,
       maybeAttemptNative,
       yesAttemptForeign,
       yesAttemptNative,
       yesAlreadyOpen
      };
      virtual Confidence MatchDocType(LPCTSTR lpszPathName,CDocument*& rpDocMatch);
      virtual CDocument* CreateNewDocument();
      virtual CFrameWnd* CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther);
      virtual void InitialUpdateFrame(CFrameWnd* pFrame, CDocument* pDoc,BOOL bMakeVisible = TRUE);
      virtual BOOL SaveAllModified(); // for all documents
      virtual void CloseAllDocuments(BOOL bEndSession);
      virtual CDocument* OpenDocumentFile(
       LPCTSTR lpszPathName, BOOL bMakeVisible = TRUE) = 0;
       // open named file
       // if lpszPathName == NULL => create new file with this type
      virtual void SetDefaultTitle(CDocument* pDocument) = 0;

      // Implementation
     public:
      BOOL m_bAutoDelete;
      virtual ~CDocTemplate();

      // back pointer to OLE or other server (NULL if none or disabled)
      CObject* m_pAttachedFactory;

      // menu & accelerator resources for in-place container
      HMENU m_hMenuInPlace;
      HACCEL m_hAccelInPlace;

      // menu & accelerator resource for server editing embedding
      HMENU m_hMenuEmbedding;
      HACCEL m_hAccelEmbedding;

      // menu & accelerator resource for server editing in-place
      HMENU m_hMenuInPlaceServer;
      HACCEL m_hAccelInPlaceServer;

      #ifdef _DEBUG
       virtual void Dump(CDumpContext&) const;
       virtual void AssertValid() const;
      #endif
      virtual void OnIdle(); // for all documents
      virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo);

     protected:
      UINT m_nIDResource; // IDR_ for frame/menu/accel as well
      UINT m_nIDServerResource; // IDR_ for OLE inplace frame/menu/accel
      UINT m_nIDEmbeddingResource; // IDR_ for OLE open frame/menu/accel
      UINT m_nIDContainerResource; // IDR_ for container frame/menu/accel

      CRuntimeClass* m_pDocClass; // class for creating new documents
      CRuntimeClass* m_pFrameClass; // class for creating new frames
      CRuntimeClass* m_pViewClass; // class for creating new views
      CRuntimeClass* m_pOleFrameClass; // class for creating in-place frame
      CRuntimeClass* m_pOleViewClass; // class for creating in-place view

      CString m_strDocStrings; // '\n' separated names
      // The document names sub-strings are represented as _one_ string:
      // windowTitle\ndocName\n ... (see DocStringIndex enum)
    };

      文档模板挂接了后面要介绍的文档、视图和框架窗口,使得它们得以互相关联。通过文档模板,程序确定了创建或打开一个文档时,以什么样的视图和框架窗口来显示。文档模板依靠保存相互对应的文档、视图和框架窗口的CRuntimeClass对象指针来实现上述挂接,这就是文档模板类中的成员变量m_pDocClass、m_pFrameClass、m_pViewClass的由来。实际上,对m_pDocClass、m_pFrameClass、m_pViewClass的赋值在CDocTemplate类的构造函数中实施:

    CDocTemplate::CDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass,
    CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass)
    {
     ASSERT_VALID_IDR(nIDResource);
     ASSERT(pDocClass == NULL || pDocClass->IsDerivedFrom(RUNTIME_CLASS(CDocument)));
     ASSERT(pFrameClass == NULL ||pFrameClass->IsDerivedFrom(RUNTIME_CLASS(CFrameWnd)));
     ASSERT(pViewClass == NULL || pViewClass->IsDerivedFrom(RUNTIME_CLASS(CView)));

     m_nIDResource = nIDResource;
     m_nIDServerResource = NULL;
     m_nIDEmbeddingResource = NULL;
     m_nIDContainerResource = NULL;

     m_pDocClass = pDocClass;
     m_pFrameClass = pFrameClass;
     m_pViewClass = pViewClass;
     m_pOleFrameClass = NULL;
     m_pOleViewClass = NULL;

     m_pAttachedFactory = NULL;
     m_hMenuInPlace = NULL;
     m_hAccelInPlace = NULL;
     m_hMenuEmbedding = NULL;
     m_hAccelEmbedding = NULL;
     m_hMenuInPlaceServer = NULL;
     m_hAccelInPlaceServer = NULL;

     // add to pStaticList if constructed as static instead of on heap
     if (CDocManager::bStaticInit)
     {
      m_bAutoDelete = FALSE;
      if (CDocManager::pStaticList == NULL)
       CDocManager::pStaticList = new CPtrList;
      if (CDocManager::pStaticDocManager == NULL)
       CDocManager::pStaticDocManager = new CDocManager;
       CDocManager::pStaticList->AddTail(this);
     }
     else
     {
      m_bAutoDelete = TRUE; // usually allocated on the heap
      LoadTemplate();
     }
    }

      文档模板类CDocTemplate还保存了它所支持的全部文档类的信息,包括所支持文档的文件扩展名、文档在框架窗口中的名字、图标等。
    CDocTemplate类的AddDocument、RemoveDocument成员函数使得CDocument* pDoc参数所指向的文档归属于本文档模板(通过将this指针赋值给pDoc所指向CDocument对象的m_pDocTemplate成员变量)或脱离与本文档模板的关系:

    void CDocTemplate::AddDocument(CDocument* pDoc)
    {
     ASSERT_VALID(pDoc);
     ASSERT(pDoc->m_pDocTemplate == NULL); // no template attached yet
     pDoc->m_pDocTemplate = this;
    }
    void CDocTemplate::RemoveDocument(CDocument* pDoc)
    {
     ASSERT_VALID(pDoc);
     ASSERT(pDoc->m_pDocTemplate == this); // must be attached to us
     pDoc->m_pDocTemplate = NULL;
    }

      而CDocTemplate类的CreateNewDocument成员函数则首先调用CDocument运行时类的CreateObject函数创建一个CDocument对象,再调用AddDocument成员函数将其归属于本文档模板类:

    CDocument* CDocTemplate::CreateNewDocument()
    {
     // default implementation constructs one from CRuntimeClass
     if (m_pDocClass == NULL)
     {
      TRACE0("Error: you must override CDocTemplate::CreateNewDocument.\n");
      ASSERT(FALSE);
      return NULL;
     }
     CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();
     if (pDocument == NULL)
     {
      TRACE1("Warning: Dynamic create of document type %hs failed.\n",m_pDocClass->m_lpszClassName);
      return NULL;
     }
     ASSERT_KINDOF(CDocument, pDocument);
     AddDocument(pDocument);
     return pDocument;
    }

      文档类对象由文档模板类构造生成,单文档模板类CSingleDocTemplate只能生成一个文档类对象,并用成员变量 m_pOnlyDoc 指向该对象;多文档模板类可以生成多个文档类对象,用成员变量 m_docList 指向文档对象组成的链表。

      CSingleDocTemplate的构造函数、AddDocument及RemoveDocument成员函数都在CDocTemplate类相应函数的基础上增加了对m_pOnlyDoc指针的处理:

    CSingleDocTemplate::CSingleDocTemplate(UINT nIDResource,
    CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass,
    CRuntimeClass* pViewClass)
    : CDocTemplate(nIDResource, pDocClass, pFrameClass, pViewClass)
    {
     m_pOnlyDoc = NULL;
    }
    void CSingleDocTemplate::AddDocument(CDocument* pDoc)
    {
     ASSERT(m_pOnlyDoc == NULL); // one at a time please
     ASSERT_VALID(pDoc);

     CDocTemplate::AddDocument(pDoc);
     m_pOnlyDoc = pDoc;
    }
    void CSingleDocTemplate::RemoveDocument(CDocument* pDoc)
    {
     ASSERT(m_pOnlyDoc == pDoc); // must be this one
     ASSERT_VALID(pDoc);

     CDocTemplate::RemoveDocument(pDoc);
     m_pOnlyDoc = NULL;
    }

      同样,CMultiDocTemplate类的相关函数也需要对m_docList所指向的链表进行操作(实际上AddDocument和RemoveDocument成员函数是文档模板管理其所包含文档的函数):

    // CMultiDocTemplate document management (a list of currently open documents)
    void CMultiDocTemplate::AddDocument(CDocument* pDoc)
    {
     ASSERT_VALID(pDoc);

     CDocTemplate::AddDocument(pDoc);
     ASSERT(m_docList.Find(pDoc, NULL) == NULL); // must not be in list
     m_docList.AddTail(pDoc);
    }
    void CMultiDocTemplate::RemoveDocument(CDocument* pDoc)
    {
     ASSERT_VALID(pDoc);

     CDocTemplate::RemoveDocument(pDoc);
     m_docList.RemoveAt(m_docList.Find(pDoc));
    }

      由于CMultiDocTemplate类可包含多个文档,依靠其成员函数GetFirstDocPosition和GetNextDoc完成对文档链表m_docList的遍历:

    POSITION CMultiDocTemplate::GetFirstDocPosition() const
    {
     return m_docList.GetHeadPosition();
    }
    CDocument* CMultiDocTemplate::GetNextDoc(POSITION& rPos) const
    {
     return (CDocument*)m_docList.GetNext(rPos);
    }

      而CSingleDocTemplate的这两个函数实际上并无太大的意义,仅仅是MFC要玩的某种"招数",这个"招数"高明吗?相信看完MFC的相关源代码后你或许不会这么认为,实际上CSingleDocTemplate的GetFirstDocPosition、GetNextDoc函数仅仅只能判断m_pOnlyDoc的是否为NULL:

    POSITION CSingleDocTemplate::GetFirstDocPosition() const
    {
     return (m_pOnlyDoc == NULL) ? NULL : BEFORE_START_POSITION;
    }

    CDocument* CSingleDocTemplate::GetNextDoc(POSITION& rPos) const
    {
     CDocument* pDoc = NULL;
     if (rPos == BEFORE_START_POSITION)
     {
      // first time through, return a real document
      ASSERT(m_pOnlyDoc != NULL);
      pDoc = m_pOnlyDoc;
     }
     rPos = NULL; // no more
     return pDoc;
    }

      笔者认为,MFC的设计者们将GetFirstDocPosition、GetNextDoc作为基类CDocTemplate的成员函数是不合理的,一种更好的做法是将GetFirstDocPosition、GetNextDoc移至CMultiDocTemplate派生类。

      CDocTemplate还需完成对其对应文档的关闭与保存操作:

    BOOL CDocTemplate::SaveAllModified()
    {
     POSITION pos = GetFirstDocPosition();
     while (pos != NULL)
     {
      CDocument* pDoc = GetNextDoc(pos);
      if (!pDoc->SaveModified())
       return FALSE;
     }
     return TRUE;
    }
    void CDocTemplate::CloseAllDocuments(BOOL)
    {
     POSITION pos = GetFirstDocPosition();
     while (pos != NULL)
     {
      CDocument* pDoc = GetNextDoc(pos);
      pDoc->OnCloseDocument();
     }
    }
    前文我们提到,由于MFC的设计者将CSingleDocTemplate和CMultiDocTemplate的行为未进行规范的区分,它对仅仅对应一个文档的CSingleDocTemplate也提供了所谓的GetFirstDocPosition、GetNextDoc遍历操作,所以基类CDocTemplate的SaveAllModified和CloseAllDocuments函数(都是遍历)就可统一CSingleDocTemplate和CMultiDocTemplate两个本身并不相同类的SaveAllModified和CloseAllDocuments行为(实际上,对于CSingleDocTemplate而言,SaveAllModified和CloseAllDocuments中的"All"是没有太大意义的。教室里有1个老师和N个同学,老师可以对同学们说"所有同学",而学生对老师说"所有老师"相信会被当成神经病)。MFC的设计者们特意使用了"将错就错"的方法意图简化CSingleDocTemplate和CMultiDocTemplate类的设计,读者朋友可以不认同他们的做法。

      CDocTemplate还提供了框架窗口的创建和初始化函数:

    /
    // Default frame creation
    CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther)
    {
     if (pDoc != NULL)
      ASSERT_VALID(pDoc);
      // create a frame wired to the specified document

     ASSERT(m_nIDResource != 0); // must have a resource ID to load from
     CCreateContext context;
     context.m_pCurrentFrame = pOther;
     context.m_pCurrentDoc = pDoc;
     context.m_pNewViewClass = m_pViewClass;
     context.m_pNewDocTemplate = this;

     if (m_pFrameClass == NULL)
     {
      TRACE0("Error: you must override CDocTemplate::CreateNewFrame.\n");
      ASSERT(FALSE);
      return NULL;
     }
     CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();
     if (pFrame == NULL)
     {
      TRACE1("Warning: Dynamic create of frame %hs failed.\n",m_pFrameClass->m_lpszClassName);
      return NULL;
     }
     ASSERT_KINDOF(CFrameWnd, pFrame);

     if (context.m_pNewViewClass == NULL)
      TRACE0("Warning: creating frame with no default view.\n");

     // create new from resource
     if (!pFrame->LoadFrame(m_nIDResource,WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles
    NULL, &context))
     {
      TRACE0("Warning: CDocTemplate couldn't create a frame.\n");
      // frame will be deleted in PostNcDestroy cleanup
      return NULL;
     }

     // it worked !
     return pFrame;
    }
    void CDocTemplate::InitialUpdateFrame(CFrameWnd* pFrame, CDocument* pDoc,BOOL bMakeVisible)
    {
     // just delagate to implementation in CFrameWnd
     pFrame->InitialUpdateFrame(pDoc, bMakeVisible);
    }

      3. CWinApp与CDocManager/CDocTemplate类

      应用程序CWinApp类对象与CDocManager和CDocTemplate类的关系是:CWinApp对象中包含一个CDocManager指针类型的共有数据成员m_pDocManager,CWinApp::InitInstance函数调用CWinApp::AddDocTemplate函数向链表m_templateList添加模板指针(实际上是调用前文所述CDocManager的AddDocTemplate函数)。另外,CWinApp也提供了GetFirstDocTemplatePosition和GetNextDocTemplate函数实现来对m_templateList链表进行访问(实际上也是调用了前文所述CDocManager的GetFirstDocTemplatePosition、GetNextDocTemplate函数)。我们仅摘取CWinApp类声明的一小部分:

    class CWinApp : public CWinThread
    {
     …
     CDocManager* m_pDocManager;

     // Running Operations - to be done on a running application
     // Dealing with document templates
     void AddDocTemplate(CDocTemplate* pTemplate);
     POSITION GetFirstDocTemplatePosition() const;
     CDocTemplate* GetNextDocTemplate(POSITION& pos) const;

     // Dealing with files
     virtual CDocument* OpenDocumentFile(LPCTSTR lpszFileName); // open named file
     void CloseAllDocuments(BOOL bEndSession); // close documents before exiting

     // Command Handlers
    protected:
     // map to the following for file new/open
     afx_msg void OnFileNew();
     afx_msg void OnFileOpen();
     int GetOpenDocumentCount();
     …
    };

      来看CWinApp派生类CSDIExampleApp(单文档)、CMDIExampleApp(多文档)的InitInstance成员函数的例子(仅仅摘取与文档模板相关的部分):

    BOOL CSDIExampleApp::InitInstance()
    {
     …
     CSingleDocTemplate* pDocTemplate;
     pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CSDIExampleDoc),
    RUNTIME_CLASS(CMainFrame), // main SDI frame window
    RUNTIME_CLASS(CSDIExampleView));
     AddDocTemplate(pDocTemplate);
     …
     return TRUE;
    }
    BOOL CMDIExampleApp::InitInstance()
    {
     …
     CMultiDocTemplate* pDocTemplate;
     pDocTemplate = new CMultiDocTemplate(IDR_MDIEXATYPE,
      RUNTIME_CLASS(CMDIExampleDoc),
      RUNTIME_CLASS(CChildFrame), // custom MDI child frame
      RUNTIME_CLASS(CMDIExampleView));
      AddDocTemplate(pDocTemplate);
     …
    }

      读者朋友,看完本次连载,也许您有许多不明白的地方,这是正常的。因为其所讲解的内容与后续几次连载息息相关,我们愈往后看,就会愈加清晰。对于本次连载的内容,您只需要建立基本的印象。最初的浅尝辄止是为了最终的深入脊髓!

      我们试图对MFC的深层机理刨根究底,"拨开云雾见月明"的过程是艰辛的!



    深入浅出MFC文档/视图架构之文档


      1、文档类CDocument

      在"文档/视图"架构的MFC程序中,文档是一个CDocument派生对象,它负责存储应用程序的数据,并把这些信息提供给应用程序的其余部分。CDocument类对文档的建立及归档提供支持并提供了应用程序用于控制其数据的接口,类CDocument的声明如下:

    /
    // class CDocument is the main document data abstraction
    class CDocument : public CCmdTarget
    {
     DECLARE_DYNAMIC(CDocument) 
    public:
     // Constructors
     CDocument();

     // Attributes
    public:
     const CString& GetTitle() const;
     virtual void SetTitle(LPCTSTR lpszTitle);
     const CString& GetPathName() const;
     virtual void SetPathName(LPCTSTR lpszPathName, BOOL bAddToMRU = TRUE);

     CDocTemplate* GetDocTemplate() const;
     virtual BOOL IsModified();
     virtual void SetModifiedFlag(BOOL bModified = TRUE);

     // Operations
     void AddView(CView* pView);
     void RemoveView(CView* pView);
     virtual POSITION GetFirstViewPosition() const;
     virtual CView* GetNextView(POSITION& rPosition) const;

     // Update Views (simple update - DAG only)
     void UpdateAllViews(CView* pSender, LPARAM lHint = 0L,
     CObject* pHint = NULL);

     // Overridables
     // Special notifications
     virtual void OnChangedViewList(); // after Add or Remove view
     virtual void DeleteContents(); // delete doc items etc

     // File helpers
     virtual BOOL OnNewDocument();
     virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
     virtual BOOL OnSaveDocument(LPCTSTR lpszPathName);
     virtual void OnCloseDocument();
     virtual void ReportSaveLoadException(LPCTSTR lpszPathName,
     CException* e, BOOL bSaving, UINT nIDPDefault);
     virtual CFile* GetFile(LPCTSTR lpszFileName, UINT nOpenFlags,
     CFileException* pError);
     virtual void ReleaseFile(CFile* pFile, BOOL bAbort);

     // advanced overridables, closing down frame/doc, etc.
     virtual BOOL CanCloseFrame(CFrameWnd* pFrame);
     virtual BOOL SaveModified(); // return TRUE if ok to continue
     virtual void PreCloseFrame(CFrameWnd* pFrame);

     // Implementation
    protected:
     // default implementation
     CString m_strTitle;
     CString m_strPathName;
     CDocTemplate* m_pDocTemplate;
     CPtrList m_viewList; // list of views
     BOOL m_bModified; // changed since last saved

    public:
     BOOL m_bAutoDelete; // TRUE => delete document when no more views
     BOOL m_bEmbedded; // TRUE => document is being created by OLE

     #ifdef _DEBUG
      virtual void Dump(CDumpContext&) const;
      virtual void AssertValid() const;
     #endif //_DEBUG
     virtual ~CDocument();

     // implementation helpers
     virtual BOOL DoSave(LPCTSTR lpszPathName, BOOL bReplace = TRUE);
     virtual BOOL DoFileSave();
     virtual void UpdateFrameCounts();
     void DisconnectViews();
     void SendInitialUpdate();

     // overridables for implementation
     virtual HMENU GetDefaultMenu(); // get menu depending on state
     virtual HACCEL GetDefaultAccelerator();
     virtual void OnIdle();
     virtual void OnFinalRelease();

     virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo);
     friend class CDocTemplate;

    protected:
     // file menu commands
     //{{AFX_MSG(CDocument)
      afx_msg void OnFileClose();
      afx_msg void OnFileSave();
      afx_msg void OnFileSaveAs();
     //}}AFX_MSG
     // mail enabling
     afx_msg void OnFileSendMail();
     afx_msg void OnUpdateFileSendMail(CCmdUI* pCmdUI);
     DECLARE_MESSAGE_MAP()
    };

      一个文档可以有多个视图,每一个文档都维护一个与之相关视图的链表(CptrList类型的 m_viewList实例)。CDocument::AddView将一个视图连接到文档上,并将视图的文档指针指向该文档:

    void CDocument::AddView(CView* pView)
    {
     ASSERT_VALID(pView);
     ASSERT(pView->m_pDocument == NULL); // must not be already attached
     ASSERT(m_viewList.Find(pView, NULL) == NULL); // must not be in list

     m_viewList.AddTail(pView);
     ASSERT(pView->m_pDocument == NULL); // must be un-attached
     pView->m_pDocument = this;

     OnChangedViewList(); // must be the last thing done to the document
    }

      CDocument::RemoveView则完成与CDocument::AddView相反的工作:

    void CDocument::RemoveView(CView* pView)
    {
     ASSERT_VALID(pView);
     ASSERT(pView->m_pDocument == this); // must be attached to us

     m_viewList.RemoveAt(m_viewList.Find(pView));
     pView->m_pDocument = NULL;

     OnChangedViewList(); // must be the last thing done to the document
    }

      从CDocument::AddView和CDocument::RemoveView函数可以看出,在与文档关联的视图被移走或新加入时CDocument::OnChangedViewList将被调用:

    void CDocument::OnChangedViewList()
    {
     // if no more views on the document, delete ourself
     // not called if directly closing the document or terminating the app
     if (m_viewList.IsEmpty() && m_bAutoDelete)
     {
      OnCloseDocument();
      return;
     }

     // update the frame counts as needed
     UpdateFrameCounts();
    }

      CDocument::DisconnectViews将所有的视图都与文档"失连":

    void CDocument::DisconnectViews()
    {
     while (!m_viewList.IsEmpty())
     {
      CView* pView = (CView*)m_viewList.RemoveHead();
      ASSERT_VALID(pView);
      ASSERT_KINDOF(CView, pView);
      pView->m_pDocument = NULL;
     }
    }

      实际上,类CDocument对视图的管理与类CDocManager对文档模板的管理及CDocTemplate对文档的管理非常类似,少不了的,类CDocument中可遍历对应的视图(出现GetFirstXXX和GetNextXXX两个函数):

    POSITION CDocument::GetFirstViewPosition() const
    {
     return m_viewList.GetHeadPosition();
    }

    CView* CDocument::GetNextView(POSITION& rPosition) const
    {
     ASSERT(rPosition != BEFORE_START_POSITION);
     // use CDocument::GetFirstViewPosition instead !
     if (rPosition == NULL)
      return NULL; // nothing left
     CView* pView = (CView*)m_viewList.GetNext(rPosition);
     ASSERT_KINDOF(CView, pView);
     return pView;
    }

      CDocument::GetFile和CDocument::ReleaseFile函数完成对参数lpszFileName指定文档的打开与关闭操作:

    CFile* CDocument::GetFile(LPCTSTR lpszFileName, UINT nOpenFlags,
    CFileException* pError)
    {
     CMirrorFile* pFile = new CMirrorFile;
     ASSERT(pFile != NULL);
     if (!pFile->Open(lpszFileName, nOpenFlags, pError))
     {
      delete pFile;
      pFile = NULL;
     }
     return pFile;
    }

    void CDocument::ReleaseFile(CFile* pFile, BOOL bAbort)
    {
     ASSERT_KINDOF(CFile, pFile);
     if (bAbort)
      pFile->Abort(); // will not throw an exception
     else
      pFile->Close();
     delete pFile;
    }

      CDocument类的OnNewDocument、OnOpenDocument、OnSaveDocument及OnCloseDocument这一组成员函数用于创建、打开、保存或关闭一个文档。在这一组函数中,上面的CDocument::GetFile和CDocument::ReleaseFile两个函数得以调用:

    BOOL CDocument::OnOpenDocument(LPCTSTR lpszPathName)
    {
     if (IsModified())
      TRACE0("Warning: OnOpenDocument replaces an unsaved document.\n");

     CFileException fe;
     CFile* pFile = GetFile(lpszPathName,
     CFile::modeRead|CFile::shareDenyWrite, &fe);
     if (pFile == NULL)
     {
      ReportSaveLoadException(lpszPathName, &fe,FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);
      return FALSE;
     }

     DeleteContents();
     SetModifiedFlag(); // dirty during de-serialize

     CArchive loadArchive(pFile, CArchive::load | CArchive::bNoFlushOnDelete);
     loadArchive.m_pDocument = this;
     loadArchive.m_bForceFlat = FALSE;
     TRY
     {
      CWaitCursor wait;
      if (pFile->GetLength() != 0)
       Serialize(loadArchive); // load me
       loadArchive.Close();
       ReleaseFile(pFile, FALSE);
     }
     CATCH_ALL(e)
     {
      ReleaseFile(pFile, TRUE);
      DeleteContents(); // remove failed contents

      TRY
      {
       ReportSaveLoadException(lpszPathName, e,FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);
      }
      END_TRY
      DELETE_EXCEPTION(e);
      return FALSE;
     }
     END_CATCH_ALL

     SetModifiedFlag(FALSE); // start off with unmodified

     return TRUE;
    }

    打开文档的函数CDocument::OnOpenDocument完成的工作包括如下几步:

      (1)打开文件对象;

      (2)调用DeleteDontents();

      (3)建立与此文件对象相关联的CArchive对象;

      (4)调用应用程序文档对象的Serialize()函数;

      (5)关闭CArchive对象、文件对象。

    BOOL CDocument::OnSaveDocument(LPCTSTR lpszPathName)
    {
     CFileException fe;
     CFile* pFile = NULL;
     pFile = GetFile(lpszPathName, CFile::modeCreate |CFile::modeReadWrite | CFile::shareExclusive, &fe);

     if (pFile == NULL)
     {
      ReportSaveLoadException(lpszPathName, &fe,TRUE, AFX_IDP_INVALID_FILENAME);
      return FALSE;
     }

     CArchive saveArchive(pFile, CArchive::store | CArchive::bNoFlushOnDelete);
     saveArchive.m_pDocument = this;
     saveArchive.m_bForceFlat = FALSE;
     TRY
     {
      CWaitCursor wait;
      Serialize(saveArchive); // save me
      saveArchive.Close();
      ReleaseFile(pFile, FALSE);
     }
     CATCH_ALL(e)
     {
      ReleaseFile(pFile, TRUE);

      TRY
      {
       ReportSaveLoadException(lpszPathName, e,TRUE, AFX_IDP_FAILED_TO_SAVE_DOC);
      }
      END_TRY
      DELETE_EXCEPTION(e);
      return FALSE;
     }
     END_CATCH_ALL
     
     SetModifiedFlag(FALSE); // back to unmodified
     return TRUE; // success
    }

      保存文档的函数CDocument::OnSaveDocument完成的工作包括如下几步:

      (1)创建或打开文件对象;

      (2)建立相对应的CArchive对象;

      (3)调用应用程序文档对象的序列化函数Serialize();

      (4)关闭文件对象、CArchive对象;

      (5)设置文件未修改标志。

    void CDocument::OnCloseDocument()
    // must close all views now (no prompting) - usually destroys this
    {
     // destroy all frames viewing this document
     // the last destroy may destroy us
     BOOL bAutoDelete = m_bAutoDelete;
     m_bAutoDelete = FALSE; // don't destroy document while closing views
     while (!m_viewList.IsEmpty())
     {
      // get frame attached to the view
      CView* pView = (CView*)m_viewList.GetHead();
      ASSERT_VALID(pView);
      CFrameWnd* pFrame = pView->GetParentFrame();
      ASSERT_VALID(pFrame);

      // and close it
      PreCloseFrame(pFrame);
      pFrame->DestroyWindow();
      // will destroy the view as well
     }
     m_bAutoDelete = bAutoDelete;

     // clean up contents of document before destroying the document itself
     DeleteContents();

     // delete the document if necessary
     if (m_bAutoDelete)
      delete this;
    }

      CDocument::OnCloseDocument函数的程序流程为:

      (1)通过文档对象所对应的视图,得到显示该文档视图的框架窗口的指针;

      (2)关闭并销毁这些框架窗口;

      (3)判断文档对象的自动删除变量m_bAutoDelete是否为真,如果为真,则以delete this语句销毁文档对象本身。

      实际上,真正实现文档存储和读取(相对于磁盘)的函数是Serialize,这个函数通常会被CDocument的派生类重载(加入必要的代码,用以保存对象的数据成员到CArchive对象以及从CArchive对象载入对象的数据成员状态):

    void CExampleDoc::Serialize(CArchive& ar)
    {
     if (ar.IsStoring())
     {
      // TODO: add storing code here
      ar << var1 << var2;
     }
     else
     {
      // TODO: add loading code here
      var2 >> var1 >> ar;
     }
    }

      地球人都知道,文档与视图进行通信的方式是调用文档类的UpdateAllViews函数:

    void CDocument::UpdateAllViews(CView* pSender, LPARAM lHint, CObject* pHint)
    // walk through all views
    {
     ASSERT(pSender == NULL || !m_viewList.IsEmpty());
     // must have views if sent by one of them

     POSITION pos = GetFirstViewPosition();
     while (pos != NULL)
     {
      CView* pView = GetNextView(pos);
      ASSERT_VALID(pView);
      if (pView != pSender)
       pView->OnUpdate(pSender, lHint, pHint);
     }
    }

      UpdateAllViews函数遍历视图列表,对每个视图都调用其OnUpdate函数实现视图的更新显示。

      2.文档的OPEN/NEW

      从连载2可以看出,在应用程序类CWinapp的声明中包含文件的New和Open函数:

    afx_msg void OnFileNew();
    afx_msg void OnFileOpen();

      而在文档模板管理者类CDocManager中也包含文件的New和Open函数:

    virtual void OnFileNew();
    virtual void OnFileOpen();
    virtual CDocument* OpenDocumentFile(LPCTSTR lpszFileName); // open named file

      而文档模板类CDocTemplate也不例外:

    virtual CDocument* OpenDocumentFile(
     LPCTSTR lpszPathName, BOOL bMakeVisible = TRUE) = 0;
     // open named file
     // if lpszPathName == NULL => create new file with this type
     virtual CDocument* CreateNewDocument();

      复杂的是,我们在CDocument类中再次看到了New和Open相关函数:

    virtual BOOL OnNewDocument();
    virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);

      在这众多的函数中,究竟文档的创建者和打开者是谁?"文档/视图"框架程序"File"菜单上的"New"和"Open"命令究竟对应着怎样的函数调用行为?这一切都使我们陷入迷惘!

      实际上"文档/视图"框架程序新文档及其关联视图和框架窗口的创建是应用程序对象、文档模板、新创建的文档和新创建的框架窗口相互合作的结果。具体而言,应用程序对象创建了文档模板;文档模板则创建了文档及框架窗口;框架窗口创建了视图。

      在用户按下ID_FILE_OPEN及ID_FILE_NEW菜单(或工具栏)命令后,CWinApp(派生)类的OnFileNew、OnFileOpen函数首先被执行,其进行的行为是选择合适的文档模板,如图3.1所示。


    图3.1文档模板的选择

      实际上,图3.1中所示的"使用文件扩展名选择文档模板"、"是一个文档模板吗?"的行为都要借助于CDocManager类的相关函数,因为只有CDocManager类才维护了文档模板的列表。CDocManager::OnFileNew的行为可描述为:

    void CDocManager::OnFileNew()
    {
     if (m_templateList.IsEmpty())
     {
      ...
      return ;
     }
     //取第一个文档模板的指针
     CDocTemplate *pTemplate = (CDocTemplate*)m_templateList.GetHead();
     if (m_templateList.GetCount() > 1)
     {
      // 如果多于一个文档模板,弹出对话框提示用户选择 
      CNewTypeDlg dlg(&m_templateList);
      int nID = dlg.DoModal();
      if (nID == IDOK)
       pTemplate = dlg.m_pSelectedTemplate;
      else
       return ;
      // none - cancel operation
     }
     …
     //参数为NULL的时候OpenDocument File会新建一个文件
     pTemplate->OpenDocumentFile(NULL);
    }

      之后,文档模板类的virtual CDocument* OpenDocumentFile(LPCTSTR lpszPathName, BOOL bMakeVisible = TRUE) = 0函数进行文档的创建工作,如果lpszPathName == NULL,是文档New行为;相反,则是Open行为。在创建框架后,文档模板根据是Open还是New行为分别调用CDocument的OnOpenDocument、OnNewDocument函数。图3.2描述了整个过程。


    图3.2文档、框架窗口的创建顺序

      而图3.3则给出了视图的创建过程。


    图3.3视图的创建顺序

      图3.1~3.3既描述了文档/视图框架对ID_FILE_OPEN及ID_FILE_NEW命令的响应过程,又描述了文档、框架窗口及视图的创建。的确,是无法单独描述文档的New和Open行为的,因为它和其他对象的创建交错纵横。

      相信,随着我们进一步阅读后续连载,会对上述过程有更清晰的认识。



    深入浅出MFC文档/视图架构之视图


      视图类CView

      在MFC"文档/视图"架构中,CView类是所有视图类的基类,它提供了用户自定义视图类的公共接口。在"文档/视图"架构中,文档负责管理和维护数据;而视图类则负责如下工作:

      (1) 从文档类中将文档中的数据取出后显示给用户;

      (2) 接受用户对文档中数据的编辑和修改;

      (3) 将修改的结果反馈给文档类,由文档类将修改后的内容保存到磁盘文件中。

      文档负责了数据真正在永久介质中的存储和读取工作,视图呈现只是将文档中的数据以某种形式向用户呈现,因此一个文档可对应多个视图。

      下面我们来看看CView类的声明:

    class CView : public CWnd
    {
     DECLARE_DYNAMIC(CView) 
     // Constructors
    protected:
     CView();

     // Attributes
    public:
     CDocument* GetDocument() const;

     // Operations
    public:
     // for standard printing setup (override OnPreparePrinting)
     BOOL DoPreparePrinting(CPrintInfo* pInfo);

     // Overridables
    public:
     virtual BOOL IsSelected(const CObject* pDocItem) const; // support for OLE

     // OLE scrolling support (used for drag/drop as well)
     virtual BOOL OnScroll(UINT nScrollCode, UINT nPos, BOOL bDoScroll = TRUE);
     virtual BOOL OnScrollBy(CSize sizeScroll, BOOL bDoScroll = TRUE);

     // OLE drag/drop support
     virtual DROPEFFECT OnDragEnter(COleDataObject* pDataObject,DWORD dwKeyState, CPoint point);
     virtual DROPEFFECT OnDragOver(COleDataObject* pDataObject,DWORD dwKeyState, CPoint point);
     virtual void OnDragLeave();
     virtual BOOL OnDrop(COleDataObject* pDataObject,DROPEFFECT dropEffect, CPoint point);
     virtual DROPEFFECT OnDropEx(COleDataObject* pDataObject,
     DROPEFFECT dropDefault, DROPEFFECT dropList, CPoint point);
     virtual DROPEFFECT OnDragScroll(DWORD dwKeyState, CPoint point);

     virtual void OnPrepareDC(CDC* pDC, CPrintInfo* pInfo = NULL);

     virtual void OnInitialUpdate(); // called first time after construct

    protected:
     // Activation
     virtual void OnActivateView(BOOL bActivate, CView* pActivateView,CView* pDeactiveView);
     virtual void OnActivateFrame(UINT nState, CFrameWnd* pFrameWnd);

     // General drawing/updating
     virtual void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint);
     virtual void OnDraw(CDC* pDC) = 0;

     // Printing support
     virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
     // must override to enable printing and print preview

     virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
     virtual void OnPrint(CDC* pDC, CPrintInfo* pInfo);
     virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

     // Advanced: end print preview mode, move to point
     virtual void OnEndPrintPreview(CDC* pDC, CPrintInfo* pInfo, POINT point,CPreviewView* pView);

     // Implementation
    public:
     virtual ~CView();
     #ifdef _DEBUG
      virtual void Dump(CDumpContext&) const;
      virtual void AssertValid() const;
     #endif //_DEBUG

     // Advanced: for implementing custom print preview
     BOOL DoPrintPreview(UINT nIDResource, CView* pPrintView,CRuntimeClass* pPreviewViewClass, CPrintPreviewState* pState);

     virtual void CalcWindowRect(LPRECT lpClientRect,UINT nAdjustType = adjustBorder);
     virtual CScrollBar* GetScrollBarCtrl(int nBar) const;
     static CSplitterWnd* PASCAL GetParentSplitter(const CWnd* pWnd, BOOL bAnyState);

    protected:
     CDocument* m_pDocument;

    public:
     virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo);
    protected:
     virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
     virtual void PostNcDestroy();

     // friend classes that call protected CView overridables
     friend class CDocument;
     friend class CDocTemplate;
     friend class CPreviewView;
     friend class CFrameWnd;
     friend class CMDIFrameWnd;
     friend class CMDIChildWnd;
     friend class CSplitterWnd;
     friend class COleServerDoc;
     friend class CDocObjectServer;

     //{{AFX_MSG(CView)
      afx_msg int OnCreate(LPCREATESTRUCT lpcs);
      afx_msg void OnDestroy();
      afx_msg void OnPaint();
      afx_msg int OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message);
      // commands
      afx_msg void OnUpdateSplitCmd(CCmdUI* pCmdUI);
      afx_msg BOOL OnSplitCmd(UINT nID);
      afx_msg void OnUpdateNextPaneMenu(CCmdUI* pCmdUI);
      afx_msg BOOL OnNextPaneCmd(UINT nID);

      // not mapped commands - must be mapped in derived class
      afx_msg void OnFilePrint();
      afx_msg void OnFilePrintPreview();
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
    };

      CView类首先要维护文档与视图之间的关联,它通过CDocument* m_pDocument保护性成员变量记录关联文档的指针,并提供CView::GetDocument接口函数以使得应用程序可得到与视图关联的文档。而在CView类的析构函数中,需将对应文档类视图列表中的本视图删除:

    CView::~CView()
    {
     if (m_pDocument != NULL)
      m_pDocument->RemoveView(this);
    }

      CView中地位最重要的函数是virtual void OnDraw(CDC* pDC) = 0;从这个函数的声明可以看出,CView是一个纯虚基类。这个函数必须被重载,它通常执行如下步骤:

      (1) 以GetDocument()函数获得视图对应文档的指针;

      (2) 读取对应文档中的数据;

      (3) 显示这些数据。

      以MFC向导建立的一个初始"文档/视图"架构工程将这样重载OnDraw()函数,注意注释中的"add draw code for native data here(添加活动数据的绘制代码)":

    /
    // CExampleView drawing
    void CExampleView::OnDraw(CDC* pDC)
    {
     CExampleDoc* pDoc = GetDocument();
     ASSERT_VALID(pDoc);
     // TODO: add draw code for native data here
    }
    CView::PreCreateWindow负责View的初始化:
    /
    // CView second phase construction - bind to document
    BOOL CView::PreCreateWindow(CREATESTRUCT & cs)
    {
     ASSERT(cs.style & WS_CHILD);

     if (cs.lpszClass == NULL)
     {
      VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
      cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background
     }

     if (afxData.bWin4 && (cs.style & WS_BORDER))
     {
      cs.dwExStyle |= WS_EX_CLIENTEDGE;
      cs.style &= ~WS_BORDER;
     }

     return TRUE;
    }

      CView::OnUpdate函数在文档的数据被改变的时候被调用(即它被用来通知一个视图的关联文档的内容已经被修改),它预示着我们需要重新绘制视图以显示变化后的数据。其中的Invalidate(TRUE)将整个窗口设置为需要重绘的无效区域,它会产生WM_PAINT消息,这样OnDraw将被调用:

    void CView::OnUpdate(CView* pSender, LPARAM /*lHint*/, CObject* /*pHint*/)
    {
     ASSERT(pSender != this);
     UNUSED(pSender); // unused in release builds

     // invalidate the entire pane, erase background too
     Invalidate(TRUE);
    }

      假如文档中的数据发生了变化,必须通知所有链接到该文档的视图,这时候文档类的UpdateAllViews函数需要被调用。

      此外,CView类包含一系列函数用于进行文档的打印及打印预览工作:

      (1)CView::OnBeginPrinting在打印工作开始时被调用,用来分配GDI资源;

      (2)CView::OnPreparePrinting函数在文档打印或者打印预览前被调用,可用来初始化打印对话框;

      (3)CView::OnPrint用来打印或打印预览文档;

      (4)CView::OnEndPrinting函数在打印工作结束时被调用,用以释放GDI资源;

      (5)CView::OnEndPrintPreview在退出打印预览模式时被调用。

      CView派生类

      MFC提供了丰富的CView派生类,各种不同的派生类实现了对不同种类控件的支持,以为用户提供多元化的显示界面。这些CView派生类包括:

      (1)CScrollView:提供滚动支持;

      (2)CCtrlView:支持tree、 list和rich edit控件;

      (3)CDaoRecordView:在dialog-box控件中显示数据库记录;

      (4)CEditView:提供了一个简单的多行文本编辑器;

      (5)CFormView:包含dialog-box控件,可滚动,基于对话框模板资源; 

      (6)CListView:支持list控件;

      (7)CRecordView:在dialog-box控件中显示数据库记录;

      (8)CRichEditView:支持rich edit控件;

      (9)CTreeView:支持tree控件。

      其中,CRichEditView、CTreeView及CListView均继承自CCtrlView类;CFormView继承自CScrollView类;CRecordView、CDaoRecordView则进一步继承自CFormView类。

      下图描述了CView类体系的继承关系:



    深入浅出MFC文档/视图架构之框架


      从前文可知,在MFC中,文档是真正的数据载体,视图是文档的显示界面,对应同一个文档,可能存在多个视图界面,我们需要另外一种东东来将这些界面管理起来,这个东东就是框架。

      MFC创造框架类的初衷在于:把界面管理工作独立出来!框架窗口为应用程序的用户界面提供结构框架,它是应用程序的主窗口,负责管理其包容的窗口。一个应用程序启动时会创建一个最顶层的框架窗口。

      MFC提供二种类型的框架窗口:单文档窗口SDI和多文档窗口MDI(你可以认为对话框是另一种框架窗口)。单文档窗口一次只能打开一个文档框架窗口,而多文档窗口应用程序中可以打开多个文档框架窗口,即子窗口(Child Window)。这些子窗口中的文档可以为同种类型,也可以为不同类型。

      在Visual C++ AppWizard的第一个对话框中,会让用户选择应用程序是基于单文档、多文档还是基于对话框的,如图5.1。


    图5.1 在AppWizard中选择框架窗口

      MFC提供了三个类CFrameWnd、CMDIFrameWnd、CMDIChildWnd用于支持单文档窗口和多文档窗口,这些类的层次结构如图5.2。


    图5.2 CFrameWnd、CMDIFrameWnd、CMDIChildWnd类的层次

      (1)CFrameWnd类用于SDI应用程序的框架窗口,SDI框架窗口既是应用程序的主框架窗口,也是当前文档对应的视图的边框;CFrameWnd类也作为CMDIFrameWnd和CMDIChildWnd类的父类,而在基于SDI的应用程序中,AppWizard会自动为我们添加一个继承自CFrameWnd类的CMainFrame类。

      CFrameWnd类中重要的函数有Create(用于创建窗口)、LoadFrame(用于从资源文件中创建窗口)、PreCreateWindow(用于注册窗口类)等。Create函数第一个参数为窗口注册类名,第二个参数为窗口标题,其余几个参数指定了窗口的风格、大小、父窗口、菜单名等,其源代码如下:

    BOOL CFrameWnd::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT &rect, CWnd *pParentWnd, LPCTSTR lpszMenuName, DWORD dwExStyle, CCreateContext *pContext)
    {
     HMENU hMenu = NULL;
     if (lpszMenuName != NULL)
     {
      // load in a menu that will get destroyed when window gets destroyed
      HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, RT_MENU);
      if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)
      {
       TRACE0("Warning: failed to load menu for CFrameWnd.\n");
       PostNcDestroy(); // perhaps delete the C++ object
       return FALSE;
      }
     } 
     m_strTitle = lpszWindowName; // save title for later

     if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle, rect.left,
    rect.top, rect.right - rect.left, rect.bottom - rect.top, pParentWnd
    ->GetSafeHwnd(), hMenu, (LPVOID)pContext))
     {
      TRACE0("Warning: failed to create CFrameWnd.\n");
      if (hMenu != NULL)
       DestroyMenu(hMenu);
      return FALSE;
     }
     return TRUE;
    }

      LoadFrame函数用于从资源文件中创建窗口,我们通常只需要给其指定一个参数,LoadFrame使用该参数从资源中获取主边框窗口的标题、图标、菜单、加速键等,其源代码为:

    BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd
    *pParentWnd, CCreateContext *pContext)
    {
     // only do this once
     ASSERT_VALID_IDR(nIDResource);
     ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);

     m_nIDHelp = nIDResource; // ID for help context (+HID_BASE_RESOURCE)

     CString strFullString;
     if (strFullString.LoadString(nIDResource))
      AfxExtractSubString(m_strTitle, strFullString, 0);
      // first sub-string

     VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));

     // attempt to create the window
     LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
     LPCTSTR lpszTitle = m_strTitle;
     if (!Create(lpszClass, lpszTitle, dwDefaultStyle, rectDefault, pParentWnd,
    MAKEINTRESOURCE(nIDResource), 0L, pContext))
     {
      return FALSE; // will self destruct on failure normally
     }

     // save the default menu handle
     ASSERT(m_hWnd != NULL);
     m_hMenuDefault = ::GetMenu(m_hWnd);

     // load accelerator resource
     LoadAccelTable(MAKEINTRESOURCE(nIDResource));

     if (pContext == NULL)
      // send initial update
      SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);

     return TRUE;
    }

      在SDI程序中,如果需要修改窗口的默认风格,程序员需要修改CMainFrame类的PreCreateWindow函数,因为AppWizard给我们生成的CMainFrame::PreCreateWindow仅对其基类的PreCreateWindow函数进行调用,CFrameWnd::PreCreateWindow的源代码如下:

    BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT &cs)
    {
     if (cs.lpszClass == NULL)
     {
      VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
      cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background
     }

     if ((cs.style &FWS_ADDTOTITLE) && afxData.bWin4)cs.style |= FWS_PREFIXTITLE;

     if (afxData.bWin4)
      cs.dwExStyle |= WS_EX_CLIENTEDGE;
     return TRUE;
    }

    (2)CMDIFrameWnd类用于MDI应用程序的主框架窗口,主框架窗口是所有MDI文档子窗口的容器,并与子窗口共享菜单;CMDIFrameWnd类相较CFrameWnd类增加的重要函数有:MDIActivate(激活另一个MDI子窗口)、MDIGetActive(得到目前的活动子窗口)、MDIMaximize(最大化一个子窗口)、MDINext(激活目前活动子窗口的下一子窗口并将当前活动子窗口排入所有子窗口末尾)、MDIRestore(还原MDI子窗口)、MDISetMenu(设置MDI子窗口对应的菜单)、MDITile(平铺子窗口)、MDICascade(重叠子窗口)。

      Visual C++开发环境是典型的MDI程序,其执行MDI Cascade的效果如图5.3。


    图5.3 MDI Cascade的效果

      而执行MDI Tile的效果则如图5.4。


    图5.4 MDI Tile的效果

      MDISetMenu函数的重要意义体现在一个MDI程序可以为不同类型的文档(与文档模板关联)显示不同的菜单,例如下面的这个函数Load一个菜单,并将目前主窗口的菜单替换为该菜单:

    void CMdiView::OnReplaceMenu() 
    {
     // Load a new menu resource named IDR_SHORT_MENU
     CMdiDoc* pdoc = GetDocument();
     pdoc->m_DefaultMenu = ::LoadMenu(AfxGetResourceHandle(), MAKEINTRESOURCE(IDR_SHORT_MENU));
     if (pdoc->m_DefaultMenu == NULL)
      return;

     // Get the parent window of this view window. The parent window is
     // a CMDIChildWnd-derived class. We can then obtain the MDI parent 
     // frame window using the CMDIChildWnd*. Then, replace the current 
     // menu bar with the new loaded menu resource.
     CMDIFrameWnd* frame = ((CMDIChildWnd *) GetParent())->GetMDIFrame();
     frame->MDISetMenu(CMenu::FromHandle(pdoc->m_DefaultMenu), NULL);
     frame->DrawMenuBar();
    }

      CMDIFrameWnd类另一个不讲"不足以服众"的函数是OnCreateClient,它是子框架窗口的创造者,其实现如下:

    BOOL CMDIFrameWnd::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext*)
    {
     CMenu* pMenu = NULL;
     if (m_hMenuDefault == NULL)
     {
      // default implementation for MFC V1 backward compatibility
      pMenu = GetMenu();
      ASSERT(pMenu != NULL);
      // This is attempting to guess which sub-menu is the Window menu.
      // The Windows user interface guidelines say that the right-most
      // menu on the menu bar should be Help and Window should be one
      // to the left of that.
      int iMenu = pMenu->GetMenuItemCount() - 2;

      // If this assertion fails, your menu bar does not follow the guidelines
      // so you will have to override this function and call CreateClient
      // appropriately or use the MFC V2 MDI functionality.
      ASSERT(iMenu >= 0);
      pMenu = pMenu->GetSubMenu(iMenu);
      ASSERT(pMenu != NULL);
     }
     return CreateClient(lpcs, pMenu);
    }

      从CMDIFrameWnd::OnCreateClient的源代码可以看出,其中真正起核心作用的是对函数CreateClient的调用:

    BOOL CMDIFrameWnd::CreateClient(LPCREATESTRUCT lpCreateStruct,
    CMenu* pWindowMenu)
    {
     ASSERT(m_hWnd != NULL);
     ASSERT(m_hWndMDIClient == NULL);
     DWORD dwStyle = WS_VISIBLE | WS_CHILD | WS_BORDER | WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
    MDIS_ALLCHILDSTYLES; // allow children to be created invisible
     DWORD dwExStyle = 0;
     // will be inset by the frame

     if (afxData.bWin4)
     {
      // special styles for 3d effect on Win4
      dwStyle &= ~WS_BORDER;
      dwExStyle = WS_EX_CLIENTEDGE;
     }

     CLIENTCREATESTRUCT ccs;
     ccs.hWindowMenu = pWindowMenu->GetSafeHmenu();
     // set hWindowMenu for MFC V1 backward compatibility
     // for MFC V2, window menu will be set in OnMDIActivate
     ccs.idFirstChild = AFX_IDM_FIRST_MDICHILD;

     if (lpCreateStruct->style & (WS_HSCROLL|WS_VSCROLL))
     {
      // parent MDIFrame's scroll styles move to the MDICLIENT
      dwStyle |= (lpCreateStruct->style & (WS_HSCROLL|WS_VSCROLL));

      // fast way to turn off the scrollbar bits (without a resize)
      ModifyStyle(WS_HSCROLL|WS_VSCROLL, 0, SWP_NOREDRAW|SWP_FRAMECHANGED);
     }

     // Create MDICLIENT control with special IDC
     if ((m_hWndMDIClient = ::CreateWindowEx(dwExStyle, _T("mdiclient"), NULL,
    dwStyle, 0, 0, 0, 0, m_hWnd, (HMENU)AFX_IDW_PANE_FIRST,
    AfxGetInstanceHandle(), (LPVOID)&ccs)) == NULL)
     {
      TRACE(_T("Warning: CMDIFrameWnd::OnCreateClient: failed to create MDICLIENT.")
    _T(" GetLastError returns 0x%8.8X\n"), ::GetLastError());
      return FALSE;
     }
     // Move it to the top of z-order
     ::BringWindowToTop(m_hWndMDIClient);

     return TRUE;
    }

      (3)CMDIChildWnd类用于在MDI主框架窗口中显示打开的文档。每个视图都有一个对应的子框架窗口,子框架窗口包含在主框架窗口中,并使用主框架窗口的菜单。

      CMDIChildWnd类的一个重要函数GetMDIFrame()返回目前MDI客户窗口的父窗口,其实现如下:

    CMDIFrameWnd *CMDIChildWnd::GetMDIFrame()
    {
     HWND hWndMDIClient = ::GetParent(m_hWnd);
     CMDIFrameWnd *pMDIFrame;
     pMDIFrame = (CMDIFrameWnd*)CWnd::FromHandle(::GetParent(hWndMDIClient));
     return pMDIFrame;
    }

      利用AppWizard生成的名为"example"的MDI工程包含如图5.5所示的类。


    图5.5 一个MDI工程包含的类

      其中的CMainFrame继承自CMDIFrameWnd,CChildFrame类继承自CMDIChildWnd类,CExampleView视图类则负责在CMDIChildWnd类对应的子框架窗口中显示文档的数据。 

      文中只是对CMDIFrameWnd的CreateClient成员函数进行了介绍,实际上,CFrameWnd、CMDIChildWnd均包含CreateClient成员函数。我们经常通过重载CFrameWnd:: CreateClient、CMDIChildWnd:: CreateClient函数的方法来实现"窗口分割",例如:

    BOOL CChildFrame::OnCreateClient(LPCREATESTRUCT lpcs, 
    CCreateContext *pContext)
    {
     …
     if (!m_wndSplitter.Create(this, 2, 2, // 分割的行、列数
       CSize(10, 10), // 最小化尺寸
       pContext))
     {
      TRACE0("创建分割失败");
      return FALSE; 
     }
     …
     return TRUE;
    }







    深入浅出MFC文档/视图架构之相互关系


      1、模板、文档、视图、框架的关系

      连载1~5我们各个击破地讲解了文档、文档模板、视图和框架类,连载1已经强调这些类有着亲密的内部联系,总结1~5我们可以概括其联系为:

      (1)文档保留该文档的视图列表和指向创建该文档的文档模板的指针;文档至少有一个相关联的视图,而视图只能与一个文档相关联。

      (2)视图保留指向其文档的指针,并被包含在其父框架窗口中;

      (3)文档框架窗口(即包含视图的MDI子窗口)保留指向其当前活动视图的指针;

      (4)文档模板保留其已打开文档的列表,维护框架窗口、文档及视图的映射;

      (5)应用程序保留其文档模板的列表。 

      我们可以通过一组函数让这些类之间相互可访问,表6-1给出这些函数。

      表6-1 文档、文档模板、视图和框架类的互相访问

    从该对象 如何访问其他对象
    全局函数 调用全局函数AfxGetApp可以得到CWinApp应用类指针
    应用 AfxGetApp()->m_pMainWnd为框架窗口指针;用CWinApp::GetFirstDocTemplatePostion、CWinApp::GetNextDocTemplate来遍历所有文档模板
    文档 调用CDocument::GetFirstViewPosition,CDocument::GetNextView来遍历所有和文档关联的视图;调用CDocument:: GetDocTemplate 获取文档模板指针
    文档模板 调用CDocTemplate::GetFirstDocPosition、CDocTemplate::GetNextDoc来遍历所有对应文档
    视图 调用CView::GetDocument 得到对应的文档指针; 调用CView::GetParentFrame 获取框架窗口
    文档框架窗口 调用CFrameWnd::GetActiveView 获取当前得到当前活动视图指针; 调用CFrameWnd::GetActiveDocument 获取附加到当前视图的文档指针
    MDI 框架窗口 调用CMDIFrameWnd::MDIGetActive 获取当前活动的MDI子窗口(CMDIChildWnd)

      我们列举一个例子,综合应用上表中的函数,写一段代码,它完成遍历文档模板、文档和视图的功能:

    CMyApp *pMyApp = (CMyApp*)AfxGetApp(); //得到应用程序指针
    POSITION p = pMyApp->GetFirstDocTemplatePosition();//得到第1个文档模板
    while (p != NULL) //遍历文档模板
    {
     CDocTemplate *pDocTemplate = pMyApp->GetNextDocTemplate(p);
     POSITION p1 = pDocTemplate->GetFirstDocPosition();//得到文档模板对应的第1个文档
     while (p1 != NULL) //遍历文档模板对应的文档
     {
      CDocument *pDocument = pDocTemplate->GetNextDoc(p1);
      POSITION p2 = pDocument->GetFirstViewPosition(); //得到文档对应的第1个视图
      while (p2 != NULL) //遍历文档对应的视图
      {
       CView *pView = pDocument->GetNextView(p2);
      }
     }
    }

      由此可见,下面的管理关系和实现途径都是完全类似的:

      (1)应用程序之于文档模板;

      (2)文档模板之于文档;

      (3)文档之于视图。

      图6.1、6.2分别给出了一个多文档/视图框架MFC程序的组成以及其中所包含类的层次关系。


    图6.1 多文档/视图框架MFC程序的组成


    图6.2 文档/视图框架程序类的层次关系

      关于文档和视图的关系,我们可进一步细分为三类:

      (1)文档对应多个相同的视图对象,每个视图对象在一个单独的 MDI 文档框架窗口中;

      (2)文档对应多个相同类的视图对象,但这些视图对象在同一文档框架窗口中(通过"拆分窗口"即将单个文档窗口的视图空间拆分成多个单独的文档视图实现); 

      (3)文档对应多个不同类的视图对象,这些视图对象仅在一个单独的 MDI 文档框架窗口中。在此模型中,由不同的类构造成的多个视图共享单个框架窗口,每个视图可提供查看同一文档的不同方式。例如,一个视图以字处理模式显示文档,而另一个视图则以"文档结构图"模式显示文档。

      图6.3显示了对应三种文档与视图关系应用程序的界面特点。


    图6.3文档/视图的三种关系

    2. 消息流动机制

      在基于"文档/视图"架构的MFC程序中,用户消息(鼠标、键盘输入等)会先发往视图,如果视图未处理则会发往框架窗口。所以,一般来说,消息映射宜定义在视图中。另外,如果一个应用同时拥有多个视图而当前活动视图没有对消息进行处理则消息也会发往框架窗口。

      下面我们来看实例,我们利用Visual C++向导创建一个单文档/视图架构的MFC程序,在其中增加一个菜单项为"自定义"(ID为IDM_SELF,如图6.4)。


    图6.4 含"自定义"菜单的单文档/视图架构MFC程序

      我们分别在视图类和框架窗口类中为"自定义"菜单添加消息映射,代码如下:

    //视图中的消息映射和处理函数
    BEGIN_MESSAGE_MAP(CExampleView, CView)
     //{{AFX_MSG_MAP(CExampleView)
      ON_COMMAND(IDM_SELF, OnSelf)
     //}}AFX_MSG_MAP
    END_MESSAGE_MAP() 
    void CExampleView::OnSelf() 
    {
     // TODO: Add your command handler code here
     AfxMessageBox("消息在视图中处理");
    }

    //框架窗口中的消息映射和处理函数
    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
     //{{AFX_MSG_MAP(CMainFrame)
      ON_COMMAND(IDM_SELF, OnSelf)
     //}}AFX_MSG_MAP
    END_MESSAGE_MAP()

    void CMainFrame::OnSelf() 
    {
     // TODO: Add your command handler code here
     AfxMessageBox("消息在框架窗口中处理");
    }

      这时候,我们单击"自定义"菜单,弹出对话框显示"消息在视图中处理";如果我们删除框架窗口中的消息映射,再单击"自定义"菜单,弹出对话框也显示"消息在视图中处理";但是,若我们将视图中的消息映射删除了,就会显示"消息在框架窗口中处理"!这验证了我们关于消息处理顺序论述的正确性。

      欲深入理解消息流动过程,还需认真分析CFrameWnd::OnCmdMsg、CView::OnCmdMsg函数的源代码:

    BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,
    AFX_CMDHANDLERINFO* pHandlerInfo)
    {
     // pump through current view FIRST
     CView* pView = GetActiveView();
     if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
      return TRUE;

     // then pump through frame
     if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
      return TRUE;

     // last but not least, pump through app
     CWinApp* pApp = AfxGetApp();
     if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
      return TRUE;

     return FALSE;
    }

    BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
    {
     // first pump through pane
     if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
      return TRUE;

     // then pump through document
     BOOL bHandled = FALSE;
     if (m_pDocument != NULL)
     {
      // special state for saving view before routing to document
      _AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
      CView* pOldRoutingView = pThreadState->m_pRoutingView;
      pThreadState->m_pRoutingView = this;
      bHandled = m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
      pThreadState->m_pRoutingView = pOldRoutingView;
     }

     return bHandled;
    }

      分析上述源代码可知,WM_COMMAND消息的实际流动顺序比前文叙述的"先视图,后框架窗口"要复杂得多,文档和应用程序都参与了消息的处理过程。如果我们再为文档和应用添加消息映射和处理函数:

    //文档的消息映射和处理函数
    BEGIN_MESSAGE_MAP(CExampleDoc, CDocument)
     //{{AFX_MSG_MAP(CExampleDoc)
      ON_COMMAND(IDM_SELF, OnSelf)
     //}}AFX_MSG_MAP
    END_MESSAGE_MAP()

    void CExampleDoc::OnSelf() 
    {
     // TODO: Add your command handler code here
     AfxMessageBox("消息在文档中处理");
    }

    //应用的消息映射和处理函数
    BEGIN_MESSAGE_MAP(CExampleApp, CWinApp)
    //{{AFX_MSG_MAP(CExampleApp) 
    ON_COMMAND(IDM_SELF, OnSelf)
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()

    void CExampleApp::OnSelf() 
    {
     // TODO: Add your command handler code here
     AfxMessageBox("消息在应用中处理");
    }

      屏蔽掉视图和框架窗口的消息映射,再单击"自定义"菜单,弹出对话框显示"消息在文档中处理";再屏蔽掉文档中的消息映射,弹出对话框显示"消息在应用中处理"!由此可见,完整的WM_COMMAND消息的处理顺序是"视图――文档――框架窗口――应用"!

      实际上,关于MFC的消息流动是一个很复杂的议题,陷于篇幅的原因,我们不可能对其进行更详尽的介绍,读者可自行寻找相关资料。


    深入浅出MFC文档/视图架构之实例剖析


      为了能够把我们所学的所有知识都在实例中得以完整的体现,我们来写一个尽可能复杂的"文档/视图"架构MFC程序,这个程序复杂到:

      (1)是一个多文档/视图架构MFC程序;

      (2)支持多种文件格式(假设支持扩展名为BMP的位图和TXT的文本文件);

      (3)一个文档(BMP格式)对应多个不同类型的视图(图形和二进制数据)。

      相信上述程序已经是一个包含"最复杂"特性的"文档/视图"架构MFC程序了,搞定了这个包罗万象的程序,还有什么简单的程序搞不定呢?

      用Visual C++工程向导创建一个名为"Example"的多文档/视图框架MFC程序,最初的应用程序界面如图7.1。


    图7.1 最初的Example工程界面

      这个时候的程序还不支持任何文档格式,我们需让它支持TXT(由于本文的目的是讲解框架而非具体的读写文档与显示,故将程序简化为只显示包含一行的TXT文件)和BMP文件。

      定义IDR_TEXTTYPE、IDR_BMPTYPE宏,并在资源文件中增加对应IDR_TEXTTYPE、IDR_BMPTYPE文档格式的字符串:

    //{{NO_DEPENDENCIES}}
    // Microsoft Visual C++ generated include file.
    // Used by EXAMPLE.RC
    //
    #define IDD_ABOUTBOX 100
    #define IDR_MAINFRAME 128
    //#define IDR_EXAMPLTYPE 129
    #define IDR_TEXTTYPE 10001 
    #define IDR_BMPTYPE 10002 

    #endif 
    STRINGTABLE PRELOAD DISCARDABLE 
    BEGIN
    IDR_MAINFRAME "Example"
    IDR_EXAMPLTYPE "\nExampl\nExampl\n\n\nExample.Document\nExampl Document"
    IDR_TEXTTYPE "\nTEXT\nTEXT\nExampl 文件 (*.txt)\n.txt\nTEXT\nTEXT Document"
    IDR_BMPTYPE "\nBMP\nBMP\nExampl 文件 (*.bmp)\n.bmp\nBMP\nBMP Document"
    END

      我们让第一个文档模板(由VC向导生成)对应TXT格式,修改CExampleApp::InitInstance函数:

    BOOL CExampleApp::InitInstance()
    {
     …
     CMultiDocTemplate* pDocTemplate;
     pDocTemplate = new CMultiDocTemplate(
      IDR_TEXTTYPE, //对应文本文件的字符串
      RUNTIME_CLASS(CExampleDoc),
      RUNTIME_CLASS(CChildFrame), // custom MDI child frame
      RUNTIME_CLASS(CExampleView));
     AddDocTemplate(pDocTemplate);
     …
    }

      为了让程序支持TXT文件的读取和显示,我们需要重载CexampleDoc文档类和CExampleView视图类。因为从文档模板new CMultiDocTemplate中的参数可以看出,CExampleDoc和CExampleView分别为对应TXT文件的文档类和视图类:

    class CExampleDoc : public CDocument
    {
     …
     CString m_Text; //在文档类中定义成员变量用于存储TXT文件中的字符串
     …
    }

    //重载文档类的Serialize,读取字符串到m_Text中
    void CExampleDoc::Serialize(CArchive& ar)
    {
     if (ar.IsStoring())
     {
      // TODO: add storing code here
     }
     else
     {
      // TODO: add loading code here
      ar.ReadString(m_Text); 
     }
    }
    //重载视图类的OnDraw函数,显示文档中的字符串
    /
    // CExampleView drawing
    void CExampleView::OnDraw(CDC* pDC)
    {
     CExampleDoc* pDoc = GetDocument();
     ASSERT_VALID(pDoc);
     // TODO: add draw code for native data here
     pDC->TextOut(0,0,pDoc->m_Text);
    }

      这个时候的程序已经支持TXT文件了,例如我们打开一个TXT文件,将出现如图7.2的界面。


    图7.2 打开TXT文件的界面

    由于CExampleDoc和CExampleView支持的是对应TXT文件的文档类和视图类,为了使程序支持BMP文件的显示,我们还需要为BMP信建文档类CBMPDoc和视图类CBMPView。

      在example.cpp中包含头文件:

    #include "BMPDocument.h"
    #include "BMPView.h"

      再在CExampleApp::InitInstance函数添加一个对应BMP格式的文档模板:

    pDocTemplate = new CMultiDocTemplate(
     //IDR_EXAMPLTYPE,
     IDR_BMPTYPE,
     RUNTIME_CLASS(CBMPDocument),
     RUNTIME_CLASS(CChildFrame), // custom MDI child frame
     RUNTIME_CLASS(CBMPView));
    AddDocTemplate(pDocTemplate);

      这个时候再点击程序的"新建"菜单,将弹出如图7.3的对话框让用户选择新建文件的具体类型,这就是在应用程序中包含多个文档模板后出现的现象。


    图7.3 包含多个文档模板后的"新建"

      这个时候再点击"打开"菜单,将弹出如图7.4的对话框让用户选择打开文件的具体类型,这也是在应用程序中包含多个文档模板后出现的现象。


    图7.4 包含多个文档模板后的"打开"

      对于新添加的视图类CBMPView,我们需要重载其GetDocument()函数:

    class CBMPView : public CView
    {
     …
     CBMPDocument* GetDocument(); //头文件中声明
     …
    }
    //重载CBMPView::GetDocument函数
    CBMPDocument* CBMPView::GetDocument()
    {
     ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CBMPDocument)));
     return (CBMPDocument*)m_pDocument;
    }

      而CBMPView::OnDraw则利用第三方类CDib来完成图形的绘制:

    void CBMPView::OnDraw(CDC* pDC)
    {
     CBMPDocument* pDoc = GetDocument();
     // TODO: add draw code here
     CDib dib; 
     dib.Load(pDoc->GetPathName());
     dib.SetPalette(pDC);
     dib.Draw(pDC);
    }

      我们打开李连杰主演电影《霍元甲》的剧照,将呈现如图7.5的界面,这证明程序已经支持位图文件了。


    图7.5 打开位图的界面

      其实,在这个程序中,我们已经可以同时打开位图和文本文件了(图7.6)。


    图7.6 同时打开位图和文本的界面

    它已经是一个相当复杂的程序,并已经具有如下两个特征:为多文档/视图架构;支持多种文件格式(扩展名为BMP、TXT)。

      而本节开头提出的第三个目标,即一个文档(BMP格式)对应多个不同类型的视图(图形和二进制数据)仍然没有实现。为了实现此目标,我们需要用到"拆分窗口"了。

      我们需要修改类CBMPDocument使之读取出位图中的二进制数据:

    class CBMPDocument : public CDocument
    {
     …
     public:
      unsigned char bmpBit[MAX_BITMAP];
    }

    void CBMPDocument::Serialize(CArchive& ar)
    {
     if (ar.IsStoring())
     {
      // TODO: add storing code here
     }
     else
     {
      // TODO: add loading code here
      CFile *file = ar.GetFile();
      for(int i=0;i<file->GetLength();i++)
      {
       ar >> bmpBit[i];
      }
     }
    }

      程序中现有的子框架窗口类(文档框架窗口类)CChildFrame并不支持窗口的拆分,我们不能再沿用这个类来处理BMP文件了,需要重新定义一个新的类CBMPChildFrame并通过重载其CBMPChildFrame::OnCreateClient函数来对其进行窗口拆分:

    class CBMPChildFrame : public CMDIChildWnd
    {
     …
     public:
      CSplitterWnd m_wndSplitter; //定义拆分
      …
    }

      重载CBMPChildFrame::OnCreateClient函数:

    BOOL CBMPChildFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext *pContext)
    {
     // TODO: Add your specialized code here and/or call the base class
     CRect rect;
     GetClientRect(&rect);
     m_wndSplitter.CreateStatic(this, 1, 2);
     m_wndSplitter.CreateView(0, 0, pContext->m_pNewViewClass, CSize(rect.right /2, 0), pContext);
     m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CBMPDataView), CSize(0, 0),pContext);
     m_wndSplitter.SetActivePane(0, 0);
     return true;
    }

      上述代码将文档框架窗口一分为二(分为一行二列),第二个视图使用了CBMPDataView类。CBMPDataView是我们新定义的一个视图类,用来以16进制数字方式显示位图中的数据信息,我们也需要为其重新定义GetDocument函数,与CBMPDocument类中的定义完全相同。

      为了支持以二进制方式显示位图,我们需要重载CBMPDataView类的OnDraw函数。这里也简化了,仅仅显示10行20列数据(前文已经提到,我们的目的是讲解框架而非显示和读取文档的细节),而且代码也不是很规范(在程序中出现莫名其妙的数字一向是被鄙视的程序风格):

    void CBMPDataView::OnDraw(CDC* pDC)
    {
     CBMPDocument* pDoc = GetDocument();
     // TODO: add draw code here
     CString str;
     char tmp[3];
     for(int i=0;i<20;i++)//假设只显示20行,每行20个字符
     {
      str = "";
      for (int j =0;j<20;j++) 
      {
       memset(tmp,0,4); 
       itoa(pDoc->bmpBit[10*i+j],tmp,16);
       str+=CString(tmp)+" ";
      }
      pDC->TextOut(0,20*i,str);
     } 
    }

      好的,大功告成!这个程序很牛了,打开位图看看,界面如图7.7。打开位图后再打开文本,界面如图7.8,成为一个"多视图+多文档"的界面。

      就这样,我们逐步让这个实例程序具备了最复杂MFC程序的特征!

      单击此处下载本实例源代码

      本系列文章的连载到此结束,最后赠送广大研发人员一句话:无尽地学习,乃是IT人的宿命,付出努力,终有回报!


    图7.7 用两种视图来显示位图的界面

    图7.8 "多视图+多文档"的界面

    展开全文
  • MFC单文档切换视图

    千次阅读 2017-06-28 15:30:00
    一些情况,应用程序有许多信息要显示,当一个窗口内显示不下时,VC提供的MDI应用程序可以解决这个问题,但是如果应用程序只是对单个文档,采用多种方式显示,则采用SDI更合适。那么,如何文档应用程序中实现...
    在一些情况下,应用程序有许多信息要显示,当一个窗口内显示不下时,VC提供的MDI应用程序可以解决这个问题,但是如果应用程序只是对单个文档,采用多种方式显示,则采用SDI更合适。那么,如何在单文档应用程序中实现多视图呢?缺省情况下,派生类的构造函数是pretected类型的,必须将它改为public类型,因为我们将会动态生成派生视图类并切换活动的视图。也可以一次生成所有的视图,但是这样造成系统资源的极大浪费。

    1  首先利用资源编辑器新建4个对话框,在对话框中添加需要的控件,各个对话框的ID分别为IDD_MULTIVIEW_FORM,IDD_MULTIVIEW_FORM2,IDD_MULTIVIEW_FORM3,IDD_MULTIVIEW_FORM4

    2  为每一个对话框创建一个CFormView类的派生类,分别为:CView1,CView2,CView3,CView4

    3  在应用程序的初始化文件中用CView1替换原先的视图,这样程序启动后的缺省显示视图是CView1,修改后的部分代码如下:
    CSingleDocTemplate* pDocTemplate;
    pDocTemplate = new CSingleDocTemplate(
    IDR_MAINFRAME,
    RUNTIME_CLASS(CMultiViewDoc),
    RUNTIME_CLASS(CMainFrame),       // main SDI frame window
    RUNTIME_CLASS(CView1));
    AddDocTemplate(pDocTemplate);

    4  利用资源编辑器,添加几个菜单,并添加相应的消息处理函数:
    void CMainFrame::OnViewView1() 
    {
    SwitchToForm(IDD_MULTIVIEW_FORM);   
    }

    void CMainFrame::OnViewView2() 
    {
    SwitchToForm(IDD_MULTIVIEW_FORM2);   
    }

    void CMainFrame::OnViewView3() 
    {
    SwitchToForm(IDD_MULTIVIEW_FORM3);   
    }

    void CMainFrame::OnViewView4() 
    {
    SwitchToForm(IDD_MULTIVIEW_FORM4);   
    }

    在SDI中CFrame的派生类即CMainFrame控制着所有的视图,而且它允许用户通过消息处理进行视图的切换。进行视图切换的原理很简单,只是断开和旧视图的连接并把他删除,创建一个新视图,将文档和它相连,设置几个标志,然后显示新视图,SwitchToForm函数是进行视图切换的代码函数。

    void CMainFrame::SwitchToForm(int nForm)
    {
      //CDocument* pDoc = GetActiveDocument();
        CView *pOldActiveView=GetActiveView();             //保存旧视图
        CView *pNewActiveView=(CView*)GetDlgItem(nForm);   //取得新视图
        if(pNewActiveView==NULL)
        {
            switch(nForm)
                //这些ID是对话框的标志符,但也可以用其他的标志
            {
            case IDD_MULTIVIEW_FORM :
                pNewActiveView=(CView*)new CView1;
                break;
            case IDD_MULTIVIEW_FORM2 :
                pNewActiveView=(CView*)new CView2;
                break;
            case IDD_MULTIVIEW_FORM3 :
                pNewActiveView=(CView*)new CView3;
                break;
            case IDD_MULTIVIEW_FORM4 :
                pNewActiveView=(CView*)new CView4;
                break;
            }
            CCreateContext context;   //将文挡和视图相连
            context.m_pCurrentDoc=pOldActiveView->GetDocument();
    pNewActiveView->Create(NULL, NULL, WS_BORDER|WS_CHILD ,
                CFrameWnd::rectDefault, this, nForm, &context);


            pNewActiveView->OnInitialUpdate();
        }
        SetActiveView(pNewActiveView);        //改变活动的视图
        pNewActiveView->ShowWindow(SW_SHOW);  //显示新的视图
        pOldActiveView->ShowWindow(SW_HIDE);  //隐藏旧的视图

        if(pOldActiveView->GetRuntimeClass() ==RUNTIME_CLASS(CView1))
    pOldActiveView->SetDlgCtrlID(IDD_MULTIVIEW_FORM);

    else if(pOldActiveView->GetRuntimeClass() ==RUNTIME_CLASS(CView2))
    pOldActiveView->SetDlgCtrlID(IDD_MULTIVIEW_FORM2);

    else if(pOldActiveView->GetRuntimeClass() ==RUNTIME_CLASS(CView3))
    pOldActiveView->SetDlgCtrlID(IDD_MULTIVIEW_FORM3);

    else if(pOldActiveView->GetRuntimeClass() ==RUNTIME_CLASS(CView4))
    pOldActiveView->SetDlgCtrlID(IDD_MULTIVIEW_FORM4);

        pNewActiveView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);
        
        

    delete pOldActiveView;   //删除旧视图

    RecalcLayout();          //调整框架窗口

    }

    以上为转载

    在测试过程中发现,想要进行视图动态切换,必须是view类,对话框类是不能动态切换的。

    展开全文
  • 文档文档视图

    千次阅读 2014-01-09 10:38:41
    1,SDI应用程序文档类由CDocument类派生,一个文档类可以有一个或多个由CView类派生的视图类。   2,重要成员函数: 1)CView::GetDocument CDocument* GetDocument( ) const; //文档对象是用来保存数据的,而视图...

    1,SDI应用程序文档类由CDocument类派生,一个文档类可以有一个或多个由CView类派生的视图类。
     
    2,重要成员函数:
    1)CView::GetDocument
    CDocument* GetDocument( ) const;
    //文档对象是用来保存数据的,而视图对象则是用来显示数据的。
    //一个视图对象只有一个与之相关连的文档对象。
    //Return A pointer to the CDocument object associated with the view.Return NULL if the view is not attached to a document.
    //Call this function to get a pointer to the view’s document. This allows you to call the document’s member functions.

    2)CDocument::UpdateAllViews 
    void UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL );
    //当文档数据发生修改的时候,调用该函数通知所有的视图对所显示的数据进行相应得更新。
    //pSender:Points to the view that modified the document, or NULL if all views are to be updated.
    //如果在派生文档类中调用UpdateAllViews函数,则pSender应该设置为NULL;如果UpdateAllViews函数在派生视图类成员函数中调用,则pSender参数应该设置成this(如:GetDocument()->UpdateAllViews(this);)

    3)CView::OnUpdate 
    virtual void OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint );
    //当应用程序调用了CDocument::UpdateAllViews函数时,会调用OnUpdate函数更新视图显示。
    //通常视图类OnUpdate函数先对文档进行访问,读取文档的数据,然后对视图的数据成员或控制进行更新,以反应文档的变化。
    //可以利用OnUpdate函数使视图的某部分无效,以触发视图的OnDraw函数调用,从而利用文档数据来重绘对应的视图窗口。
    //默认的OnUpdate函数使得整个窗口矩形无效。
    //当程序调用CDocument::UpdateAllViews函数,如果pSender参数指向了某个特定的视图对象,则除了该指定的视图之外,文档的所有其它视图的OnUpdate函数都会被调用。

    4)CView::OnInitialUpdate 
    virtual void OnInitialUpdate( );
    //当应用程序被启动,或当用户从File菜单选择了New时候,或当用户从File菜单选择了Open时候,该虚函数都会被自动调用。
    //CView基类中的OnInitialUpdate函数除调用OnUpdate函数中没做其它任何事情。如果在派生类中重载该函数一定要调用基类的OnInitialUpdate函数,或调用派生类的OnUpdate函数。
    //当应用程序启动时,框架调用视图类的OnCreate函数之后立即调用OnInitialUpdate函数。OnCreate函数只能被调用一次,而OnInitialUpdate可以被调用多次。
    //可以通过在派生重载OnInitialUpdate函数,在其中对视图对象进行初始化。

    5)CDocument::OnNewDocument 
    virtual BOOL OnNewDocument( );
    //Called by the framework as part of the File New command. The default implementation of this function calls the DeleteContents member function to ensure that the document is empty and then marks the new document as clean. Override this function to initialize the data structure for a new document. You should call the base class version of this function from your override.
    //If the user chooses the File New command in an SDI application, the framework uses this function to reinitialize the existing document, rather than creating a new one. If the user chooses File New in a multiple document interface (MDI) application, the framework creates a new document each time and then calls this function to initialize it. You must place your initialization code in this function instead of in the constructor for the File New command to be effective in SDI applications.


    3,简单文档视图交互应用程序步骤(单文档单视图):
    1)在派生文档类中定义文档的数据成员,用以保存程序中数据。为了方便派生视图类的访问可以将这些数据成员定义成公有类型或将派生视图类定义为派生文档类的友元类。
    2)在派生视图类中对OnInitialUpdate虚成员函数进行重载。当文档数据被初始化或被从磁盘中读出后,框架会自动调用OnInittialUpdate函数。该函数对视图进行更新,以便放映出当前的文档数据。
    3)在派生视图类中,让窗口消息控制函数和命令消息控制函数直接读取和更新文档数据成员,利用GetDocument函数对文档对象进行访问。

    对应事件发生次序:
    程序启动: CMyDocument对象被创建->CMyView对象被创建->视图窗口被创建->CMyView::OnCreate函数被调用(如果被映射)->CMyDocument::OnNewDocument函数被调用->CMyView::OnInitialUpdate函数被调用->视图对象被初始化->视图窗口无效->CMyView:OnDraw函数被调用
    用户编辑数据: CMyView类中函数对CMyDocument数据成员进行更新
    退出程序: CMyView对象被删除->CMyDocument对象被删除


    4,CFormView类
    CFormView类具有许多无模式对话框的特点,其派生类也和相应的对话框资源相联系,也支持DDX和DDV等。
    CFormView类对象可以直接接收来之本身的控制通告消息,也可接收来自框架窗口的命令消息。(同时具有许多无模式类对话框和视图类特征)。
    派生层次:CView|CScrolView|CFromView
    CFormView类虽然不是从CDialog类派生,但是围绕对话框创建,因而可以使用许多CDialog类成员函数。(这时只需将CFormView指针强制转换成CDialog类指针即可)如:((CDialog*)this)->GotoDlgCtrl(GetDlgItem(IDC_NAME));


    5,高级文档视图交互应用程序步骤(单文档多视图):
    编写多视图应用程序,只要对其中某一视图编辑改变了文档,则其它的文档需要随之更新,以反映出文档的变化。
    步骤:
    1)在派生文档类中定义需要的数据成员。同时设计访问该数据成员的方法,或将视图类设计成文档类的友元类。
    2)在派生视图类中,利用向导重载OnUpdate虚成员函数。当文档数据被修改后,应用程序框架会自动调用该函数。利用重载的OnUpdate函数来完成视图的更新,以反映当前的文档数据。
    3)对所有的命令消息判断其哪些是针对文档的哪些是针对视图的,然后将其映射到相应的类中。
    4)在派生视图类中,允许对文档数据进行更改,在退出之前,一定要调用CDocument::UpdateAllViews函数更新其它的视图。使用CView::GetDocument获取相关联的文档对象指针。
    5)在派生文档类中,允许对文档数据进行修改,修改后退出前要调用UpdateAllViews函数更新与其关联的所有视图。

    对应发生的事件次序:
    应用程序启动:CMyDocument对象被创建->CMyView对象被创建->其它视图对象被创建->视图窗口被创建->CMyView::OnCreate函数被调用(如果被映射)->CMyDocument::OnNewDocument函数被调用->CMyView::OnInitialUpdate函数被调用->调用CMyView::OnUpdate->初始化视图
    用户编辑数据: 视图类中函数对CMyDocument数据成员进行更新
      退出时候调用CDocument::UpdateAllViews函数更新其它的视图
    退出程序: 视图对象被删除->CMyDocument对象被删除


    转载说明出处:http://blog.csdn.net/hczhiyue/article/details/6228703

    展开全文
  • 使用下列原则初始化文档视图并对其进行事后清理: MFC 框架初始化文档视图;您初始化向文档视图添加...您必须解除分配从这些文档视图的成员函数中堆上分配的任何内存。 注意 请记住,整个应用程序的
  • 理解文档/视图框架

    千次阅读 2012-08-02 13:10:49
    但是MFC的应用程序框架把文档视图之间的关系封装了起来,初学的朋友往往不得要领,因此写程序往往被局限于用向导生成的框架中。本文希望能够尽可能说明白文档视图框架之间是如何进行作用,希望给一些朋友带来...
  • MFC-文档视图

    千次阅读 2007-12-17 10:04:00
    MDI程序 MiniDraw只有一个About对话框,这回要把它变成一个MDI程序,借助于文档视图的威力,并需要花很大的力气。 MDI由4个类组成:主框架类,由CMDIFrameWnd派生而来,表示程序的MDI父窗口。子框架类,由...
  • 深入浅出MFC文档/视图架构之视图

    千次阅读 2008-06-11 11:05:00
    视图类CView MFC"文档/视图"架构中,CView类是所有视图类的基类,它提供了用户自定义视图类的公共接口。"文档/视图"架构中,文档负责管理和维护数据;而视图类则负责如下工作: (1) 从文档类中将文档中的...
  • MFC文档视图结构学习笔记

    千次阅读 2015-03-15 07:44:58
    MFC通过其文档类和视图类提供了大量有关数据处理的方法 分为数据的管理和显示,文档用于管理和维护数据,视图用来显示和编辑数据 什么文档 文档的概念MFC应用程序中的适用范围很广,一般说来,文档是能够被...
  • 什么视图

    千次阅读 2013-06-08 20:49:23
    什么视图? 使用视图是以不同方式展现列表或库中的数据的有效方式。不同的视图可以展示不同的列、具有不同的排序和筛选、分组、样式。 SharePoint中,视图可以是公共的或私有的: 公共视图: 列表或库的管理...
  • MFC单文档视图切换

    千次阅读 2010-03-01 13:54:00
    代码简介或代码解析: 一些情况,应用程序有许多信息要显示,当一个窗口内显示不下时,VC提供的MDI应用程序可以解决这个问题,但是如果应用程序只是对单个文档,采用多种方式显示,则采用SDI更合适。...
  • 文档视图和框架

    千次阅读 2013-05-17 10:44:44
    文档是用户在编辑会话中与之交互的数据对象。它是用“文件”菜单上的“新建”或“打开”命令创建的,并且通常保存文件中。(从 CDocument 类派生的标准 MFC 文档不同于活动文档和 OLE 复合文档。)视图是窗口...
  • VC 单文档视图分割

    千次阅读 2012-02-08 15:06:15
    静态拆分窗口的行列数拆分窗口被创建时就设置好了,用户不能更改。但是用户可以缩放各行各列。一个静态拆分窗口最多可以包含16行16列。要找一个使用了静态拆分窗口的应用程序,只要看一下windows管理器即可。 ...
  • vc workspace不能显示类视图/工程文件视图/资源视图 打开一个工程时,左边的workspace空空白白的,很多初学者就愣了,这是啥错误啊,      当VC工作区类视图中的信息错误时,可以将VC自动生成的文件(.CLW,....
  • 文档视图

    千次阅读 2014-06-10 21:28:14
    1、创建单文档exe,支持切分窗口。 2、新建对话框资源 ID为IDD_TREEVIEW,Style=CHILD,BORDER=NONE, 删掉按钮OK和CANCEL,添加Tree控件IDC_TREE,占满整个对话框 导入位图资源,ID为IDB...
  • Axure RP 编辑自适应视图

    万次阅读 2015-01-19 14:00:12
    4、完成设置了不同自适应视图文档的界面编辑步骤。 步骤1:  点击“基本”按钮,切换到基本页面 RP编辑自适应视图" TITLE="Axure RP编辑自适应视图" /> 步骤2: 基本页面制作淘宝网首页: 注意:仅制作...
  • 出现分段符是因为你把"视图"里的"显示段落标记"小心关了.点击菜单工具栏上的"视图"下拉对话框中的"显示段落标记" 显示段落标记上打勾就OK了
  • 实验-多个视图同用一个文档

    千次阅读 2010-06-13 15:42:00
     一、目的 MDI工程,同一个文档类,拥有多个视图; 新建文档时,所以视图同时出现; 修改其中一个视图成员数据,其它与该成员数据相对应视图自动更新。 欲了解其中步骤和相关影响因素。...
  • 详解MFC框架窗口、文档视图

    万次阅读 2014-05-21 07:31:32
    尽管窗口、文档视图是MFC的基础,但可能也是最容易理解的部分,因为其概念比传统编程所需要的Windows函数更强一些,因此,须本章做进一步详细讨论框架窗口、文档视图的方法和技巧。 6.1框架窗口 分两类:...
  • 一、建立一个文档视图的工程,然后窗口上显示数据。 1、 阅读《MFC Windows 应用程序设计》 2、 记录笔记。 (1)、什么是消息?---何时,何地,发了什么事情。 (2)、MFC的机制----应用程序处于主导地位。 ...
  • Django Xadmin 官方文档 之七 视图

    千次阅读 2019-01-11 14:38:11
    Django Xadmin 官网上的第七部分, 也是官网的最后一部分, 本部分讲解的是 Xadmin 的视图相关类及其方法。 由于博主英语水平有限, 翻译不足之处, 烦请大神指教。 希望能够帮助挣扎 Django Xadmin 中的小伙伴们...
  • VS2017MFC单文档视图分割

    千次阅读 2019-09-11 21:18:43
    VS2017中,无法用类向导创建一个继承自CFormView类与对话框关联,所以分割视图不是很方便,接下来我将介绍一种用VS2017分割视图的方法。 结果:如图,左半边是一个带有TeeChart控件的对话框,右半边又分为两...
  • 文档视图(分割窗口)

    千次阅读 2012-03-22 11:36:47
    MFC中,视图必须存在于框架之中,而单文档只有一个框架,所以一次只能显示一个视图,如果想显示新视图必须关闭当前视图。如果想同时显示两个视图可以用分割窗口。 本示例用于查询学生信息,数据存在文档类中,...
  • MFC文档/视图结构体系及SDI回顾(1)

    千次阅读 2016-10-17 21:29:10
    1.文档/视图历史小传 MFC早期,应用程序的体系结构是相当的粗浅。早期的MFC1.0版本,应用程序具有两个主要的控件:代表应用程序自身的应用程序对象和代表...在文档视图应用程序中,应用程序的数据由文档对象代
  • MFC之文档/视图结构应用程序

    千次阅读 2019-05-19 16:18:19
    文档/视图结构应用程序 一、文档/视图结构分析 MFC 通过多个类提供了对程序框架的支持,使用这些类可以简单地实现文档/视图结构;其中主要包括以下五个类: 应用程序类(CWinAPP):是 MFC 程序的应用程序管理类,...
  •  (2)视图保留指向其文档的指针,并被包含其父框架窗口中;  (3)文档框架窗口(即包含视图的MDI子窗口)保留指向其当前活动视图的指针;  (4)文档模板保留其已打开文档的列表,维护框架窗口、文档及...
  • 解决: 1.窗口——&gt;关闭所有文档 2.资源视图中重新打开Test.rc(双击Test.rc再双击) 
  • Firefox - 附加软件 - Firebug - DOM文档结构视图 - “冻结”被AJAX不断更新的DOM视图 当页面中包含定期执行的AJAX脚本时,DOM视图将自动刷新,便于查看,此时可以script视图中AJAX脚本处设置断点,这样DOM视图...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 100,399
精华内容 40,159
关键字:

在什么视图下不能编辑文档