前面几节讲了菜单、工具栏和状态栏的使用,本节开始将为大家讲解文档、视图和框架的知识。

文档、视图和框架简介

在VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)创建的单文档工程Example34中,我们可以看到MFC向导自动为我们生成了CExample34Doc类、CExample34View类和CMainFrame类,它们就分别是文档类、视图类和框架窗口类。

文档/视图结构是MFC提供的一种不错的设计,它将数据的处理和显示分开来,这样更便于我们对程序的维护和扩展。下面分别介绍这种结构中涉及到的几个重要概念。

文档

文档对象用于管理和维护数据,包括保存数据、取出数据以及修改数据等操作,在数据被修改以后,文档可以通知其对应的所有视图更新显示。

视图

视图对象将文档中的数据可视化,负责从文档对象中取出数据显示给用户,并接受用户的输入和编辑,将数据的改变反映给文档对象。视图充当了文档和用户之间媒介的角色。

框架

一个文档可能有多个视图界面,这就需要有框架来管理了。框架就是用来管理文档和视图的。框架窗口是应用程序的主窗口,应用程序执行时会先创建一个最顶层的框架窗口。视图窗口是没有菜单和边界的子窗口,它必须包含在框架窗口中,即置于框架窗口的客户区内。

文档模板

文档模板中存放了与文档、视图和框架相关的信息。应用程序通过文档模板创建文档对象、框架窗口对象和视图对象。另外,文档、视图和框架之间的关系也是由文档模板来管理的。

我们来看看Example34单文档程序中,CExample34App应用程序类的成员函数CExample34App::InitInstance()创建并注册文档模板的部分:

