为WTL设计一个文档视图模型.docx
- 文档编号:1324201
- 上传时间:2023-04-30
- 格式:DOCX
- 页数:31
- 大小:51.16KB
为WTL设计一个文档视图模型.docx
《为WTL设计一个文档视图模型.docx》由会员分享,可在线阅读,更多相关《为WTL设计一个文档视图模型.docx(31页珍藏版)》请在冰点文库上搜索。
为WTL设计一个文档视图模型
为WTL设计一个“文档-视图”模型
作者:
戴霖bill.dai@
前言2
1-编译期虚函数调用&嵌入类3
2-文档类4
3-视图类5
4-文档模板类6
5-文档模板管理器类6
6-上下文信息——CCreateContext6
7-改造你的代码7
8-模型的工作流程10
9-实现代码11
10-后记22
前言
WTL(WindowsTemplateLibrary,Windows模板库)提供了比MFC库更加精减的代码框架,这其中就省略了对MFC“文档-视图”模型的支持。
而“文档-视图”模型的核心思想——将数据和其表示分开,在开发大型软件时颇为有用。
那如何既能保留WTL精减方便的优点,又能拥有MFC“文档-视图”模型的精髓呢?
本文将探讨一种方法,在为WTL引入“文档-视图”模型的同时,让其接口尽可能地接近于MFC,从而方便实际应用。
我们将构建四个主要的类来实现“文档-视图”模型的行为,分别是文档类、视图类、文档模板类和文档模板管理器类。
四部分的关系如下图:
记录并管理一个或多个文档模板
由于WTL是构建在ATL基础之上的,所以其代码风格迥异于MFC,包含了大量的C++模板类代码。
即使你能毫不费力地读懂它们,但是下面的代码仍然可能让你一头雾水:
ClassCMyWin:
publicCWindowImpl
{
……
};
这样做是合法的,虽然CMyWin类只是被部分定义,但是类名CMyWin已经被列入了C++的递归继承列表,是可以使用的。
可是为什么要这样做呢?
目的何在?
这里蕴含了两个概念,一是编译期间的虚函数调用机制,二是嵌入类。
1-编译期虚函数调用&嵌入类
如果你想了解它们如何工作,请看下面的例子;如果你已经有了很好的ATL基础,那么可以直接跳过本章。
例子:
template
classBase
{
protected:
voidSayHello()
{
cout<<"HelloBase";
}
public:
voidPrint()
{
T*ptr=static_cast
ptr->SayHello();
}
};
classDeriver1:
publicBase
{/*空类*/};
classDeriver2:
publicBase
{
protected:
voidSayHello()
{
cout<<"HelloDeriver2";
}
};
main()
{
Deriver1d1;
Deriver2d2;
d1.Print();//显示HelloBase
d2.Print();//显示HelloDeriver2
}
这句代码static_cast
它在函数调用时将指向Base类型的指针this转换为指向派生类Deriver1或Deriver2的指针。
因为模板代码是在编译期间生成的,所以只要编译器生成正确的继承列表,这种转换就是安全的。
因为在这个例子里this只能是指向Deriver1或Deriver2类型的对象,不会是其他的东西。
这很像C++的多态性,只是Print()方法不是虚函数。
要解释这是如何工作的,首先看看对每个Print()函数的调用,在d1.Print()语句里,对象Base被指派为Deriver1,所以代码被解释成:
voidBase
:
Print()
{
Deriver1*ptr=static_cast
ptr->SayHello();
}
由于Deriver1没有重载SayHello(),所以查看基类Base,Base有SayHello(),所以Base的SayHello()被调用。
再看d2.Print()语句,这一次对象被指派为Deriver2类型,Print()被解释成:
voidBase
:
Print()
{
Deriver2*ptr=static_cast
ptr->SayHello();
}
这一次,Deriver2含有SayHello()方法,所以Deriver2的SayHello()方法被调用。
这种技术的好处在于:
∙不需要使用指向对象的指针。
∙不需要虚函数表,节省内存。
∙因为没有虚函数表,所以运行时不会发生调用了空指针指向的虚函数的错误。
∙所有的函数调用在编译时确定,而非C++虚函数机制使用的动态联编,这有利于编译程序对代码的优化。
读到这里你应该可以明白什么是编译期间的虚函数调用机制,那么嵌入类又是什么呢?
其实这是ATL引入的一个概念,或者叫作“另一种继承机制”更合适。
上例中,编译期的虚函数调用机制能够让Base类安全地操纵其派生类的指针,就好像Base类被直接“嵌入”到了其派生类中。
而在传统的继承方法中,只能够在派生类中去操纵基类。
所以,我们不妨将嵌入类理解成WTL/ATL的继承机制吧。
明白了这样写代码的原因和工作原理之后,可以接着往下看了。
2-文档类
文档是用来保存数据以及关于数据的处理的,每当SDI/MDI应用程序响应File(Open)/File(New)命令的时候都会打开文档。
一个文档可以拥有多个视图。
文档和视图的关系可以这样理解:
文档是被视图观察的对象。
在数据发生改变时,文档负责通知所有与其关联的视图更新其显示。
为实现该目的,文档需要维护一份包含所有关联视图的引用列表,这通常用指针数组来实现。
在MFC中,你的所有文档类都继承自CDocument基类。
我们模仿这一概念,根据WTL继承机制将构造一个嵌入类CDocument,它是你所有文档类的基类。
我们还将构建一个CDocument的基类CDocumentBase,它是一个非模板类,而且是一个空类。
为什么呢?
因为CDocument
只需建立一个CDocumentBase类型的指针,它可以指向任何一个文档类,是不是方便了?
classCDocumentBase;
{
};
template
classCDocument:
publicCDocumentBase
{
……
};
3-视图类
视图在Windows中就是一个窗口(模型中的视图需要依附在一个框架上才能显示为我们看到的窗口),是用来表示文档的数据的。
每个视图都对应于一个特定的文档,它拥有一个指向其关联文档的指针以便操作数据对象,并且在数据改动后通知其它与同一文档相关联的视图更新其显示。
我们同样根据WTL继承机制将视图类设计成嵌入类。
//视图的基类,请让你的视图类TUser_View继承它
template
classCView:
publicTDoc_View
{
……
}
TDoc_View是什么?
它就是“文档-视图”模型中的“视图”。
你的视图类继承它以后,也具备了与一个文档类关联的属性。
TDoc_View定义为CViewBase
构建CViewBase类的其中一个目的与CDocumentBase相同,不过它可不是一个空类,它是所有视图的抽象基类。
由于一个视图是对应于一个特定文档的,所以其模板参数必然是文档类。
//所有视图的抽象基类
template
classCViewBase
{
……
}
此外,CViewBase还将负责记录当前处于活动状态的视图。
4-文档模板类
文档模板类是整个模型的核心部分,不过它非常适合用C++类模板来编程实现(MFC中使用了CRuntimeClass类型,繁琐很多)。
文档模板的职责是创建一个文档及其对应的视图(包括视图依附的子窗口窗口)。
它必须拥有文档、视图和框架这三个对象的信息。
一个文档模板可创建多个文档。
每一个文档模板对象由文档模板管理器负责记录并管理。
另外,模仿MFC中的概念,文档模板类还应该持有特定文档所对应的资源ID(菜单、工具栏和其它资源)。
所以,文档模板类是一个拥有4个模板参数的类模板:
//所有文档模板的抽象基类
classCDocTemplateBase
{
……
};
//文档模板类
template
classCDocTemplate:
publicCDocTemplateBase
{
……
}
5-文档模板管理器类
在MFC中,文档模板管理器的功能是在CWinApp类中提供的,而WTL没有该类。
怎么办呢?
我们决定在MainFrame中提供该功能。
为什么呢?
因为MainFrame位于窗口体系的顶层,且每一个应用程序只有一个MainFrame。
当然,如果你正在做一个SDI应用程序,那么文档模板管理器就是不必要的。
文档模板管理器的职责是记录并管理应用程序的所有文档模板,这通常用指针数组来实现。
由于我们将在MainFrame中提供该功能,所以CDocManager文档模板管理器将被设计成嵌入类(嵌入到MainFrame中去),其模板参数为MainFrame。
template
classCDocManager
{
……
}
6-上下文信息——CCreateContext
上下文信息是一个struct,在创建文档的过程中负责记录该文档及其所对应的文档模板和视图的相关信息,与MFC中的功能一样,那就仿着MFC中的写吧:
template
structCCreateContext
{
TDoc*m_pCurrentDoc;
TView*m_pCurrentView;
CDocTemplateBase*m_pNewDocTemplate;
……
}
7-改造你的代码
假设你已经(我们把SDI看成是MDI的特殊情况),并且你的程序只有1个文档模板(多文档模板只需增加类似代码即可)。
下面以我最近写的一个“语法高亮显示编辑器”为例,介绍代码改造过程。
该编辑器由WTL7.5的向导生成了一个MDI应用程序框架,默认视图选用了RichEdit。
最初包含下面4个类:
CAboutDlg,CChildFrame,CMainFrame和CSHLEditorView。
首先,为程序增加一个文档类——CSHLEditDoc,作如下声明:
classCSHLEditorDoc
:
publicCDocument
{
public:
CSHLEditorDoc(void);
~CSHLEditorDoc(void);
protected:
CStringm_data;
//必须实现的方法,CDocument需要调用它们
public:
//创建新文档时调用,返回FALSE表示失败
BOOLOnNewDocument();
//保存文档时调用,返回FALSE表示失败
BOOLOnSaveDocument(LPCTSTRlpszPathName);
//打开文档时调用,返回FALSE表示失败
BOOLOnOpenDocument(LPCTSTRlpszPathName);
};
//这就是前面提过的TDoc_View
typedefCViewBase
接着修改视图类CSHLEditorView。
改变如下(注意深灰色背景的文字,那就是你要添加或改变的部分):
typedefCWindowImpl
typedefCView
classCSHLEditorView:
publicCRichEditView,
publicCViewModel
{
public:
DECLARE_WND_SUPERCLASS(NULL,RichEditCtrl:
:
GetWndClassName())
BOOLPreTranslateMessage(MSG*pMsg);
BEGIN_MSG_MAP(CSHLEditorView)
MSG_WM_CREATE(OnCreate)
……
CHAIN_MSG_MAP(CViewModel)
END_MSG_MAP()
//必须实现的方法
virtualvoidOnFinalMessage(HWNDhWnd);
//对应的文档类内容发生改变时调用(如:
读取了某个文件后更新显示)
voidOnUpdate(CSHLEditorDocView*pSender,LPARAMHint,LPVOIDpHint);
public:
HWNDCreate(HWNDhWndParent,CCreateContext
……
}
HWNDCSHLEditorView:
:
Create(HWNDhWndParent,CCreateContext
{
//设置与该视图关联的文档
SetDocument(pContext->m_pCurrentDoc);
//将该视图添加到关联文档的视图列表里
pContext->m_pCurrentDoc->AddView(this);
returnCWindowImpl
:
Create(hWndParent,NULL,0,WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_HSCROLL|WS_VSCROLL|ES_AUTOHSCROLL|ES_AUTOVSCROLL|ES_MULTILINE|ES_NOHIDESEL|ES_SAVESEL,WS_EX_CLIENTEDGE,(UINT)0,pContext);
}
voidCSHLEditorView:
:
OnFinalMessage(HWNDhWnd)
{
m_pDocument->RemoveView(this);
return;
}
voidCSHLEditorView:
:
OnUpdate(CSHLEditorDocView*pSender,LPARAMHint,LPVOIDpHint)
{
……
return;
}
视图类改造完成后,接下来是框架类。
先看CChildFrame:
LRESULTCChildFrame:
:
OnCreate(UINT/*uMsg*/,WPARAM/*wParam*/,LPARAMlParam,BOOL&bHandled)
{
LPCREATESTRUCTlpcs=(LPCREATESTRUCT)lParam;
LPMDICREATESTRUCTlpms=
(LPMDICREATESTRUCT)lpcs->lpCreateParams;
CCreateContext
(CCreateContext
_ASSERTE(pContext->m_pCurrentView==NULL);
pContext->m_pCurrentView=&m_view;
m_hWndClient=m_view.Create(m_hWnd,pContext);
bHandled=FALSE;
return0;
}
然后再把你的MainFrame改变如下:
ClassCMainFrame:
publicCMDIFrameWindowImpl
publicCUpdateUI
publicCMessageFilter,publicCIdleHandler,
publicCDocManager
{
public:
CDocTemplate
……
}
LRESULTCMainFrame:
:
OnCreate(……)
{
……
AddDocTemplate(&m_DocTemplate);
return0;
}
做好了如上改变后,下面我们开始分析一下模型的工作流程。
8-模型的工作流程
模型的工作起点通常位于MainFrame的OnFileNew(……)消息响应函数中,那里有默认的新建子窗口的过程:
CChildFrame*pChild=newCChildFrame;
pChild->CreateEx(m_hWndClient);
将它们改为:
//创建一个新文档
GetDocTemplate(0)->CreateNewDocument();
该语句将调用第一个文档模板来创建一个新的文档。
我们来看一下该方法:
TDoc*CreateNewDocument()
{
TDoc*pDoc=newTDoc;
AddDocument(pDoc);
TFrame*pFrame=CreateNewFrame(pDoc);
if(!
pDoc->OnNewDocument())//如果创建新文档不成功,则销毁框架窗口
{
pFrame->DestroyWindow();
RemoveDocument(pDoc);
deletepDoc;
returnNULL;
}
returnpDoc;
}
先创建一个新文档对象,由文档模板把它记录下来,然后创建视图(包括视图依附的子窗口),接着调用该文档对象的OnNewDocument()方法,去完成整个文档的创建并显示出来。
如果创建失败了则做一些收尾工作,否则返回新建的文档对象指针。
创建视图时调用了CreateNewFrame()方法,我们也来看一下:
TFrame*CreateNewFrame(TDoc*pDoc)
{
CCreateContext
context.m_pCurrentDoc=pDoc;
context.m_pNewDocTemplate=this;
TFrame:
:
GetWndClassInfo().m_uCommonResourceID=nID;
TFrame*pFrame=newTFrame;
pFrame->CreateEx(m_hWndClient,NULL,NULL,0,0,&context);
returnpFrame;
}
首先,我们用一个上下文结构记录当前的创建过程——保存当前创建的文档及其对应的文档模板。
接着将资源ID分配给将要创建的子窗口。
然后该上下文结构被传递给子窗口的创建函数。
现在回头去看CChildFrame的OnCreate()消息响应函数,前3条语句先取出该上下文结构,再用它保存将要创建的视图——于是,该上下文结构便保存了一份完整的“文档-视图”映射关系。
接下来它又被传递给视图类CSHLEditView的Create方法,Create方法依次设置了“视图的关联文档”和“文档的关联视图”,之后再创建CSHLEditView视图并显示出来。
至此,一个完整的“文档-视图”模型便建立了起来。
9-实现代码
以下是该方案的完整实现代码,你可以将它保存到一个C++头文件中(我给它命名为“Doc-ViewModelforWTL.h”),然后在你的“stdafx.h”文件中包含它(建议将它置为最后一个包含文件)。
#pragmaonce
#defineHINT_UPDATE_ALL_DOCUMENTS-1
#defineHINT_DOCUMENT_MODIFIED-2
//超前声明:
所有文档模板的基类
classCDocTemplateBase;
//上下文信息
template
structCCreateContext
{
TDoc*m_pCurrentDoc;
TView*m_pCurrentView;
CDocTemplateBase*m_pNewDocTemplate;
CCreateContext()
{
memset(this,0,sizeof(*this));
}
//用于“单文档-多视图”模型的构造函数
template
CCreateContext(CCreateContext
{
m_pCurrentDoc=pContext->m_pCurrentDoc;
m_pNewDocTemplate=pContext->m_pNewDocTemplate;
}
};
//所有视图的抽象基类
template
classCViewBase
{
public:
staticCViewBase
static_cast
protected:
TUser_Doc*m_pDocument;//视图所对应的文档指针
staticCViewBase
public:
virtualvoidU
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- WTL 设计 一个 文档 视图 模型