翻译自Using views effectively without the doc/view overhead From TheCodeProject,By Antti Keskinen。
原文在这里。
去除文档/视图结构,有效使用视图
下载示例工程源码 – 62.3Kb
介绍
大家好,这是我在CodeProject的第一篇文章。我尽可能的使文章表达清楚和专业,但任可能存在一些笔误或明显的bug。如果你碰巧发现了什么问题,欢迎联系我或者在评论栏留言。
正如标题所言,本文讨论在没有文档/视图结构支持情况下如何使用不同的类型的视图。概括起来说,我们将创建一个标准的框架窗口程序,并在其中使用定制的视图类型。为了增加一点额外难度,我们使用的视图将继承自CFormView。
背景
尝试一下你就会知道,CFormView或类似视图类型用起来多么让人抓狂。它们的构造函数和Create成员函数被封装为私有的。更糟糕的是,除了更改MFC源代码,没有其它办法可以改变这一点。真是太不方便了……
前一段时间我做了一个项目,最初使用普通的基于对话框的解决方案。随着项目的进行,我发现需要给程序增加一个工具条和菜单。我试了一下,发现要在对话框上完成这些工作相当困难,于是我采用了另一种方法:把对话框改成继承自CFormView的。经过又几个小时的调试之后,代码终于调试通过了。但当运行程序时,我真JB傻眼了,Assert失败、访问违规、未处理异常……你说什么就有什么。
让MFC Framework完成困难的工作
闲话少叙。首先,你需要一个标准的MFC单文档程序。除了去掉文档/视图结构支持,其余的使用向导默认设置就OK了。启动你的Visual Studio马上创建一个吧。注意我将以Visual Studio为例进行说明,如果你使用不同的IDE,请参考IDE的帮助文档完成我们的以下步骤。
程序的骨架已经建立好了,现在去掉自动生成的CChildView的相关代码,因为我们用不上这些。然后,创建一个窗体视图(对话框)并放置一两个控件在上面,什么控件都行。Visual Studio 5.0及以后的版本都提供了一些窗体模板,插入新资源时点击“对话框”旁边的加号你就能看见。接着,为刚创建的对话框生成一个新的类,比如CMainView,并指定CFormView为它的基类。当然,你可以使用任何你认为合适的类名,但要记住同时修改我提供的范例代码使之与你选择的类名一致。
打开新生成的类,将它的构造函数修改为public类型:
class CMainView : public CFormView
{
DECLARE_DYNCREATE(CMainView)
// Change constructor to public. It is private by default
public:
CMainView();
virtual ~CMainView();
...
然后,定位到主框架类所在的头文件(默认为MainFrm.h)并逐一替换原来子视图CChildView。include CMainView所需的头文件,为主框架类添加一个CMainView* m_pMainView成员,并注释掉原有的代表子视图的成员变量。记住,向导产生的代码在OnSetFocus和OnCmdMsg函数中用到了这个变量,你必须修改主框架类的.cpp文件用m_pMainView来替换。
现在,打主框架类的开实现文件(MainFrm.cpp)并定位到OnCreate函数。这个函数将在创建框架时被调用,让我们马上修改它以完成其它一些工作。这是修改后的代码:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
// First, build the view context structure
CCreateContext ccx;
// Designate the class from which to build the view
ccx.m_pNewViewClass = RUNTIME_CLASS(CMainView);
在上面的代码中,我们创建了一个新的上下文结构CCreateContext ccx。此结构用于标明这个上下文使用的视图和文档类型。当注册文档模板时,Framework会自动生成这些并为你生成所需的视图。创建ccx以后,我们指定CMainView为所需要的视图类型。你可以在这儿使用任何从CView继承下来的类,只要它支持动态创建。如果你使用类向导来生成继承自CView或其衍生类的新类,则动态创建所需的一切都已经为你准备好了。
// Using the structure, create a view
m_pMainView = DYNAMIC_DOWNCAST( CMainView, this->CreateView(&ccx) );
// Did we succeed ?
if ( !m_pMainView )
{
TRACE0("Creation of view failed\n");
}
等等,CreateView是什么调用?DYNAMIC_DOWNCAST又是什么飞机?冷静,深呼吸。实际上,Framework使用CreateView为框架窗口创建视图。我们所做的只是要求框架窗口以新创建的CCreateContext为参数调用它的CreateView成员函数。一切顺利的话,CreateView返回一个CWnd基类指针指向一个新建视图,否则它返回NULL。我们使用DYNAMIC_DOWNCAST将CWnd基类指针cast为CMainView类。毕竟,CView继承自CWnd,而CFromView间接继承自CView。这个转换是合法、没问题的。另外,你可以填写CreateView的第二个参数为视图指定ID。如果不指定的话,CreateView将使用默认的AFX_IDW_PANE_FIRST作为视图的ID。
好吧,如果你不太熟悉嵌套的函数调用,这是另一种写法:
// Creating the child view step-by-step
// First order the frame window to create a new view for us
CWnd* pNewView = this->CreateView(&ccx);
// Cast down from the returned pointer
m_pMainView = DYNAMIC_DOWNCAST( CMainView, pNewView );
// Succesfull ?
if ( !m_pMainView )
{
TRACE0("Creation of view failed\n");
}
OK,让我们继续。现在我们的视图已经创建好并关联到框架窗口了。但是,如果现在就让视图可见的话,要是框架窗口还使用了工具条、状态栏或者ReBar等控件,它们可能得不到正确的显示。因此我们要求框架窗口重新布置一下它的子控件:
// Do layout recalc
RecalcLayout();
接下来要做的就是显示并激活视图了,如下面的代码片断所示①:
// Show the view and do an initial update
m_pMainView->ShowWindow(SW_SHOW);
m_pMainView->OnInitialUpdate();
// Set this view active
SetActiveView(m_pMainView);
如果就这样运行程序的话,窗口不会显得好看,因为框架窗口还没有根据视图窗体的大小自动调整。我们的视图类提供一个函数,它可以命令框架窗口调整客户区域容纳视图本身。客户区域大小调整了,整个窗体的大小也就自然调整了。
// Order it to resize the parent window's client area to fit
m_pMainView->ResizeParentToFit(FALSE);
// Done
return 0;
}
就是这些,现在你可以生成你的程序了。你会看到一个漂亮的窗体,而不是一片枯燥的、光秃秃的背景。
结论
本文只涉及对MFC Framework的初步探讨。现在,你可能想修改窗体视图的背景让它变得更个性化一些,或者你希望去掉一些标志啊,设置啊什么的让它看起来更平滑(最初的窗体视图看起来像是凹陷在框架窗口里)些。记住,文中提到的框架窗口可以是任意支持视图封装的窗口,如CSplitterWnd,只是调用约定或有些微差异。或许我会在另一篇文章讨论如何处理这一问题。
许可
本文和所有相关代码、文件遵循The Code Project Open License(CPOL)。
关于作者
Antti Keskinen
译者注:
① 根据原文后的评论,视窗的OnInitialUpdate会被Framework自动调用,因此这里应将 m_pMainView->OnInitialUpdate()去掉。后来我用VS6.0做了一个测试,使用的视图类继承自CTreeView,如果不注释掉这一句,Debug版本初始化时会出现一个Assertion Failure,Release版本能正常运行;注释掉以后,Debug和Relase版本都能正常运行。