BOOL CExample34App::InitInstance()   
{   
    ......略   
    // Register the application's document templates.  Document templates   
    //  serve as the connection between documents, frame windows and views   
    CSingleDocTemplate* pDocTemplate;   
    pDocTemplate = new CSingleDocTemplate(   
        IDR_MAINFRAME,   
        RUNTIME_CLASS(CExample34Doc),   
        RUNTIME_CLASS(CMainFrame),       // main SDI frame window   
        RUNTIME_CLASS(CExample34View));   
    if (!pDocTemplate)   
        return FALSE;   
    AddDocTemplate(pDocTemplate);   
   
    ......略   
   
    return TRUE;   
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

在构造文档模板类CSingleDocTemplate的对象时,第一个参数是资源ID IDR_MAINFRAME,它包括框架窗口图标等,后面的三个参数都是RUNTIME_CLASS宏的调用,RUNTIME_CLASS用于获取类的运行时信息,文档模板可以根据这些动态创建信息来创建相应类的对象,即文档对象、框架窗口对象和视图对象。AddDocTemplate函数用来注册文档模板对象。

框架类、文档类和视图类

在VS2010自动生成的代码中,框架类继承于CFrameWndEx类,文档类继承于CDocument类,视图类继承于CView类。

CFrameWndEx类又继承于CFrameWnd类,CFrameWnd类中用于管理文档和视图的成员函数包括:

virtual CDocument* GetActiveDocument( );
  • 1.

获得当前活动视图对应文档对象的指针,如果不存在则返回NULL。

CView* GetActiveView( ) const;
  • 1.

获得当前活动视图对象的指针,如果不存在则返回NULL。

void SetActiveView(CView* pViewNew, BOOL bNotify = TRUE);
  • 1.

设置活动视图。参数pViewNew为要激活的视图对象的指针,参数bNotify指定视图是否接收激活通知。

CDocument类的主要成员函数:

virtual BOOL OnNewDocument( );
  • 1.

创建新文档。可以重载使用。

virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
  • 1.

打开文档。参数lpszPathName为要打开的文档的路径。可以重载使用。

virtual BOOL OnSaveDocument(LPCTSTR lpszPathName);
  • 1.

保存文档。参数lpszPathName指定文档保存到的全路径。可以重载使用。

CDocTemplate* GetDocTemplate( ) const;
  • 1.

获取此文档类型对应的文档模板对象的指针。如果此文档没有被文档模板管理则返回NULL。

virtual POSITION GetFirstViewPosition( ) const;
  • 1.

获取文档中视图列表的第一个视图的位置。

virtual CView* GetNextView(POSITION& rPosition) const;
  • 1.

利用此函数可以迭代处理文档的所有视图。参数rPosition为上一次调用GetFirstViewPosition或GetNextView成员函数返回的POSITION值的引用。

void AddView(CView* pView);
  • 1.

为文档增加一个视图。参数pView为要增加的视图对象的指针。

void RemoveView(CView* pView);
  • 1.

移除某个视图与文档的关联。参数pView为要移除的视图对象的指针。

void UpdateAllViews(CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL);
```cpp
在文档被更改后调用此函数更新视图。参数pSender指向修改文档的视图,实际应用时常用来指定哪个视图不需要更新,如果更新所有视图则设为NULL,参数lHint包含了文档修改的信息,参数pHint指向存储文档修改信息的对象。

# CView类中与文档/视图结构相关的成员函数包括:
```cpp
       CDocument* GetDocument( ) const;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

获取视图关联的文档对象的指针。如果视图没有关联到文档上则返回NULL。

前面一节中进行了文档、视图和框架的概述,本节主要讲解文档、视图、框架结构中各对象之间的关系。

各个对象之间的关系

文档、视图、框架结构中涉及到的对象主要有:应用程序对象、文档模板对象、文档对象、视图对象和框架窗口对象等。根据上一节的概述,大家对它们的概念已经有所了解了,下面就对它们之间的关系进行总结和概括,并对各个关系中用到的类的成员函数进行介绍。

  1. 应用程序对象保存了一个文档模板的列表。在任何对象中调用全局函数AfxGetApp都可以获得应用程序对象的指针。通过调用CWinAppEx::GetFirstDocTemplatePosition、CWinAppEx::GetNextDocTemplate函数可以遍历所有的文档模板。
  2. 文档模板对象用于维护文档、视图和框架窗口的映射关系,它包含有一个已打开文档的列表。我们可以通过调用CDocTemplate::GetFirstDocPosition、CDocTemplate::GetNextDoc来遍历该文档模板对应的所有文档。
  3. 框架窗口对象中包含有指向当前活动视图对象的指针。AfxGetApp()->m_pMainWnd即为主框架窗口对象的指针。我们可以通过调用CFrameWndEx::GetActiveView来获取当前活动视图对象的指针,并且使用CFrameWndEx::GetActiveDocument函数可以获得当前活动视图对应的文档。
  4. 文档对象中维护着该文档的视图列表,以及创建该文档的文档模板对象的指针。我们可以通过调用CDocument::GetFirstViewPosition,CDocument::GetNextView来遍历该文档关联的所有视图,调用CDocument::GetDocTemplate获取创建该文档的文档模板对象的指针。
  5. 视图是框架窗口的子窗口,它保存有指向对应的文档对象的指针。我们可以通过调用CView::GetParentFrame获取其所属的框架窗口对象的指针,调用CView::GetDocument获取该视图对应的文档对象的指针。

另外,在MDI多文档程序中,调用CMDIFrameWnd::MDIGetActive可以获取当前活动的MDI子窗口。

文档和视图的关系

应用程序可以是单文档程序也可以是多文档程序。单文档程序中主框架窗口和文档框架窗口重合,而多文档程序的主框架窗口中有客户窗口,客户窗口中又包含了多个文档框架窗口。

文档和视图是一对多的关系。一个文档可以对应多个视图,例如在Word中一个文档有普通视图、大纲视图、Web版式视图、阅读版式视图等多种视图。而一个视图只能属于一个文档。最简单的应用程序是单文档单视图程序,除此之外还有单文档多视图程序、多文档程序等。

每个文档对象都保存着一个视图列表,可以通过CDocument::AddView函数添加视图,通过CDocument::RemoveView函数删除视图,在数据发生变化时调用CDocument::UpdateAllViews函数更新所有视图。

在MFC中文档可以有三种视图模式:

  1. 文档有多个视图对象,它们是同一个视图类的对象,每个视图对象位于一个独立的文档框架窗口中。
  2. 文档的基于同一个视图类的多个视图对象,位于同一个文档框架窗口中。Word的子窗口就是这种视图模式。

3.文档的视图对象属于不同的视图类,但所有的视图对象位于同一文档框架窗口中。

在网上找到了一张分别对应三种视图模式的图如下:

MFC---概述、各对象之间的关系和分割窗口(文档、视图和框架)_应用程序

有关文档、视图和框架等对象之间的关系就讲到这里了。

上一节中讲了文档、视图和框架结构中各对象之间的关系,本节主要讲讲在MFC中如何分割窗口。

分割窗口概述

分割窗口,顾名思义,就是将一个窗口分割成多个窗格,在每个窗格中都包含有视图,或者是同一类型的视图,或者是不同类型的视图。

MFC分割窗口的方式有两种,动态分割和静态分割。

动态分割窗口通常用于创建同一个文档对应的多个视图,而且这些视图一般都是同一类型的视图,能够在用户编辑文档的不同部分时提供方便。

大家看下Word里的动态分割窗口就很明白了,以Word 2007文档为例,在菜单中点击“视图”->“拆分”,就可以看到一条随鼠标移动的分隔条,当我们在文档中某个位置按下鼠标左键时,分割条就固定了下来,生成了上下两个分割窗格,通过滚动每个窗格中的垂直滚动条可以看到,两个窗格中的内容相同,这就是所说的对应同一个文档的同一类视图。

动态分割窗口最多可以有两行两列

静态分割窗口比较常见。我们经常能看到某个软件打开后,界面窗口默认被分割成了几个窗格,这就是静态分割窗口。

静态分割窗口指在窗口创建时,分割的窗格就已经生成了,而且用户不能改变窗格的数量和顺序。静态分割窗口最多支持16行16列。通常静态分割窗口的每个窗格中包含不同类的视图,当然也可以是同一类的视图。

CSplitterWnd类

MFC中的分割窗口类-CSplitterWnd类提供了分割窗口的功能。CSplitterWnd类中包含一个分割器窗口,该分割器窗口就是一个包含多个窗格的窗口。我们分割窗口时就是直接在此分割器窗口中分割的。

下面介绍三个最常用的成员函数:

virtual BOOL Create(   
   CWnd* pParentWnd,   
   int nMaxRows,   
   int nMaxCols,   
   SIZE sizeMin,   
   CCreateContext* pContext,   
   DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | SPLS_DYNAMIC_SPLIT,   
   UINT nID = AFX_IDW_PANE_FIRST    
);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

创建动态分割窗口。参数pParentWnd为分割器窗口的父框架窗口;参数nMaxRows为分割器窗口的最大行数,不能超过2;参数nMaxCols为分割器窗口的最大列数,也不能超过2;参数sizeMin为窗格能显示的最小尺寸,如果窗格尺寸小于sizeMin则不显示;参数pContext为指向CCreateContext结构的指针,大多数情况下可以赋值为父框架窗口的pContext;参数dwStyle指定窗口风格;参数nID为分割窗口的ID,除非分割器窗口嵌入到另一个分割器窗口中,否则可以取值AFX_IDW_PANE_FIRST。

virtual BOOL CreateStatic(   
   CWnd* pParentWnd,   
   int nRows,   
   int nCols,   
   DWORD dwStyle = WS_CHILD | WS_VISIBLE,   
   UINT nID = AFX_IDW_PANE_FIRST    
);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

创建静态分割窗口。参数pParentWnd、dwStyle和nID同上;参数nRows为行数,不能超过16;参数nCols为列数,同样不能超过16。

virtual BOOL CreateView(   
   int row,   
   int col,   
   CRuntimeClass* pViewClass,   
   SIZE sizeInit,   
   CCreateContext* pContext    
);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

为静态分割窗口创建窗格视图。参数row指定分割器窗口中放置新视图的行;参数col指定放置新视图的列;参数pViewClass指定新视图的CRuntimeClass对象;参数sizeInit指定新视图的初始大小;参数pContext为指向CCreateContext结构的指针,通常可以赋值为传递给父框架窗口的重载函数CFrameWnd::OnCreateClient的pContext参数值。

动态分割窗口

创建动态分割窗口的步骤为:

  1. 在父框架类中定义一个CSplitterWnd类型的成员对象。
  2. 重载父框架类的CFrameWnd::OnCreateClient成员函数。
  3. 在重载的CFrameWnd::OnCreateClient函数中调用CSplitterWnd成员对象的Create函数。

下面给大家一个实例。同样以VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)中创建Example34工程为例,我们要实现在主框架窗口的客户区中创建两行两列的动态分割窗口。以下是创建动态分割窗口的具体步骤:

  1. 在MainFrm.h文件中为CMainFrame类添加成员对象:CSplitterWnd m_wndSplitter;。
  2. 在Class View类视图中找到CMainFrame类,右键点击,在右键菜单中选择Properties,就会显示属性页,然后在属性页的工具栏上点击Tip为Overrides的按钮,下面的列表中就列出了能够重载的函数,找到OnCreateClient生成重载函数。
    设置ID为ID_WINDOW_SPLIT的原因

