65.9K
CodeProject 正在变化。 阅读更多。
Home

混合使用 WTL 和 MFC

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (20投票s)

2003年1月28日

公共领域

7分钟阅读

viewsIcon

196705

downloadIcon

3188

一种简单的使用 WTL 模板增强 MFC 窗口的方法。

Sample Image - mixed.png

这是什么?

本文介绍了一种在 MFC 窗口类上使用 WTL 模板类的方法,即将 MFC CWnd 类转换为其 ATL/WTL 对应类 CWindow,同时使该类可以从 MFC 代码中使用。

您可以在这里找到所需的文件、详细的 10 步说明以及一个可运行的演示。

分步操作

这些是获取混合 MFC/WTL 窗口的基本步骤。它们已在基于 MFC 对话框的项目中使用过,但应该适用于任何 MFC 应用程序。

为 MFC 应用程序添加 WTL 支持

  1. 选择一个您想要修改以支持 ATL/WTL 函数和类的 MFC 项目。
  2. 打开其预编译头文件(通常是 "stdafx.h")并添加以下行
    // Add support for ATL/WTL
    #define _WTL_NO_AUTOMATIC_NAMESPACE

    这可以防止 WTL 头文件将 WTL 命名空间自动合并到全局命名空间。这避免了与同名 MFC 类(如 CRectCDC 等)的冲突。

    #include <atlbase.h>
    #include <atlapp.h>
    
    extern WTL::CAppModule _Module;

    ATL/WTL 代码可能会访问全局 _Module 变量,因此它必须具有外部链接。

    #include <atlwin.h>

    我们在这里添加了通用的 WTL 头文件以利用预编译头,但您可能只想在真正需要它的地方包含它。

  3. 打开项目主文件,即实现 CWinApp 派生类的地方,并按如下修改它
    /////////////////////////////////////////////////////////////////////////////
    // The one and only CMixedWindowApp object
    
    CMixedWindowApp theApp;
    WTL::CAppModule _Module;	// add this line

    全局 _Module 变量必须在某处定义,这是一个很好的位置。

  4. 按如下修改 InitInstance()
    /////////////////////////////////////////////////////////////////////////////
    // CMixedWindowApp initialization
    
    BOOL CMixedWindowApp::InitInstance()
    {
        // Initialize ATL
        _Module.Init(NULL, AfxGetInstanceHandle());
    
        ...
    }
  5. 如果不存在,请使用 ClassWizard 等工具将 ExitInstance() 虚函数添加到应用程序对象,然后按如下修改它
    int CMixedWindowApp::ExitInstance() 
    {
        // Terminate ATL
        _Module.Term();
    
        return CWinApp::ExitInstance();
    }

创建一个混合窗口类

假设我们想为静态位图控件添加滚动功能。我们可能希望将 WTL 模板类 CScrollImpl<...> 与 MFC 类 CStatic 一起使用,因为 MFC 中没有这样的窗口类。

  1. 创建一个新类,从 CStatic(或任何其他 CWnd 派生类)派生,例如使用 ClassWizard。
  2. 打开类头文件并包含任何您需要的 WTL 头文件
    // Add support for scrolling windows
    #include <atlscrl.h>

    请记住,如果您在上面的第 3 点没有选择使用预编译头,您需要在此处,在任何其他 WTL 头文件之前添加该行

    #include <atlwin.h>
  3. 包含必要的头文件以将 CWnd 类转换为 CWindow。它包含一个定义缺失成员的模板类
    // Make this class CWindow compatible
    #include "Wnd2Window.h"

    如果您经常使用 WTL 头文件,为了加快重新编译速度,您可以将上面一行放入您的预编译头文件,就像所有其他 WTL 头文件一样。这完全由您选择。

  4. 然后修改类声明以进行到 CWindow 的转换并使用您选择的 WTL 模板类
    class CScrollPicture : public CWnd2Window<CScrollPicture, CStatic>,
                           public WTL::CScrollImpl<CScrollPicture>
  5. 添加 WTL 类可能需要的任何其他函数,注意为其参数指定 WTL 命名空间。在此示例中,我们需要实现 DoPaint() 以与滚动配合使用
  6. public:
        // Inline helper
        void DoPaint(WTL::CDCHandle dc)
        {
            OnDraw(CDC::FromHandle(dc));
        }
    
    protected:
        // Implement painting with scroll support
        void OnDraw(CDC* pDC);

实现注意事项

不要在任何地方过多地混合使用 MFC 和 WTL 类代码可能是一个好主意,因为它可能会变得相当混乱。一种有效的方法是定义内联辅助函数,将 WTL 参数转换为其 MFC 对应项,除非您只想使用 WTL 编写所需的函数。请注意,您可以安全地将 WTL 对象与 MFC 代码混合使用,但您始终需要在适当的时候指定 WTL 命名空间。

