了解文档和视图的相互作用关系是编写MFC程序的基本功。但是MFC的应用程序框架把文档和视图之间的关系封装了起来,初学的朋友往往不得要领,因此写程序往往被局限于在用向导生成的框架中。本文希望能够尽可能说明白文档视图框架之间是如何进行作用,希望能给一些朋友带来小小的帮助。
几个概念:
(虽然大家都知道了,还是要重申一次)
文档对象:是用来保存数据的。
视图对象:是用来显示和编辑数据的。
应用程序框架:框架是用来管理不同文档显示界面的。例如你有一个数据网格显示界面,还有一个图形显示界面,它们的数据可能都来自你的文档,但是视图不同,怎么办用框架。为什么不用视图?为的是把界面管理独立的拿出来。
文档模板:MFC把文档/视图/框架视为一体,只要你创建文档/视图框架结构的程序,必定会为你创建这三个类。这个工作在在应用程序初始化时完成,如下:
BOOL CMyHtmlApp::InitInstance()
{
//。。。。。。
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CMyHtmlDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CMyHtmlView));
AddDocTemplate(pDocTemplate);
//。。。。。。
}
单文档:就是一次只能打开一个文件,和你的文档类型支持的多少无关。你完全可以做一个单文档的支持所有图象格式的程序,只不过它一次只能打开一个文档罢了。
多文档:就是你可以打开多个文件,和文档类型也无关。你也可以作一个可以同时打开多个文档的程序,但它只支持一种文档类型。
何时需要文档/视图框架结构?
首先你可以不使用文档视图这种框架结构,即便是在MFC中。你可以在你需要的时候选择使用这种方式。你可以完成一个只有视图没有文档的程序,例如一个基于对话框的应用。
哪什么时候需要呢?
当你想将你的数据层和界面层分开的时候。
通常我们对数据的操作放在文档类中,例如存取,打开,关闭。在这里你可以尽情的对你的数据进行操作,如果你需要,在对数据进行了改变后,对视图做一下更新,那么程序会将你对数据所做的改变呈现给你的程序的用户。由此可见视图的作用就是提供一个用户和数据之间进行数据交换的界面,它的作用就是在需要的时候显示数据,并在需要的时候提供输入界面。当用户输入后实际的数据操作工作是由文档类来做的。那框架类有在做什么呢?
框架类是为了便于管理你的文档类和视图类而存在的。通常我们的操作都是通过视图窗口完成,消息由视图进行接收并且进行处理。所以消息映射定义一般在视图中。但如果一个应用同时拥有多个视而当前活动视没有对消息进行处理则消息会发往框架窗口。另外框架窗口可以方便的处理非窗口消息。
再来说一边典型的单文档程序的生成过程(不完整,只挑有用的)
1、 CwinApp对象被建立,这个对象是全局的且只能有一个,名字叫theApp。这时你可以完成一些工作,例如对注册表的操作,(如果你想写一个不修改注册表的软件,需要在这里做写工作)
2、 在InitInstance()函数中创建文档模板,文档模板以CruntimClass静态成员指针做构造参数。
3、 执行MFC框架默认的命令行参数。命令行参数有很多其中之一是,Cmd1它会创建一个新文件。(如果没有命令行参数则执行默认的ID_FILE_NEW)
4、 文档模板的实例根据三个类的动态创建信息创建出文档、视图、框架。
5、 对文档、视图、框架进行初始化。
我们对文档,视图,框架如何产生以及他们的用途有了一定的了解,如何有效的使用它们呢。
文档,视图,框架之间的相互作用。
由上面的典型的单文档程序的生成过程可以看出一个完整的应用一般由四个类组成:CWinApp应用类,CFrameWnd框架类,CDocument文档类,CView视图类。我将四个类常用的成员函数列出,大家一看便知。不过参数,返回值均未列出,大家可以从MSDN上了解更多。几个重要的虚函数也未做说明。大家自己看吧。
通过全局函数AfxGetApp可以得到CWinApp应用类的全局对象theApp.
CwinApp
数据成员:
m_pszAppName 应用程序名称
m_pszExeName 可执行文件的名称
m_pszProfileName INI文件的名
m_pszRegistryKey 注册表或INI文件的KEY
m_hInstance 实例的句柄
m_pMainWnd 为框架窗口指针
成员函数:
InitInstance() //初始化
ParseCommandLine() //完成命令行的解析处理
CFrameWnd
GetActiveDocument() //得到当前活动文档指针
GetActiveView() //得到当前活动视指针
SetActiveView() //设置当前视图为活动视图
CDocument
OnNewDocument()
OnOpenDocument()
OnSaveDocument()
OnFileClose()
//以上是用来对文档的操作
GetFirstViewPosition() //文档对象链表中的第一个文档位置
GetNextView() //下一个
//以上是用来遍历所有和文档关联的视图
GetDocTemplate()得到文档模板指针
AddView() //增加一个视图
RemoveView() //删除一个视图
UpdateAllView() //更新所有视图
Cview
GetDocument()得到对应的文档指针
其他的就不列出了,大家还是看MSDN。你可以直接查看CWinApp应用类,CFrameWnd框架类,CDocument文档类,CView视图类的类成员。
最后说说几个常见到的问题。
1、 为什么在对话框的应用程序中没有发现文档模板?
默认的对话框程序没有使用文档/视图框架结构。
2、 如果我使用数据库作为数据源是否意味着可以不需要文档类?
看你自己,但是我建议使用。因为可以文档,视图这一个清晰方便的框架结构,以及方便完成三者之间的相互作用。
补充:
菜单栏包含在主框架窗口(MainFrame)中,基类为CMenu.通常有两种应用。应用一:创建主菜单的使用步骤:
1、利用资源编辑器来编辑菜单。可以加入一个分隔符,可以为菜单项添加快捷键和键盘加速键以及在状态栏中显示提示字符串等属性。
2、利用ClassWizard为视图类增加命令消息(COMMAND)控制函数和更新命令UI消息(UPDATE_COMMAND_UI)控制函数。每一菜单项都有一个ID,即命令ID,只有当该ID在0x8000-0xDFFF范围内,才能够发送COMMAND消息。当我们选中某一菜单项时,将会执行相应的命令消息控制函数;每当弹出式菜单第一次显示时,都会调用更新命令用户界面(UI)控制函数。更新命令UI控制函数可以用来对菜单项的显示进行修改,但是注意只适用于弹出式菜单的菜单项,而对长久显示的顶层菜单项则不适用。
MFC中,应用程序框架提供了一个命令消息传递系统。当应用程序框架接受到框架窗口命令时,它将按如下次序来寻找相应的消息控制函数:
SDI应用程序:视图-->文档-->SDI主框架窗口-->应用程序
MDI应用程序:视图-->文档-->MDI子框架窗口-->MDI主框架窗口-->应用程序
所以,我们几乎在程序中的任何地方对消息进行控制。如果某菜单项在当前命令传递路径中无法找到相应的命令消息控制函数,则此时应用程序框架就可以禁用该菜单项。但如果我们将CFrameWnd的数据成员m_bAutoMenuEnable置成了FALSE,那么我们可以禁用这一特性。
3、当主框架窗口的Create()或LoadFrame()被调用时,菜单的资源都被直接连到了框架窗口中,因此,我们不必另外专门创建CMenu对象。我们可以通过CWnd的GetMenu()来返回一个CMenu对象的指针,通过该指针来对菜单对象进行访问和更新。
我们也可以动态创建菜单,方法如下:
{
CMenu menu;
menu.LoadMenu(IDR_MYMENU)//菜单的ID;将菜单从资源中装入;
menu.SetMenu();//将菜单连到框架窗口中;
......
menu.Detach();//将menu对象与菜单独立出来,这样menu对象生存期
//结束后该菜单依然在内存中。
}
应用二:创建浮动的弹出式菜单
1、利用资源编辑器来编辑菜单。
2、利用ClassWizard编辑菜单项的命令控制函数。
3、在视图类中编辑WM_CONTEXTMENU消息控制函数(当鼠标右键弹起时发送此消息)。
void CMyView::OnContextMenu(CWnd *pWnd,CPoint point)
{
CMenu menu;
menu.LoadMenu(IDR_MYMENU);
menu.GetSubMenu(0)->TrackPopupMenu
(TPM_LEFTALLGN|TPM_RIGHTBUTTON,point.x,point.y,this);
//显示浮动弹出菜单;
}
MFC中的文档和视图的关系
文档对象是用来保存程序运行过程中所使用的数据的,视图对象是用来显示数据的,并且允许用户对数据进行编辑,编辑的结果应该再存储在文档对象中。
1.应用程序类:
WINDOWS应用程序的初始化、运行和结束都是由应用程序类完成的。应用程序类构成了应用程序执行的主线程。每个MFC程序有且仅有一个从CwinApp类派生的类对象。如CsampleApp theApp。
2.文档模板:
应用程序对象使用文档模板存放与应用程序文档、视图和边框窗口有关的信息。应用程序使用文档模板创建文档类对象来存放文档,创建边框窗口类对象来画出视图窗口,创建视图类对象来显示文档,多个视图类对象可以共享一个边框窗口类对象。文档类,视图类和边框窗口类之间的关系由文档模板管理。
在应用程序类的InitInstance函数中创建并注册文档模板:
BOOL CsampleApp::InitInstance()
{
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CsampleDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CsampleView));
AddDocTemplate(pDocTemplate);
return TRUE;
}
传给CsingleDocTemplate构造函数的第一个参数是资源符号IDR_MAINFRAME,该资源包含边框的图标和选单等,传给CsingleDocTemplate构造函数的其余三个参数是对RUNTIME_CLASS宏的调用,每次调用都返回指定类的信息,从而使应用程序可以动态的创建该类的一个实例,即返回一个指向参数类的对象的指针。AddDocTemplate注册文档模板对象,将文档模板存放在应用程序对象中。
3.文档和视图的关系:
在创建MFC的应用程序的框架时,AppWizard为应用程序创建文档和视图类的基本结构。由AppWizard创建的文档类是从CDocument 类派生的,视图类是从CView类派生的。CDocument类为应用程序定义的文档类提供基本功能,而CView类为应用程序定义的视图类提供基本功能。视图是与文档联系在一起的,在文档与用户之间起中介作用。视图在屏幕上显示文档数据并把用户输入转换成对文档的操作。MFC中文档与视图的这种实现方法把数据与数据的显示以及用户对数据所作的操作分离开来。数据的所有更改都通过文档类管理,而视图则调用这个接口来访问和更新数据。视图类对象可以通过CsampleDoc* CsampleView::GetDocument() 函数得到其所属的文档对象的指针,以对文档的数据进行操作。
文档用于管理应用程序的数据,使用方法如下:
(1) 从CDocument类派生出各种不同类型的文档类,每种类型对应一种文档。
(2) 添加用于存储文档数据的成员变量。
(3) 根据需要重载CDocument类的其他成员函数。
视图以图形方式显示文档数据、接受用户输入并将其解释成对文档的操作:
(1) 处理视图类的OnDraw成员函数,该函数负责提供文档数据。调用函数GetDocument()得到文档数据。
(2) 映射并实现消息处理函数,以便解释用户的输入。
(3) 根据需要重载CView类的其他成员函数。如:重载OnInitialUpdate,以便进行必要的视图初始化工作,重载OnUpdate,以便在视图即将重新绘制前进行必要的处理。
应用程序可以是单文档或多文档,每个文档可以有单个或多个视图,但一个视图只隶属于一个文档。最简单的应用是单文档单视图的,MFC也支持单文档多视图。每个文档对象保存有该文档的视图列表,并提供用于添加和删除视图的成员函数,以及在文档数据发生变化时提供UpdateAllView成员函数来更新所有视图。单文档程序中主边框窗口和文档边框窗口重合,合二为一。多文档程序中主边框窗口中有客户窗口,客户窗口中又包含多个文档边框窗口。
MFC支持以下三种多视图模式:
(1) 同一文档的多个视图对象,每个对象置于独立的文档边框窗口中。
(2) 同一文档边框窗口中有同一文档的多个视图对象。如分割窗口。
(3) 单个文档边框窗口中有不同类的视图对象,多个视图共享单个边框窗口,每个视图从不同的类构造。
4.MFC中各对象之间的关系:
(1) 文档中含有该文档的视图列表和指向创建该文档的文档模板的指针。
(2) 视图中含有指向文档的指针,视图窗口是文档边框窗口的子窗口。
(3) 文档边框窗口含有指向当前活动视图的指针。
(4) 文档模板含有已打开文档的一个列表。
(5) 应用程序对象含有文档模板的一个列表
(6) Windows跟踪所有已打开的窗口并发送消息给这些窗口。
通过调用AfxGetApp,任何对象都可以获得指向应用程序对象的指针。
各对象之间的访问方法:
文档:调用GetFirstViewPosition和GetNextView函数访问文档的视图列表。调用GetDocTemplate函数获取创建该文档的文档模板的指针。
视图:调用GetDocument函数获取指向文档的指针。调用GetParentFrame函数获取指向文档边框窗口的指针。
文档边框窗口:调用GetActiveView函数获取当前视图。调用GetActiveDocument函数获取当前视图的文档。
MDI主边框窗口:调用MDIGetActive函数获取当前活动的MDI子窗口。