2023年12月16日发(作者:)

2016/5/30

文档视图结构

文档视图结构是MFC中结构最为复杂,体系最为庞大,最具有代表性的部分。MFC之所以被称为应用程序框架,原因之一就是文档视图结构将应用程序数据管理与数据显示分离,允许用户对两部分代码进行独立编写。

一、MFC支持两种文档视图应用程序,

1、单文档界面(SDI):只有一个窗口,即用户在同一时刻只能操作一个文档。(记事本)

多文档界面(MDI):可以同时操作多个文档。(WORD)

2、MFC隐藏了两种用户界面模型的许多差别,通过应用程序向导创建两种文档的过程完全一样,但SDI文档不生成类CChildFrame,SDI主框架类CMainFrame的基类是CFrameWnd,而MDI的类CMainFrame的基类是CMDIFrameWnd。

二、文档、视图、框架、文档模板

文档视图结构由四个要素组成,文档,视图,框架和文档模板。

1、 文档:用于管理应用程序的数据——应用程序数据的抽象表示

1.1任务:1)对数据进行管理与维护,数据保存在文档类的成员变量中。2)处理命令消息,如菜单,工具栏,加速键的WM_COMMAND通知消息(但不能处理其他WINDOWS消息)(WINDOWS消息和控件消息只能被窗口和视图处理)。类CDocument是所有文档类的基类,提供文档类所需要的,最基本的功能实现,为文档对象和其他对象的交互提供基本框架。

1.2类CDocument提供的三种方法:

1)构造方法:创建CDocument类对象,函数声明:CDocument()。

2)操作方法(12种方法):

AddView,为文档添加视图;

GetTitle,返回文档标题;

RemoveView,为文档移除视图;

SetTitle,为文档设置标题;

SetPathName,设置文档数据文件的路径 。。。 。。。

3)虚拟方法(12种):使应用程序能够对其重写一=以获取类CDocument派生类方法。

DeleteContents,删除文档数据;

OnChangeViewList,检查视图是否有增加或减少;

OnNewDocument,创建文档;

OnOpenDocument,打开文档;

OnSaveDocument,保存文档 。。。 。。。

2、 视图:用于控制数据的显示。

2.1在文档和用户之间扮演着中介的角色,将文档的数据取出后显示给用户,同时也接受用户通过界面操作对文档中数据的编辑和修改。类CView是所有视图类的基类,可以接受任何WINDOWS消息,是纯虚基类

2.2三种方法

1)构造方法:创建CView类对象,函数声明:CView()。

2)操作方法(2种方法):

GetDocument,获取与视图相关联的文档的指针(最重要的函数)

DoPreparePrinting,初始化打印功能

3)虚拟方法(11种):

OnDraw,绘制文档;(Windows产生WM_PAINT消息请求绘制文档,应用程序调用函数

OnPaint处理消息,函数OnPaint会创建CPaintDC对象,并用指向该对象的指针调用OnDraw函数完成文档的绘制)。

OnUpdate,通知视图文档被修改;(文档接受修改后,应用程序就将调用它使当前视图无效并执行重绘,函数OnUpdate内的Invalidate(TRUE)将整个窗口设置成需要重绘的无效区,它产生WM_PAINT消息并调用OnDraw)。

OnActivateView,激活视图;(MDI任何时刻都只有一个活动视图)

OnPrint,打印或预览文档;

OnBeginPrinting,开始打印;

OnEndPrinting,结束打印 。。。 。。。

2.3 CView的派生类:

CScrollView :实现视图的滚动显示

CEditView:支持编辑控件的显示

CListView :支持列表控件显示

CTreeView :支持树形控件显示

CRecordView :支持数据库记录显示

3、 框架:用于进行界面管理。

3.1应用程序在显示屏幕上定义的实际工作区域,也是视图的容器,应用程序的主窗口。SDI,一个框架窗口,MDI,两个框架窗口,CMDIFrameWnd用作顶层框架窗口,CMDIChildWnd窗口被包含在顶层窗口中。

3.2四种方法

1)构造方法:创建CFrameWnd类对象,CFrameWnd();

2)初始化方法(8种):

Create,创建并初始化Windows框架窗口;(设置窗口样式,尺寸,父窗口等)

