• 深入了解CCtrlView


     如果我们要将一个控件转换成视图类,我们一般会想到CCtrlView,用它实现的控件视图一般添加一个GetXXXCtrl函数,函数的作用是返回视图中控件的引用,如果在MFC程序中跟踪它的调用我们会发现它的实现是这样的(以CEdit控件为例)

    _AFXEXT_INLINE   CEdit&   CEditView::GetEditCtrl()   const
    {   return   *(CEdit*)this;   }

    这个转换让人觉得很疑惑,因为CEdit和CEditView是来自两条不同继承链上的类,一般来说这样的转换是要出问题的,但是为什么可以进行这种转换?MSDN上有一篇文章对这种转换做了比较深入的解释,下面把这篇文章翻出来给大家看看。

    问题:
    我设计了一个从CWnd派生而来的自定义控件,现在我想把它当成视图来使用。我想到的的第一个办法是把控件嵌入到视图中然后操控视图中的OnSize函数以使控件覆盖客户区。但问题是传给控件的鼠标消息无法在视图中重载。而传给视图的击键消息必须手动传递给控件。我了解到CCtrlView可以作为公用控件的基类。于是我试图围绕着它设计视图(我记得你在MSJ的某一期上讨论了这个问题),但是我无法让它与我自定义的CWnd派生控件类一起工作。请问这样做可以吗?应该怎样做?

    Mateo Anderson(回答者)

    回答:
    CCtrlView是一个MFC将控件类转换为视图类的技巧。比如从CTreeCtrl转换成CTreeView或者从CListCtrl转换成CListView。在CCtrlView的文
    档注释中提到:“CCtrlView几乎允许把任何控件转换成视图。”。不幸的是,说“几乎”稍微有些夸大其词了,除非作者所说的“任何控件”只是关于像CEdit和CTreeCtrl这样的Windows内建控件。CCtrlView用了一个只能在特定环境下工作的技巧。要理解CCtrlView如何工作,首先让我们看看CTreeView,CTreeView派生于CCtrlView。其中有三个重要的函数需要考虑:构造函数,PreCreateWindow函数和GetTreeCtrl函数。构造函数告诉CCtrlView要创建哪种Windows控件。

    CTreeView::CTreeView() :
      CCtrlView(WC_TREEVIEW, dwStyle)

    在这个例子中,WC_TREEVIEW(在commctrl.h中定义)是树型控件类的类名,也就是“SysTreeView32”。CCtrlView会将此类名保存在一个数据成员中稍后使用:

    CCtrlView::CCtrlView(LPCTSTR lpszClass,
      DWORD dwStyle)
    {
      m_strClass = lpszClass;
      m_dwDefaultStyle = dwStyle;
    }

    下一个起作用的函数是PreCreateWindows,这是一个CTreeCtrl从CCtrlView继承来的函数。CCtrlView::PreCreateWindow在窗口刚好创建完成之前使用m_strClass在CREATESTRUCT种设置类名。

    // CCtrlView uses stored class name
    BOOL CCtrlView::PreCreateWindow(CREATESTRUCT& cs)
    {
      cs.lpszClass = m_strClass;
      •••
      return CView::PreCreateWindow(cs);
    }

    现在创建的窗口就是使用所期望的类创建的了-在这个例子中就是SysTreeView32。到目前为止,一切都很好。但是如果CTreeCtrl派生于CCtrlView,而CCtrlView又派生于CView,那么它为什么能又派生于CTreeCtrl?难道是MFC类封装了树型控件?CTreeView和CTreeCtrl是完全独立的,有着不同的继承链。CTreeCtrl直接派生于CWnd,而CTreeView派生于CCtrlView/CView。这就是技巧发生作用的地方了。要让树型视图像树型控件一样操控,CCtreeView提供了特殊的函数GetTreeCtrl来获得树型控件。

    CTreeCtrl& CTreeView::GetTreeCtrl() const
    {
      return *(CTreeCtrl*)this;
    }

    GetTreeCtrl只是简单的将CtreeView转换为CTreeCtrl。但是等等-怎么会发生这么诡异的事?这两个类完全不同,有着不同的数据成员和虚函数表-你根本无法将一个转换成另一个同时还指望着能够正常工作!答案就是:CTreeCtrl没有虚函数也没有数据成员。你可以把它称为一个纯包装类。CTreeCtrl不对它的基类CWnd添加任何东西(不添加数据成员也不添加虚函数),所有添加的内容只是一些包装函数,一些将消息发送给内在HWND的有形函数。

    HTREEITEM CTreeCtrl::InsertItem(...)
    {
      return (HTREEITEM)::SendMessage(m_hWnd,
        TVM_INSERTITEM, ...);
    }

    InsertItem访问的唯一数据成员是m_hWnd,所有的CWnd派生类都有该成员。InsertItem和所有其它的包装函数只是将它们的参数传递给了内在的HWND,将C++风格的成员函数转换成Windows风格的SendMessage调用。对象本身(“this”指针)可以是任何CWnd派生类的实例,只要m_hWnd处于正确的位置(也即是类的第一个数据成员),并且HWND(实际上就是如此)是一个树型控件的句柄。相同的原因发生在下面的写法中:
     
    pEdit = (CEdit*)GetDlgItem(ID_FOO);

    即便在这里GetDlgItem返回的是一个指向CWnd的指针,不是CEdit。但是这样是允许的,因为CEdit也是一个包装类,而且对于它从CWnd继承来的内容没有添加额外的数据和虚函数。所以注释中“CCtrlView几乎允许将任何控件转换为视图”的说法中所说的“几乎任何”意思是特指对CWnd没有添加数据成员和虚函数的这些控件,也就是我所说的“纯包装类。”如果你的控件类有它自己的数据或虚函数,你无法使用CCtrlView,因为在CCtrlView/CView中不存在这些额外的数据成员和虚函数。

    举例来说,CView中的第一个虚函数是CView::IsSelected。如果你的控件类有其它虚函数,在你把CCtrlView类转换成你的CFooCtrl类调用那个虚函数的时候就肯定会爆发问题了,因为这个函数根本不存在。类似的,CView中的第一个数据成员是m_pDocument。如果你的控件类需要一些其它的数据成员,那你在访问它们的时候就要倒霉了,因为对象事实上调用的是CCtrlView,而不是CFooCtrl。多么糟糕,多么令人沮丧。


    简而言之,唯一可以使用CCtrlView技巧的时候是CWnd派生控件类没有它自己的虚函数和数据成员之时。这就是生活。


    看到这里,大家应该就很明白了,这里的转换有一个与普通类类型之间的转换有一个重要区别,控件类中没有虚函数和新的数据成员,因此控件类具有和CWnd一样的虚函数表和数据摆放,所谓将CEditView转换成CEdit控件,实际CEdit控件对象只会调用CEditView中CWnd那部分的成员,因而这种调用是可行的。

  • 相关阅读:
    iOS开发 当前时间 时间戳 转换
    iOS开发 下滑隐藏Tabbar
    iOS开发 浅见runloop
    iOS开发 检测版本更新
    IOS开发 二维码功能的实现
    级数
    算法-快速排序
    struts2.0中struts.xml配置文件详解【转】
    javascript原型【转】
    AOP各种的实现【转】
  • 原文地址:https://www.cnblogs.com/8586/p/1364521.html
Copyright © 2020-2023  润新知