使用辅助函数还可以使编写“桥接”类更容易。例如,您可以定义一个 CScrollWnd 类以在需要滚动功能和自定义绘图时重用(通过继承),声明一个虚函数 OnDraw(),就像在 CViewCScrollView 中一样

public:
    // Inline helper
    void DoPaint(WTL::CDCHandle dc)
    {
        OnDraw(CDC::FromHandle(dc));
    }

protected:
    // Must be implemented in derived class
    virtual void OnDraw(CDC* pDC) = 0;
我使用了一个纯虚函数,但 CView 定义了一个只触发断言的基本实现。这些只是一些想法,但欢迎提出新的建议。

听起来不错……但是它是如何工作的呢?

嗯,没什么特别或神秘的。ATL 类 CWindow(WTL 使用的)只有一个成员变量 m_hWnd,它与 CWnd 类中的完全相同。因此,所有 CWindow 成员函数只需要该成员变量,该变量也可以在 CWnd 派生类中找到。

我所做的是将所有 CWindow 成员(除了 m_hWnd)复制到一个包含文件中并定义一个新的模板类。

template <class T, class TBase> class CWnd2Window : public TBase

要使用该模板,您需要传递派生类和基类,如上面关于滚动控件的示例所示。

然后我添加了必要的代码以启用 WTL 消息映射。我实现了 MFC 函数 DefWindowProc(),它在 WindowProc() 之后调用,当在 MFC 消息映射中找不到条目时。

protected:
    virtual LRESULT DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam)
    {
        T* pT = static_cast<T*>(this);
        ATLASSERT(::IsWindow(pT->m_hWnd));

        LRESULT lResult;
        if (pT->ProcessWindowMessage(m_hWnd, nMsg, wParam, lParam, lResult))
                return lResult;

        return TBase::DefWindowProc(nMsg, wParam, lParam);
    }

请记住,WindowProc() 首先被调用。如果它没有被覆盖,或者如果您将消息传递给 CWnd 中的基本实现,则会调用 MFC 消息处理程序。如果在 MFC 消息映射中找不到相应的条目,或者如果您在消息处理程序中调用基本实现,或者如果您显式调用 Default(),则消息最终会到达 DefWindowProc(),它会调用 ATL/WTL 消息映射实现。

请注意,您不必更改 MFC 消息映射宏,这些宏会跳到 MFC 基类,忽略中间模板 CWnd2Window。我没有在那里定义 MFC 消息映射,所以这是完全合法的。

我还注释掉了 MFC 已经定义的一些“危险”成员函数,例如 Attach()Detach()CreateWindow()DestroyWindow(),但我可能还有其他一些我不知道的需要注释掉。据我所知,Attach/Detach 处理 CWnd 映射,而 CreateWindow/DestroyWindow 设置 Windows 钩子或调用一些虚函数。所以如果你仍然想将你的类用作 CWnd,你需要调用原始函数,而不是 CWindow 定义的那些。

可能还有一些与 MFC 函数相同的函数可以删除。欢迎提出改进建议。

到目前为止一切顺利……但是有什么变化?

嗯,您需要注意几件事

  • 访问 WTL 对象时需要明确指定命名空间,无论如何这都不是一个坏习惯。
  • 您可能会发现编译器抱怨您的混合类的某些成员函数。那是因为许多 CWndCWindow 成员具有相同的名称,您必须解决歧义才能编译。

就是这样。我只使用过这种方法几次,所以我不知道还有其他问题。但我期待评论……

许可证

我认为只要您有权使用 ATL 源代码,您就可以自由使用此代码。这里没有原始代码,除了 CScrollPicture 类,只有一个好主意。我所有的源代码都已发布到公共领域,您可以随意使用它。至于 CWnd2Window 类,几乎所有代码都归 Microsoft Corporation 所有。

演示项目

在随附的演示项目中,您可以找到一个非常简单的滚动图片控件实现。由于我子类化了一个 CStatic 控件,所以我有时需要覆盖默认行为。一个更简洁的实现会使用 CWnd,但这样我就可以展示消息路由按预期工作,即使混合了 MFC 消息处理程序和 WindowProc()

要测试控件,请在滚动条上使用鼠标或将焦点移到其上并使用键盘箭头,按住 CTRL 键可按页滚动。没有焦点指示器,所以您必须通过排除法猜测。还有一个按钮可以加载外部 BMP 文件。

请注意,此控件并非旨在功能齐全,而只是一个在 MFC 项目中使用 WTL 模板的示例。特别是,如果该控件用于可调整大小的对话框中,我预计会出现错误,因为根据我的经验,实现滚动的 WTL 类从这个角度来看并不“完美”。我正在开发一个替代类,但还没有时间发布。

任何改进本文的想法,请告诉我!

© . All rights reserved.