LoadFrame,根据资源信息动态创建框架窗口;

ShowControlBar,显示控制栏

SetDockState,框架窗口停靠 。。。 。。。

继承于CWnd的初始化函数PreCreateWindow,主要功能是注册窗口类。

操作方法和虚拟方法比较少用。

CMDIFrameWnd和CMDIChildWnd的操作方法:9种和5种,窗口激活,窗口最大化等。

4、 文档模板:负责创建文档、视图和框架。

4.1文档模板是基于文档视图结构的应用程序中最主要的部分,用来创建和管理文档类,视图类以及框架窗口,每一种文档类型都有一种文档模板与之对应,文档模板保存了文档,视图和框架窗口之间指向对方的指针。

创建先后顺序:创建文档—>创建框架—>创建视图。

4.2类CDocTemplate成员变量:m_pDocClass/m_pViewClass/m_pFrameClass,分别代表相互对应的文档、视图和框架窗口的CRuntimeClass对象指针。

4.3四种方法:构造方法,属性方法,操作方法,虚拟方法,其中属性方法与操作方法统称为一般方法。

一般方法(6种):GetDocString,得到与文档相关的字符串

虚拟方法(8种):CreatDocument,创建新的文档;

SetDefultTitle,设置文档的默认标题

三、文档、视图、框架的关系

1、关系概括

1)文档保留了该文档的视图列表,一个文档至少有一个相关联的视图,而一个视图只能与

一个文档相关联。

2)视图保留了指向其对应文档的指针,该指针包含在视图所在的框架窗口中。

3)框架保留了指向其当前活动视图的指针。

2、交互方式

1)文档与视图的交互方式:文档调用CDocument::GetFiestViewPosition、CDocument::GetNextView来遍历所有和文档关联的视图,视图调用CView::GetDocument得到对应的文档指针。

virtual POSITION GetFiestViewPosition ( ) const

virtual CView * GetNextView (POSITION & rPosition ) const

CDocument * GetDocument ( ) const

2)视图与框架的交互方式:视图调用CView::GetParentFrane获取框架窗口,而框架窗口调用CFrameWnd::GetActiveView获取当前活动视图指针。

CFrameWnd * GetParentFrane ( ) const

CView * GetActiveView ()const

3)文档与框架的交互方式:文档与框架没有直接的关联,将视图作为媒介。

Virtual CDocument * GetActiveDocument ();返回当前视图对应文档的指针,如果当前没有对应视图,则返回NULL。

单文档新建:CWinApp_________docManager->docSingleTemplate的

OpenDocumentFile函数参数为空,此函数完成了大部分东西,包括新建文档类框架类等______________然后是调用CDocument就没什么意思了,当然我们要是重载了CDocument的新建函数就是调用子类虚函数。

多文档新建:CWinApp_________docManager->docMultTemplate的 OpenDocumentFile函数参数为空,此函数完成了大部分东西,包括新建文档类框架类等______________然后是调用CDocument就没什么意思了,当然我们要是重载了CDocument的新建函数就是调用子类虚函数。

单文档打开:CWinApp_________docManager中经过一个打开对话框传递参数,中途还调用了APP的OpenDocumentFile,当然如果我们的APP重载了这个函数也要调用我们的但是我们的函数一定别忘记最后返回是调用父类的此函数___________docSingleTemplate的 OpenDocumentFile函数参数不为空,此函数完成了大部分东西,包括新建文档类框架类等______________然后是调用CDocument就没什么意思了,当然我们要是重载了CDocument的新建函数就是调用子类虚函数。

多文档打开:CWinApp_________docManager中经过一个打开对话框传递参数,中途还调用了APP的OpenDocumentFile,当然如果我们的APP重载了这个函数也要调用我们的但是我们的函数一定别忘记最后返回是调用父类的此函数___________docMultTemplate的

OpenDocumentFile函数参数不为空,此函数完成了大部分东西,包括新建文档类框架类等______________然后是调用CDocument就没什么意思了,当然我们要是重载了CDocument的新建函数就是调用子类虚函数。

他们两个只有在docMultTemplate和docSingleTemplate的 OpenDocumentFile函数中的动作不同,单文档负责新建框架类和视类但是如果存在了我们就不重建了,只是给其赋值。而多文档无论如何都会新建一个视类和框架类文档类,这也就是为什么他是多文档结构的原因。

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

