buf

BE something YOU love and understand
posts - 94, comments - 35, trackbacks - 0, articles - 2
   :: 首页 :: 新随笔 :: 联系 ::  :: 管理

Custom SDI 2 —— 替换CChildView

Posted on 2008-04-14 22:31 buf 阅读(2335) 评论(1)  编辑 收藏 引用 所属分类: MFC

翻译自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版本都能正常运行。

Feedback

# re: Custom SDI 2 —— 替换CChildView  回复  更多评论   

2009-02-09 15:55 by acc
还要注意新增加的视图类对应的对话框必须是WS_CHILD的
只有注册用户登录后才能发表评论。