第八章:Document----View深入探讨
MFC之所以为应用程序框架,最重要的特征就是他能够将管理数据的程序代码和负责数据显示的程序代码分离开来。这种能力由文档视图结构提供。Document在MFC的CDocument中被实例化,它本身就是一个空壳,当开发自己的程序时,应该从CDocument派生出一个属于自己的CMyDocument类,并且在类中声明一些成员变量,用以容纳数据。然后再改写Serialize函数,CDocument派生自CObject类,所以它具有CObject所支持的一切性质,包括运行时类型识别,动态创建,文件读写,又由于它派生自CCmdTarget,所以它可以接受来自菜单或工具栏的WM_COMMAND消息。
CView负责数据的显示。CView本身并无用途,它也是提供了一个空壳,当开发程序时,应该从CView派生出一个属于自己的CMyView类,并且在类中改写负责数据显示的OnDraw函数。由于CView派生自CWnd,因此它可以接受一般的Windows消息,又由于它派生自CCmdTarget,所以它可以接收来自菜单或工具栏的WM_COMMAND消息。
View是一个没有边框的窗口,其外围还有一个Frame窗口。
CDocTemplate负责管理Document、View和Frame。它有两个派生类,分别为:CMultiDocTemplate,CSingleDocTemplate。如果程序能够处理两种以上的数据,就必须构造两个Document Template出来,然后调用CWinApp::AddDocTemplate将它们加入到系统之中。这和程序是不是MDI没有关系。如果程序支持多种数据类型,但是是SDI,那只是每次只能打开一个文件罢了,MDI可以同时打开多个。
CDocTemplate用以管理View,document和Frame。而CWinApp管理CDocTemplate。在CWinApp::InitInstance中构建CDocTemplate:
Bool CMyWinApp::InitInstance()
{
.....
CMultiDocTemplate *pDocTemplate=new CMultiDocTemplate(
IDR_SCRIBYTEP,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CChildFrame),
RUNTIME_CLASS(CMyView);
AddDocTemplate(pDocTemplate);
}
注意构造CView对象和产生View窗口之间的区别。CView类用于管理View窗口,它内部含有要产生的View窗口的句柄,因此必须首先构造CView对象,在调用该类的成员函数如Create函数产生窗口。
由于在构建CDocTemplate时,传递的是document,View和Frame的CRuntimeClass结构,在CDocTemplate内部,会构建调用各自类的构建函数构建这三个对象。这是在CMultiDocTemplate::OpenDocumentFile内实现的。
当使用者单击File/New,CWinApp::OnFileNew函数被调用,然后它又调用CDocManager::OnFileNew,后者又调用CMultiDocTemplate::OpenDocumentFile.此函数调用CreateNewDocument动态创建Document,调用CreateNewFrame动态产生Document Frame。然而View的产生就没有这么显而易见了,首先Frame的创建发出WM_CREATE消息,是CFrameWnd::OnCreate被调用,经过一些列调用,在最后调用的CFrameWnd::CreateView内,View对象以及windows窗口都被创建出来。
CWinApp又有一个指向CDocManager的指针,它指向CDoManager类型的对象。在CDocManager类中,有一个指针链表,用来维护一系列的Document Template,如果一个程序支持两种以上的文件类型,那么就应该调用AddDocTemplate将Document Template加入到链表中。
CDocTemplate拥有三个成员变量,分别为指向document ,view和Frame的CRuntimeClass指针,另一个成员为m_nIDResource。用来表示此Document显现时应该使用的UI对象。这四个成员应该在
CMyWinApp::InitInstance中构造CDocTemplate时指定。然后CDocTemplate可以借助这些参数,动态构建Document,view和Frame。如果是单文档程序,Document Template就是CSingleDocTemplate,此时它有一个m_pOnlyDoc成员变量,也就是说它只能打开一份文档。如果为多文档应用程序,Document Template使用的就是CMultiDocTemplate,它有一个成员变量m_docList,此变量为CPtrList类型,指向一系列打开的文档。
CDocument有一个成员变量CDocTemplate,指向它所属的Document Template。另一个成员变量m_viewList指向它维护的一系列view。
CFrameWnd有一个成员m_pViewActive指向当前活动的View。
CView有一个成员变量,m_pDocument指向相关的Document。
类似于C++中STL中的顺序容器vector和List,MFC也提供了自己的容器类。它们可以分为List,Array ,Map;
CArray ,CList ,CMap为模板类,使用时需要指定元素类型和函数的参数类型。
如 CArray<TYPE,ARG_TYPE> a;
CList <TYPE ,ARG_TYPE> b;
CMap<KEY,ARG_KEY,VALUE,ARG_VALUE> c;
ARG_TYPE则用来指定函数的参数类型。
类型指针使用时仍然需要指定参数:
CTypedPtrArray<BASE_CLASS,TYPE> a;
CTypedPtrList<BASE_CLASS,TYPE>b;
CTypedMap<BASE_CLASS,KEY,VLAUE>c;
TYPE用以指定元素的类型。
BASE_CLASS则用来指定基类的类型。它可以是任何非模板的容器。如CObList0.,CPtrList,CPtrArray。
MFC容器类支持的对象中,有两种需要特别说明:
一是Ob,表示派生自CObject的任何对象。MFC提供CObList,CObArray两种类。
二是Ptr,表示对象指针。MFC提供CPtrList,CPtrArray两种类。
绝大部分的MFC类,以及自己写的类都要从CObject派生下来,因为当一个类派生自CObject时,它也就继承了许多重要的性质,它至少提供两个机能:IsKindOf和IsSerialzable.
IsKindOf它是判断一个对象是不是某个类。它是依赖于CRuntimeClass。
bool CObject::IsKindOf(const CRuntimeClass *pClass)const
{
CRuntimeClass *pClassThis=GetRuntimeClass();
return pClassThis->IsDerivedFrom(pClass);
}
bool CRuntimeClass::IsDerived(cosnt CRuntimeClass*pBaseClass)const
{
const CRuntimeClass *pClassThis=this;
whiel(pClassThis)
{
if(pClassThis==pBaseClass)
return true;
pClassThis=pClassThis->pBaseClass;
}
return false;
}
IsSerializable函数用以判断一个类是否可以Serialize,这主要通过判断CRuntimeClass的schema成员是否为0xFFFF.
bool CObject ::IsSerialize()const
{
return (GetRuntimeClass()->m_wSchema!=0xffff);
}
CObject有一个虚函数Serialize,每一个希望具备serialize功能的类都需要重写这个函数。
DYNCREATE/DYNCREATE/SERIAL分别在CRuntimeClass所组成的类型型录中填写不同的记录。它们分别对应三个不同等级。
DYNAMIC 仅具备动态类型识别。
DYNCREATE包括DYNAMIC同时还具备动态创建。
SERIAL包括DYNCREATE,还包括Serialize.
CArchive类管理文件缓冲区。它是Serialize的对象。CArchive针对许多C++数据类型,windows数据类型以及CObject派生类定义了operator<<和operator>>重载运算符。正因为如此,可以将各种类型的数据读出或写入CArchive。一个自定义的C++类如果想要有Serialization机制,就得直接或间接派生自CObject。为的是从CObject派生下列三个运算符:
_AFX_INLINE CArchive &AFXAPI operator<<(CArchive&ar,const CObject*pOb);
_AFX_INLINE CArchive &AFXAPI operator>>(CArchive&ar,CObject*&2pOb);
_AFX_INLINE CArchive &AFXAPI operator>>(CArchive&ar,const CObject*&pOb);
一个类如果希望有Serialization机制,它的第二要件就是使用SERIAL宏。
这个宏包含DYNCRETE宏,比DYNCREATE多了个:
friend CArchive &AFXAPI operator>>
(CArchive&ar,class_name*&pOb);
在定义处增添了:
CArchive &AFXAPI operator>>(CArchive&ar,class_name*&pOb)/
{/
pOb=(class_name*)ar.ReadObject(RUNTIME_CLASS(class_name));/
return ar;/
}
此处只改写了CObject::operator>>而没有改写operator<<,原因是WriteObject并不需要CRuntimeClass信息。但ReadObject需要,因为在读完文件之后还要进行动态创建操作。
前面就提到过CObject会抽象类且它没有基类,因此它需要特殊处理。
当多个视图显示同一个文档,为了保持各个视图操作的文档内容的一致性,需要以消息通知使用同一份文档的其他视图,CView中有三个虚函数:
1:CView::OnInitialUpdate:负责View的初始化。
2:CView::OnUpdate,当FrameWork调用此函数时,表示Document的内容已经发生了变化。
3:CView::OnDraw:在WM_PAINT消息时会调用此函数,此函数负责更新View窗口的内容。
让所有的View窗口同步更新数据的关键在于两个函数:
1:CDocument::UpdateAllViews,它会遍历使用这个文档的各个视图,逐个调用它们的OnUpdate函数。
2:CView::OnUpdate,这是个虚函数,可以改写。它的作用就是告诉View,document的内容已经改变,你需要更新了。
具体步骤为:
1:在CView中调用GetDocument获得CDocument指针。
2:在CView中调用CDocument::OnUpdateAllViews;
3:所有使用这一份Document的view都被调用OnUpdate。