接下来介绍这个最重要的函数,它基本什么都干了,不管是新建还是打开都得调用它,呵呵……

//

CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,

BOOL bMakeVisible)

{

//下面调用的是CDocTemplate::CreateNewDocument()

CDocument* pDocument = CreateNewDocument();//这里面调用了AddDocument(pDocument);

//添加了一个CMyMultiTestDoc : public CDocument

//CMultiDocTemplate::m_docList保存的所有该种文档的

//文档实例的指针列表。

if (pDocument == NULL)

{

TRACE0("CDocTemplate::CreateNewDocument returned NULL.n");

AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);

return NULL;

}

ASSERT_VALID(pDocument);

BOOL bAutoDelete = pDocument->m_bAutoDelete;

pDocument->m_bAutoDelete = FALSE; // don't destroy if something goes wrong

CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL);//创建了一个新的CChildFrame框架

pDocument->m_bAutoDelete = bAutoDelete;

if (pFrame == NULL)

{

AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);

delete pDocument; // explicit delete on error

return NULL;

}

ASSERT_VALID(pFrame);

if (lpszPathName == NULL)

{

// create a new document - with default document name

SetDefaultTitle(pDocument);

// avoid creating temporary compound file when starting up invisible

if (!bMakeVisible)

pDocument->m_bEmbedded = TRUE;

if (!pDocument->OnNewDocument())//刚打开时新建的文档。add by ralf

{

// user has be alerted to what failed in OnNewDocument

TRACE0("CDocument::OnNewDocument returned FALSE.n");

pFrame->DestroyWindow();

return NULL;

}

// it worked, now bump untitled count

m_nUntitledCount++;

}

else

{

// open an existing document

CWaitCursor wait;

if (!pDocument->OnOpenDocument(lpszPathName))//这里是打开一个文档。add by ralf

{

// user has be alerted to what failed in OnOpenDocument

TRACE0("CDocument::OnOpenDocument returned FALSE.n");

pFrame->DestroyWindow();

return NULL;

}

pDocument->SetPathName(lpszPathName);

}

InitialUpdateFrame(pFrame, pDocument, bMakeVisible);

return pDocument;

}

要了解 文档、视图、框架窗口、文档模板之间的相互关系,关键要理解他们的结构

1、首先应该对 CWinApp类有充分的了解

它包含并管理着应用程序的文档/视窗的所有信息。它有一个成员变量

CDocManager * m_pDocManager,此变量是文档/视窗的管理器,m_templateList

是CDocManager里的一个列表,此列表里保存了所有文档模板的指针,当用户调用

CWinApp::AddDocTemplate( pDocTemplate ) 后该pDocTemplate存入了

CWinApp::m_pDocManager::m_templateList里。

CWinApp::GetFirstDocTemplatePosition()

CWinApp::GetNextDocTemplate(POSITION& pos)

是遍例所有的文档模板指针。

2、上面我们提到了文档模板(CMultiDocTemplate(我们主要针对对文档)),

这是一个极重要的类。CMultiDocTemplate::m_docList保存的所有该种文档的

文档实例的指针列表。下面两个函数用于维护CMultiDocTemplate::m_docList数据

CMultiDocTemplate::AddDocument(CDocument* pDoc);

CMultiDocTemplate::RemoveDocument(CDocument* pDoc);

而CMultiDocTemplate::GetFirstDocPosition() const;

CMultiDocTemplate::CDocument* GetNextDoc(POSITION& rPos) const;

用于遍例该文档类型所有文档实例。

3、上面提到文档(CDocument)

CDocument 我们最熟悉不过了。每一个文档实例可有多个视与之相对应。

CDocument::m_viewList用来保存所有与此文档实例相关的View

★★★★这里我拉鲁夫说一下:CDocument::AddView函数用来维护m_viewList数据但我们一般不用★★★★

void CDocument::AddView(CView* pView)//★★★MFC源码