在MFC框架中,ID_WINDOW_SPLIT是一个预定义的菜单项ID。当MFC检测到此ID被点击时,它会自动执行一些预定义的操作来显示分割窗口。这个ID在MFC框架中是专门用来处理分割窗口的,因此必须设置为ID_WINDOW_SPLIT,以便MFC框架能够识别并处理这个操作。

为什么可以分割窗口
当你点击菜单中的View->Splitter Window菜单项时,MFC会处理ID_WINDOW_SPLIT并执行分割窗口的相关操作。这包括调用分割窗口对象的创建方法和显示分割窗口的界面布局。这些操作已经在MFC的底层实现中定义好了,所以只需使用正确的ID即可触发这些行为。

  1. 在MainFrm.cpp文件中找到刚重载的OnCreateClient函数修改如下:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)   
{   
 // TODO: Add your specialized code here and/or call the base class   
 // 创建动态分割窗口,两行两列   
 return m_wndSplitter.Create(this,2, 2, CSize(10, 10), pContext);   
   
 //return CFrameWndEx::OnCreateClient(lpcs, pContext);   
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  1. 在Resource View资源视图中,打开Menu下的IDR_MAINFRAME菜单,在View下添加一个菜单项,Caption设为Splitter Window,ID设为(一定要设为)ID_WINDOW_SPLIT。这样在运行结果界面中点击此菜单项时MFC会执行一些操作显示动态分割窗口。
  2. 运行程序,点击菜单中的View->Splitter Window菜单项,创建动态分割窗口后效果如下:
  3. MFC---概述、各对象之间的关系和分割窗口(文档、视图和框架)_成员函数_02

静态分割窗口

创建静态分割窗口的步骤为:

  1. 在父框架类中定义一个CSplitterWnd类型的成员对象。
  2. 重载父框架类的CFrameWnd::OnCreateClient成员函数。
  3. 在重载的CFrameWnd::OnCreateClient函数中调用CSplitterWnd成员对象的CreateStatic成员函数,然后可以调用CSplitterWnd成员对象的CreateView成员函数为每个窗格创建视图。

仍通过Example34工程给大家一个实例,目的是在主框架窗口中的客户区创建一个两行一列的静态分割窗口。如果已经试验过动态分割窗口的创建,那么麻烦撤销那些修改吧。创建静态分割窗口的具体步骤如下:

  1. 在MainFrm.h文件中为CMainFrame类添加成员对象:CSplitterWnd m_wndSplitter;。
  2. 在Class View类视图中找到CMainFrame类,右键点击,在右键菜单中选择Properties,就会显示属性页,然后在属性页的工具栏上点击Tip为Overrides的按钮,下面的列表中就列出了能够重载的函数,找到OnCreateClient生成重载函数。
  3. 在MainFrm.cpp文件中找到刚重载的OnCreateClient函数进行修改。因为没有新建其他视图类,所以上下两个窗格的视图都是CExample34View。为了能识别CExample34View类,还需在MainFrm.cpp文件中添加#include “Example34View.h”,在Example34View.h文件中添加#include “Example34Doc.h”。最终OnCreateClient函数修改如下:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)   
{   
    // TODO: Add your specialized code here and/or call the base class   
    CRect rc;   
   
    // 获取框架窗口客户区的CRect对象   
    GetClientRect(&rc);   
   
    // 创建静态分割窗口,两行一列   
    if (!m_wndSplitter.CreateStatic(this, 2, 1))   
        return FALSE;   
   
    // 创建上面窗格中的视图   
    if (!m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CExample34View), CSize(rc.Width(), rc.Height()/2), pContext))   
        return FALSE;   
   
    // 创建下面窗格中的视图   
    if (!m_wndSplitter.CreateView(1, 0, RUNTIME_CLASS(CExample34View), CSize(rc.Width(), rc.Height()/2), pContext))   
        return FALSE;   
   
    return TRUE;   
   
    //return CFrameWndEx::OnCreateClient(lpcs, pContext);   
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  1. 运行程序,在结果界面中关掉其他面板后效果如下:

如果大家想创建在其中某个窗格中再嵌套分割窗口,那么就需要再定义一个CSplitterWnd对象,以父窗格所在的CSplitterWnd对象为父框架窗口创建分割窗口即可。

分割窗口的内容就讲到这里了。