混合使用 WTL 和 MFC






4.76/5 (20投票s)
一种简单的使用 WTL 模板增强 MFC 窗口的方法。
这是什么?
本文介绍了一种在 MFC 窗口类上使用 WTL 模板类的方法,即将 MFC CWnd
类转换为其 ATL/WTL 对应类 CWindow
,同时使该类可以从 MFC 代码中使用。
您可以在这里找到所需的文件、详细的 10 步说明以及一个可运行的演示。
分步操作
这些是获取混合 MFC/WTL 窗口的基本步骤。它们已在基于 MFC 对话框的项目中使用过,但应该适用于任何 MFC 应用程序。
为 MFC 应用程序添加 WTL 支持
- 选择一个您想要修改以支持 ATL/WTL 函数和类的 MFC 项目。
- 打开其预编译头文件(通常是
"stdafx.h"
)并添加以下行// Add support for ATL/WTL #define _WTL_NO_AUTOMATIC_NAMESPACE
这可以防止 WTL 头文件将 WTL 命名空间自动合并到全局命名空间。这避免了与同名 MFC 类(如
CRect
、CDC
等)的冲突。#include <atlbase.h> #include <atlapp.h> extern WTL::CAppModule _Module;
ATL/WTL 代码可能会访问全局
_Module
变量,因此它必须具有外部链接。#include <atlwin.h>
我们在这里添加了通用的 WTL 头文件以利用预编译头,但您可能只想在真正需要它的地方包含它。
- 打开项目主文件,即实现
CWinApp
派生类的地方,并按如下修改它///////////////////////////////////////////////////////////////////////////// // The one and only CMixedWindowApp object CMixedWindowApp theApp; WTL::CAppModule _Module; // add this line
全局
_Module
变量必须在某处定义,这是一个很好的位置。 - 按如下修改
InitInstance()
///////////////////////////////////////////////////////////////////////////// // CMixedWindowApp initialization BOOL CMixedWindowApp::InitInstance() { // Initialize ATL _Module.Init(NULL, AfxGetInstanceHandle()); ... }
- 如果不存在,请使用 ClassWizard 等工具将
ExitInstance()
虚函数添加到应用程序对象,然后按如下修改它int CMixedWindowApp::ExitInstance() { // Terminate ATL _Module.Term(); return CWinApp::ExitInstance(); }
创建一个混合窗口类
假设我们想为静态位图控件添加滚动功能。我们可能希望将 WTL 模板类 CScrollImpl<...>
与 MFC 类 CStatic
一起使用,因为 MFC 中没有这样的窗口类。
- 创建一个新类,从
CStatic
(或任何其他 CWnd 派生类)派生,例如使用 ClassWizard。 - 打开类头文件并包含任何您需要的 WTL 头文件
// Add support for scrolling windows #include <atlscrl.h>
请记住,如果您在上面的第 3 点没有选择使用预编译头,您需要在此处,在任何其他 WTL 头文件之前添加该行
#include <atlwin.h>
- 包含必要的头文件以将
CWnd
类转换为CWindow
。它包含一个定义缺失成员的模板类// Make this class CWindow compatible #include "Wnd2Window.h"
如果您经常使用 WTL 头文件,为了加快重新编译速度,您可以将上面一行放入您的预编译头文件,就像所有其他 WTL 头文件一样。这完全由您选择。
- 然后修改类声明以进行到
CWindow
的转换并使用您选择的 WTL 模板类class CScrollPicture : public CWnd2Window<CScrollPicture, CStatic>, public WTL::CScrollImpl<CScrollPicture>
- 添加 WTL 类可能需要的任何其他函数,注意为其参数指定 WTL 命名空间。在此示例中,我们需要实现
DoPaint()
以与滚动配合使用
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()
,就像在 CView
和 CScrollView
中一样
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 对象时需要明确指定命名空间,无论如何这都不是一个坏习惯。
- 您可能会发现编译器抱怨您的混合类的某些成员函数。那是因为许多
CWnd
和CWindow
成员具有相同的名称,您必须解决歧义才能编译。
就是这样。我只使用过这种方法几次,所以我不知道还有其他问题。但我期待评论……
许可证
我认为只要您有权使用 ATL 源代码,您就可以自由使用此代码。这里没有原始代码,除了 CScrollPicture
类,只有一个好主意。我所有的源代码都已发布到公共领域,您可以随意使用它。至于 CWnd2Window
类,几乎所有代码都归 Microsoft Corporation 所有。
演示项目
在随附的演示项目中,您可以找到一个非常简单的滚动图片控件实现。由于我子类化了一个 CStatic
控件,所以我有时需要覆盖默认行为。一个更简洁的实现会使用 CWnd
,但这样我就可以展示消息路由按预期工作,即使混合了 MFC 消息处理程序和 WindowProc()
。
要测试控件,请在滚动条上使用鼠标或将焦点移到其上并使用键盘箭头,按住 CTRL 键可按页滚动。没有焦点指示器,所以您必须通过排除法猜测。还有一个按钮可以加载外部 BMP 文件。
请注意,此控件并非旨在功能齐全,而只是一个在 MFC 项目中使用 WTL 模板的示例。特别是,如果该控件用于可调整大小的对话框中,我预计会出现错误,因为根据我的经验,实现滚动的 WTL 类从这个角度来看并不“完美”。我正在开发一个替代类,但还没有时间发布。
任何改进本文的想法,请告诉我!