{

ASSERT_VALID(pView);

ASSERT(pView->m_pDocument == NULL); // must not be already attached

ASSERT(m_(pView, NULL) == NULL); // must not be in list

m_l(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::GetDocTemplate 可获得CMultiDocTemplate;

4、CView 他是放在CMDIChildWnd里的,每一个CMDIChildWnd有一个View

CView::GetDocument可获得与此视相关的CDocument

CView::GetParentFrame() 可获得CMDIChildWnd;

通过以上分析可见CWinApp,CMDIChildWnd,CView,CDocument,CMultiDocTemplate之间知道其中一个实例

必可知道其他所有几个实例,CWinApp统领全局,任何时候,只要获得CWinApp实例,则所有的文档模板,

文档实例,视,Frame窗口均可被枚举出来。AfxGetApp() 获得CWinApp实例指针。

理一理MFC的这几个类的关系,可以很容易明白上面的这些乱七八糟的逻辑。

App是应用域,所有的域中的东西都可以通过全局函数访问到它。

MainFrame是主框架,也基本可以用全局函数访问到。

MainFrame下是若干个ChildFrame,ChildFrame中若干个View和Document(可能不成对),ChildFrame管理着View,View和Document进行互操作。

因此整体框架就出来了,一般除了直接应用的关系都可以通过MainFrame-->Active

ChildFrame-->Active View-->Document这条线进行访问

_______________________________

关于MFC下的文档和视图以及框架之间的访问, 这些问题已经是老生常谈了,但我觉得还是都没有详细的说明,特

别是对于英语较差的人,我查看了一些blog,总结了一下!希望对和我一样的人有点帮助!

一:

1: 因为对于SDI程序,主框架窗口就是文档框窗(如果这个也不知道,就要查看一下MFC下的单文档的构成原理了).

下面所说的是关于单文档的.

例子: 在CMainFrame框架中如何得到视图类的指针.

可以 先得到框架指针,然后调用 GetActiveView 函数指向当前活动视.

C **View * pView;

pView=(C**View*)((CFrameWnd*)AfxGetApp()->m_pMainWnd)->GetActiveView();

当然这些也许都知道是这么用的,但真正的m_pMainWnd和AfxGetApp()是什么意思也许有的人不明白.

大家也许都知道如何在App中获得MainFrame指针(框架类): CWinApp 中的

m_pMainWnd变量就是CMainFrame的指针.

所以在别的类下也可以先得到m_pMainWnd,就得到了MainFrame的指针. 所以得到视图类的指针,必先 得到CFrameWnd的指针m_pMainWnd,然后在调用FrameWnd下的GetActiveView 就指向当前活动视.

m_pMainWnd的由来:

每一个MFC应用程序都有一个CWinApp派生类的对象。这个对象对应着程序的主线程。而 CWinApp 类中有一个 CWnd * m_pMainWnd 成员变量。这个成员变量记录了应用程序的主窗口。

当你新建一个MFC应用程序的时候,在 InitInstance虚函数里都会出现对 m_pMainWnd

赋值的语句.唯一的例外是单文档界面的MFC应用程序,你无法在 InitInstance 函数里看到这段代码,因为它已经被隐藏在 ProcessShellCommand 这个函数里了。由此你就可以下结论了:只要创建自己的窗口类,就要把这个类的对象赋值给 m_pMainWnd .而这个成员只能在C**APP类中才可以使用,所以怎样使用这个CWinApp类里的CWnd 类型的变量来得到主框架窗口的指针呢??

AfxGetApp函数才可以 , 因为AfxGetApp()得到的是CWinApp类的对象,且AfxGetApp返回值为CWinApp对象指针,就是MFC生成的C**中定义的那个对象(对象theApp的指针)。

因为你是在自己创建的项目中得到CWndApp成员函数或者成员变量,所以你必须强制转换为你自己的项目中的类,才能找到成员函数或者变量.

注: 在单文档中,获得视指针的最简单的方法还是

((C**View *)CFrameWnd::GetActiveView())

2: 当然在FrameWnd中也可以得到文档类的指针:

CMyDocument* pDoc;

pDoc=(CMyDocument*)((CFrameWnd*)AfxGetApp()->m_pMainWnd)->GetActiveDocument();

3: 由上面可以知道:在View中怎样获得MainFrame指针

CMainFrame *pMain=(CMainFrame *)AfxGetApp()->m_pMainWnd;

注: 从视图类中获得主帧窗口类指针:用函数:CWnd::GetParentFrame()或AfxGetMainWnd()也

可达到目的。GetParentFrame()的工作原理是在父窗口链中搜索,直到找到CFrameWnd或其派生类为止,并返回其指针。

((CMainFrame *)CWnd::GetParentFrame())

或者

((CMainFrame *)AfxGetMainWnd())

二:

当然对于MDI程序,由于子窗口才是文档框窗,因此首先要用GetActiveFrame()取得活动子框架窗口,然后通过该子窗口获取活动视图和文档:

CMDIChildWnd* pChild=(CMDIChildWnd*)((CFrameWnd*)AfxGetApp()->m_pMainWnd)-

>GetActiveFrame();

取得活动视图:

CMyView* pView=(CMyView*)pChild->GetActiveView();

取得活动文档:

CMyDocument* pDoc=pChild->GetActiveDocument();

注: 也可以用这种方法来得到多文档中的视指针

//获得活动子框架窗口

CMDIChildWnd* pChild=(CMDIChildWnd*)GetActiveFrame();

//或:

CMDIChildWnd* pChild=MDIGetActive();

//获得活动子帧窗口的活动视图

CMyView* pView=(CMyView*)pChild->GetActiveView();

三:

1. 从视图类获得文档类的指针

在视图类中需要引用文档类的地方之前,使用以下语句:

C*Doc *pDoc=(C*Doc*)GetDocument();

以后便可使用pDoc指针访问文档类。

2. 从文档类取得视图类的指针 CDocument类提供了两个函数用于视图类的定位:

GetFirstViewPosition()和GetNextView()

注意:GetNextView()括号中的参数用的是引用方式,因此执行后值可能改变.GetFirstViewPosition()用于

返 回第一个视图位置(返回的并非视图类指针,而是一个POSITION类型值),GetNextView()有两个功能:返回下一个视图类的指针以及用引用 调动的方式来改变传入的POSITION类型参数的值。很明显,在Test程序中,只有一个视图类,因此只需将这两个函数调用一次即可得到 CTestView的指针如下(需定义一个POSITION结构变量来辅助操作):

C*View* pView;

POSITION pos=GetFirstViewPosition();

pView=GetNextView(pos);

这 样,便可到了C*View类的指针pView.执行完成几句后,变量pos=NULL,因为没有下一个视图类,自然也没有下一个视图类的 POSITION.但是之几条语句太简单,不具有太强的通用性和安全特征;当象前面说的那样,当要在多个视图为中返回某个指定类的指针时,我们需要遍历所 有视图类,直到找到指定类为止。判断一个类指针指向的是否某个类的实例时,可用IsKindOf()成员函数时行检查.

如:

pView->IsKindOf(RUNTIME_CLASS(C*View));

即可检查pView所指是否是C*View类。

有了以上基础,我们已经可以从文档类取得任何类的指针。为了方便,我们将其作为一个文档类的成员函数,它有一个参数,表示要获得哪个类的指针。实现如下:

CView* C*Doc::GetVieww(CRuntimeClass* pClass)

{ CView* pView;

POSITION pos=GetFirstViewPosition();

while(pos!=NULL){

pView=GetNextView(pos);

if(!pView->IsKindOf(pClass))

break;}

if(!pView->IsKindOf(pClass)){

AfxMessageBox("Connt Locate the View.");

return NULL;}

return pView;}

其中用了两次视图类的成员函数IsKindOf()来判断,是因为退出while循环有三种可能:

为NULL,即已经不存在下一个视图类供操作;

已符合要求。

3.1 和2同是满足。这是因为GetNextView()的功能是将当前视图指针改变成一个视图的位置同时返回当前视图指针,因此pos是pView的下一个视图类的POSITION,完全有可能既是pos==NULL又是pView符合需要。当所需的视图是最后一个视图是最后一个视图类时就如引。因此需采用两次判断。

使用该函数应遵循如下格式(以取得CTestView指针为例):

CTestView* pTestView=(CTestView*)GetView(RUNTIME_CLASS(CTestView));

RUNTIME_CLASS是一个宏,可以简单地理解它的作用:将类的名字转化为CRuntimeClass为指针。

至于强制类型转换也是为了安全特性考虑的,因为从同一个基类之间的指针类型是互相兼容的。这种强制类型转换也许并不必