☰
WTL 10.0最终版在VS2019下的实战:环境搭建、编译配置与常见坑
2026/10/6 5:33:31 网站建设 项目流程

简介:WTL10.0最终版本支持Visual Studio 2019,是面向Windows桌面应用开发者的轻量级C++模板库,适合需要直接调用Windows API、追求高效精简程序体积的场景。相比MFC其框架更小,代码结构清晰,同时保留模板类、Unicode、事件映射等现代C++特性,也便于与ATL等库协同工作。压缩包约704KB,共296个文件,以104个.h头文件和39个.cpp源文件为核心,另含22个.sln/11个.vcxproj解决方案工程、13个.rc资源脚本、28个.bmp/18个.ico等界面素材,并附有安装向导、示例项目、API文档和更新日志。目前已有325人学习,通过对照自带示例可快速掌握对话框、工具栏、标签页等控件的封装方法,以及消息映射、资源脚本和Unicode处理的关键技巧。开发者可在VS2019中直接编译运行这些工程,结合调试工具理解WTL与ATL的协作机制,降低从MFC迁移或精简依赖的上手门槛,便于维护老项目或开展二次开发。

1. 为什么还要在 vs2019 里用 WTL10.0 的最终版:先看它解决什么

WTL 10.0 最终版本 支持 vs2019,这句话对做 C++ 桌面维护的人来说,等于宣告一个旧时代工具在这个新编译器下还能继续服役。WTL 是 Windows Template Library,它不依赖 MFC,思路是用模板把 Win32 窗口过程包起来,代码量和运行体量都比 MFC 轻。如果你手里还有一个维护了十年的内部工具,或者你只是想在 vs2019 里少写一点 CreateWindow 样板代码,这篇笔记能帮你直接跑起来。

网上讨论 WTL 的人少了,但库本身并没有死。10.0 是官方明确的最终版本,之后主要是修 bug,也正是因为接口冻结,它比很多每年破坏一次接口的新框架更适合老项目。它适合三类人:维护老代码的工程师、需要轻量桌面前端的 C++ 开发者、以及不想被 MFC 类向导绑住手的人。

下文从环境搭建讲到消息映射,再讲到 Release/Debug 编译差异和常见坑,最后给出高 DPI 与自绘控件的做法。所有工程配置都以 vs2019 的 v142 工具集为基准,代码不依赖任何第三方库。

2. 半小时搭好 vs2019 下的 WTL 10 开发环境并跑通最小窗口

WTL 10 和大多数 GUI 库不一样,它没有安装包,也不是动态库,本质就是一组头文件。编译时把 include 路径指过去,代码里包含对应头文件,剩下的交给模板展开。所以环境搭建的关键不在 WTL 本身,而在它依赖的 ATL 组件,以及 vs2019 的工程属性是否对得上。

2.1 先认清 WTL 10 和 ATL 的依赖关系

WTL 的上层是 ATL(Active Template Library)。ATL 提供 CComPtr、CWindow、CString 这些基础设施,WTL 提供窗口、消息映射、控件封装。vs2019 并不自带 WTL,但自带 ATL,前提是你在安装 vs2019 时勾选了“适用于最新 v142 生成工具的 C++ ATL(x86 和 x64)”组件。如果没勾,后面所有代码都会卡在#include <atlbase.h>这一步。

另一个容易混淆的点是 MFC。MFC 和 WTL 在理念上有点像,都封装 Win32,但 WTL 编译出来的代码更直接、依赖更少。同一个工程里也可以让 WTL 和 MFC 共存,但绝大多数情况不需要,而且混用会让消息映射宏产生歧义。我一般直接定义_WTL_NO_MFC把 MFC 相关分支排除掉。

从使用体感上说,MFC 的类向导在 vs2019 里已经很成熟,但生成代码量也大;WTL 的宏虽然要记,胜在可控。你打开 WTL 的头文件,能直接看到BEGIN_MSG_MAP这样的宏定义,不藏着。ATL 的安装位置一般跟随 VS 实例目录,不需要手动设置,真正要设置的是 WTL 头文件路径。

2.2 获取 10.0 最终版并设置头文件路径

获取 WTL 10.0 最终版的常见做法,是到 WTL 的官方 GitHub 仓库找 Release,文件名类似 WTL 10.0 Final 的 zip 包。下载后解压到一个固定位置,比如C:\WTL10。解压后能看到 Include、Samples 等目录,注意不要直接把文件散放到C:\WTL10根目录,要指向C:\WTL10\Include。

典型的目录结构是这样的:

C:\WTL10\ Include\ atlapp.h atlframe.h atlctrls.h atlgdi.h ... Samples\

在 vs2019 里打开项目属性,选择“所有配置”,找到“VC++ 目录 -> 包含目录”,添加C:\WTL10\Include。加在$(VC_IncludePath)前面还是后面有讲究,多数情况加在前面,避免系统 SDK 里同名的atlhost.h被先找到。如果你之前装过旧版 WTL,检查一下系统里是否残留多套 Include,工程只认配置里写入的那一条。

如果你的本机还没有 vs2019,下载安装教程里的重点只有两个:工作负载勾“使用 C++ 的桌面开发”,单个组件里勾 ATL。vs2019 社区版本来就不需要产品密钥,直接用;企业版请使用公司正规授权的许可。用 vs2019 离线安装包部署的同学,同样要在安装包的可选组件里找到 ATL,离线包默认并不全装。

2.3 手写一个最小 WTL 窗口:不靠向导也能跑

WTL 在 vs2019 里没有现成的类向导,这也逼我们手动把创建窗口的路径走通。新建一个“空项目”,添加一个 .cpp,把下面的代码放进去:

// 最小 WTL 窗口程序,x86 / x64 均可编译 #include <atlbase.h> #include <atlapp.h> CAppModule _Module; // 全局模块对象,整个程序只定义一次 #include <atlwin.h> class CMainWnd : public CWindowImpl<CMainWnd> { public: DECLARE_WND_CLASS_EX(L"Wtl10DemoClass", CS_HREDRAW | CS_VREDRAW, COLOR_WINDOW) BEGIN_MSG_MAP(CMainWnd) MESSAGE_HANDLER(WM_PAINT, OnPaint) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) END_MSG_MAP() LRESULT OnPaint(UINT, WPARAM, LPARAM, BOOL& bHandled) { PAINTSTRUCT ps; HDC hdc = ::BeginPaint(m_hWnd, &ps); ::TextOutW(hdc, 20, 20, L"WTL 10.0 Final on vs2019", 24); ::EndPaint(m_hWnd, &ps); bHandled = TRUE; return 0; } LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL& bHandled) { PostQuitMessage(0); bHandled = TRUE; return 0; } }; int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) { _Module.Init(nullptr, hInstance); CMainWnd wnd; if (nullptr == wnd.Create(nullptr, CWindow::rcDefault, L"WTL 10 Demo")) return 1; wnd.ShowWindow(nCmdShow); wnd.UpdateWindow(); MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } _Module.Term(); return (int)msg.wParam; }

这段代码的逻辑分四块。第一,CAppModule _Module是 WTL 需要的全局模块对象,负责管理资源句柄和模块状态,必须且只能放在一个 .cpp 里。第二,CWindowImpl<CMainWnd>用 CRTP 模式生成窗口类,DECLARE_WND_CLASS_EX声明窗口类名、样式和背景色,窗口过程由模板内部接管。第三,BEGIN_MSG_MAP到END_MSG_MAP是消息映射表,MESSAGE_HANDLER(WM_PAINT, OnPaint)表示 WM_PAINT 交给 OnPaint 函数处理。第四,_Module.Init绑定实例句柄,消息循环还是经典 Win32 写法。

这里说明两个参数细节。DECLARE_WND_CLASS_EX的第三参数COLOR_WINDOW是背景色索引,宏内部会自动加 1 再转成画刷,这是 WTL 沿用 ATL 的写法;Create的第二个参数CWindow::rcDefault让系统给默认位置和尺寸,第三个参数是窗口标题。入口函数我写的是wWinMain而不是WinMain,因为 vs2019 默认 Unicode 工程的入口符号是wWinMainCRTStartup,这样写可以少改一处链接器设置。

编译这段代码之前,把“链接器 -> 系统 -> 子系统”设为“窗口 (/SUBSYSTEM:WINDOWS)”,语言标准保持默认(C++14)即可。编译通过后窗口标题栏显示 WTL 10 Demo,客户区左侧有一行文字。

3. 用 WTL 10 写一个带控件和事件响应的对话框程序

窗口程序跑通后,下一步是对话框,因为实际项目里大部分界面是模态对话框。WTL 的CDialogImpl只要求你提供一个对话框模板资源 ID,不需要像 MFC 那样生成一堆DoDataExchange代码。从一个带编辑框和按钮的对话框开始,正好能把消息映射、控件封装、事件响应串起来。

3.1 先认识 3 个消息映射宏:MESSAGE_HANDLER / COMMAND_ID_HANDLER / COMMAND_HANDLER

消息映射是 WTL 的命脉。它本质上是一张由宏展开的静态表,把 Windows 消息或WM_COMMAND通知映射到你的成员函数。最常用的三个宏各有分工:

  • MESSAGE_HANDLER(msg, func)处理原始消息,比如 WM_PAINT、WM_SIZE。
  • COMMAND_ID_HANDLER(id, func)处理WM_COMMAND按控件 ID 分发的那一类,比如按钮点击。
  • COMMAND_HANDLER(id, code, func)既看 ID 又看通知码,比如编辑框的EN_CHANGE。

这三个宏对应的函数签名不同。MESSAGE_HANDLER对应LRESULT Func(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);COMMAND_ID_HANDLER对应LRESULT Func(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)。初学最容易忽略的是bHandled的语义:进入 handler 时它是 FALSE,你处理完消息又希望它不要再往后传,就把它置为 TRUE;如果你只做观测、不打算消费,就保持 FALSE,让消息继续沿消息映射链找下一个 handler。

3.2 最小对话框:资源模板、对话框类和主程序

先在resource.h里定义 ID,然后在.rc文件里写对话框模板。vs2019 支持可视编辑.rc,但对于纯代码派,直接写模板更清楚,ID 全在自己掌控中。

// resource.h #define IDD_MAIN_DLG 101 #define IDC_EDT_INPUT 1001 #define IDC_BTN_RUN 1002 #define IDC_STATIC_OUT 1003
// WtlDemo.rc #include "resource.h" IDD_MAIN_DLG DIALOGEX 0, 0, 280, 100 STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "WTL10 对话框示例" FONT 9, "Microsoft YaHei" BEGIN EDITTEXT IDC_EDT_INPUT, 20, 20, 180, 14, ES_AUTOHSCROLL PUSHBUTTON "运行", IDC_BTN_RUN, 210, 18, 55, 17 LTEXT "输出:", IDC_STATIC_OUT, 20, 55, 230, 20 END

对话框类的头文件如下:

// MainDlg.h #pragma once #include <atlbase.h> #include <atlapp.h> #include <atlmisc.h> #include <atldlgs.h> #include <atlctrls.h> #include "resource.h" extern CAppModule _Module; // 与主程序里定义的全局对象对应 class CMainDlg : public CDialogImpl<CMainDlg> { public: enum { IDD = IDD_MAIN_DLG }; BEGIN_MSG_MAP(CMainDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) COMMAND_ID_HANDLER(IDC_BTN_RUN, OnBtnRun) COMMAND_ID_HANDLER(IDCANCEL, OnBtnCancel) END_MSG_MAP() LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) { m_edit.Attach(GetDlgItem(IDC_EDT_INPUT)); return TRUE; // 返回 TRUE,让系统把焦点交给第一个控件 } LRESULT OnBtnRun(WORD, WORD, HWND, BOOL&) { CString s; m_edit.GetWindowText(s); if (s.GetLength() < 3) { MessageBox(L"至少输入 3 个字符", L"校验", MB_ICONINFORMATION); return 0; } SetDlgItemText(IDC_STATIC_OUT, s); return 0; } LRESULT OnBtnCancel(WORD, WORD, HWND, BOOL&) { EndDialog(IDCANCEL); return 0; } private: CEdit m_edit; };

主程序文件:

// WtlDemo.cpp #include <atlbase.h> #include <atlapp.h> CAppModule _Module; #include <atlwin.h> #include "MainDlg.h" int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) { _Module.Init(nullptr, hInstance); AtlInitCommonControls(); // 加载通用控件,避免按钮变成旧版外观 CMainDlg dlg; dlg.DoModal(); _Module.Term(); return 0; }

这段代码里最关键的是CDialogImpl<CMainDlg>和enum { IDD = IDD_MAIN_DLG }。DoModal时读取 IDD 对应的对话框模板资源,由模板内部自动实现对话框过程。OnInitDialog里的m_edit.Attach(GetDlgItem(IDC_EDT_INPUT))是把已有编辑框的 HWND 包装成 CEdit 对象,后续GetWindowText比直接发WM_GETTEXT好读得多。OnBtnRun先做输入长度校验,再回显到静态文本,这是最典型的数据流。

AtlInitCommonControls()在 atlapp.h 中,作用是加载通用控件 DLL 的 6.x 版本,否则按钮和编辑框还是 Windows 2000 时代的经典样式,在现代系统上会明显感到视觉违和。DoModal的返回值就是EndDialog传入的值,所以OnBtnCancel里传IDCANCEL之后,外部拿到的是IDCANCEL,这和 Win32 对话框的语义完全一致。

3.3 给编辑框加 EN_CHANGE 事件:COMMAND_HANDLER 的联动写法

对话框能响应按钮之后,最常遇到的是输入过程中实时校验,比如输入不足长度时按钮置灰。这要用COMMAND_HANDLER同时匹配控件 ID 和通知码:

BEGIN_MSG_MAP(CMainDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) COMMAND_HANDLER(IDC_EDT_INPUT, EN_CHANGE, OnEditChange) COMMAND_ID_HANDLER(IDC_BTN_RUN, OnBtnRun) COMMAND_ID_HANDLER(IDCANCEL, OnBtnCancel) END_MSG_MAP()
LRESULT OnEditChange(WORD, WORD, HWND, BOOL&) { CString s; m_edit.GetWindowText(s); GetDlgItem(IDC_BTN_RUN).EnableWindow(s.GetLength() >= 3); return 0; }

COMMAND_HANDLER和COMMAND_ID_HANDLER的区别只在是否匹配通知码。按钮点击的通知码是BN_CLICKED,编辑框是EN_CHANGE,两个宏按顺序匹配,正常不会撞。但代码顺序还是建议先放更具体的COMMAND_HANDLER,再放COMMAND_ID_HANDLER,阅读时逻辑更清楚。

再往深一层,WTL 支持消息反射。自定义控件需要自己处理一部分通知时,可以在控件类消息映射末尾加REFLECT_NOTIFICATIONS(),把父窗口收到的WM_COMMAND通知先反射给控件类自己的消息映射处理,处理不掉再走父对话框。理解反射机制后,封装按钮、列表这类控件会更顺手。

4. vs2019 里 WTL10 工程的编译配置:Release/Debug 差异和 5 个必调项

很多初学者把 Debug/Release 切一刀就完事,WTL10 工程卡在这儿的概率很高。Debug 配置默认定义_DEBUG,使用调试运行时和调试版 ATL 库;Release 配置定义NDEBUG,使用发布运行时。对 WTL 来说,头文件里的ATLASSERT会受两个开关影响,Release 下断言编译为空,所以同一段代码可能在 Debug 下正常、在 Release 下异常,反过来也常见,排查时先确认配置再怀疑代码。

4.1 动态 ATL 与静态 ATL:选错会在切配置时翻车

WTL 10 推荐使用动态 ATL,也就是项目属性里“ATL 的使用”选“使用 ATL(动态链接)”。在这个模式下,编译器自动定义_ATL_DLL,链接器自动选择 Debug 的atlsd.lib或 Release 的atls.lib。如果你手动在附加依赖项里写死atlsd.lib,切到 Release 就会报LNK1104: cannot open file 'atlsd.lib'。

很多老工程为了部署方便,会改成“使用静态 ATL”。静态 ATL 下,每个可执行文件会把 ATL 代码链接进来,文件体积大 100KB 左右,但目标机器不需要装 ATL DLL。这个选择没有绝对对错,只是 WTL10 的头文件里有些宏会在编译期检查 ATL 使用模式,切换后建议全量重新编译,不要增量凑合。

4.2 项目属性里需要拧紧的 5 个开关

下面这张表是我在 vs2019 里调 WTL10 工程时必查的参数:

属性位置推荐值不这么设的典型后果
常规 -> 平台工具集Visual Studio 2019 (v142)用 v143 虽然多数能过,但老工程跨版本工具集容易在字符集宏上出现隐藏差异
常规 -> 字符集使用 Unicode 字符集多字节模式下 L 前缀和 TCHAR 宏不一致,中文界面乱码
常规 -> ATL 的使用使用 ATL(动态链接)手动写库名会在 Release/Debug 切换时 LNK1104
C/C++ -> 预处理器加_WTL_NO_MFC不排除 MFC 时,atlapp.h 会走一组 MFC 兼容分支,宏展开更绕
链接器 -> 系统 -> 子系统Windows (/SUBSYSTEM:WINDOWS)控制台子系统启动时闪黑框,且入口符号变成 main/wmain

这里的顺序也重要。设置路径是:先切到“所有配置”,把平台工具集、字符集、ATL 使用改完,再单独切 Debug/Release 微调。如果一开始只在当前配置下改,新加的文件继承不到完整属性。

4.3 入口点、附加依赖项和清单的链接器细节

WTL 工程的链接器有三个点容易被忽略。第一是入口点,Unicode 项目默认找wWinMainCRTStartup,所以源码入口用wWinMain。如果从老项目拷来的代码是WinMain,就在“链接器 -> 高级 -> 入口点”填WinMainCRTStartup,两个方案选一个,不要同时改。

第二是附加依赖项。WTL 本身没有 lib 文件,但通用控件部分需要comctl32.lib。vs2019 向导生成工程时一般会自动带,空项目手写示例时容易漏,导致InitCommonControlsEx链接失败。我习惯在代码里加一行#pragma comment(lib, "comctl32.lib"),让依赖跟随源码走,换工程不容易丢。

第三是清单。WTL10 老项目里常见的是通过.manifest声明 DPI 感知,但如果你在链接器设置里把“生成清单”关掉,外面的.manifest文件不会自动嵌入。正确做法是在“清单工具 -> 输入和输出 -> 附加清单文件”里指名,或者直接用项目生成清单,不要一边关一边期待生效。

4.4 预编译头:stdafx.h 到 pch.h 的迁移

老 WTL 工程几乎都有stdafx.h,文件开头通常是三行固定的 include:

// stdafx.h #pragma once #include <atlbase.h> #include <atlapp.h> #include <atlwin.h>

vs2019 新建项目默认启用“预编译头”,但生成的文件名是pch.h而不是stdafx.h。把旧工程迁到 vs2019 时,项目属性里的预编译头文件名要改成对应名字。最怕的是属性里开着预编译头,工程里却找不到stdafx.h,每个 .cpp 编译到一半报C1010。解决方式不是关掉预编译头,而是新建一个空的stdafx.h和stdafx.cpp,把统一包含环境收纳进去,这样后续代码日志、类型定义、ATLASSERT 的展开环境都一致。

5. WTL 10.0 在 vs2019 下的常见编译坑:现象、原因、处理

下面这些坑是我在 vs2019 下接 WTL10 老工程时踩过的真实问题,按“现象、原因、解决”写,方便你直接搜索对应错误码。

5.1 C1083:无法打开 atlbase.h

现象:新建空项目,代码第一行#include <atlbase.h>就报C1083: 无法打开包括文件: "atlbase.h"。

原因:vs2019 安装时只勾了“使用 C++ 的桌面开发”,但没勾单独的“C++ ATL”组件。ATL 和 MFC 是两个独立可选组件,默认不一定会选,尤其用 vs2019 离线安装包批量部署的机器最容易缺。

解决:打开 vs2019 Installer,点“修改”,在“单个组件”里搜 ATL,勾选“适用于最新 v142 生成工具的 C++ ATL(x86 和 x64)”,点修改后重启 vs2019。WTL 本身只是头文件,不需要重装这种流程。

5.2 LNK2019:无法解析的外部符号 wWinMain 或 WinMain

现象:链接时报LNK2019: unresolved external symbol wWinMain referenced in function wWinMainCRTStartup。

原因:vs2019 生成工程时按 Unicode 配置,链接器默认入口是wWinMainCRTStartup,但源码写的是int WINAPI WinMain(...)。入口符号不匹配,CRT 初始化完成后找不到定义。

解决:把函数签名改成int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)。如果代码是从多字节老项目拷来的,想保留WinMain,就去“链接器 -> 高级 -> 入口点”填WinMainCRTStartup,两个方案二选一。

5.3 C2065:IDD_MAIN_DLG 未定义

现象:编译对话框类时IDD_MAIN_DLG标红,跟在 enum 定义处也查不到值。

原因:最常见的是头文件里没#include "resource.h",或者工程里有多个 resource.h 被不同目录覆盖。WTL 没有 MFC 那种自动维护资源符号的向导,ID 全靠手写,漏一处就崩。

解决:在对话框头文件顶部加#include "resource.h",在解决方案资源管理器里确认只有一个 resource.h 参与编译。再对一下 .rc 里模板 ID 和 resource.h 的宏值,模板里的 101、1001 这些数字和#define IDD_MAIN_DLG 101必须一致。

5.4_Module未定义或重定义

现象:编译报_Module undeclared identifier,或者反过来报_Module already defined。

原因:WTL 需要程序里恰好有一个全局CAppModule _Module;。老示例普遍写CComModule _Module;,在 WTL10 里 CComModule 被 CAppModule 替代,但 atlapp.h 对两者都兼容,混用时会出现两个不同类名的全局对象同时存在。

解决:选一个 .cpp,只写一次CAppModule _Module;,位置放在#include <atlapp.h>之后;其他文件需要引用时用extern CAppModule _Module;。把老代码里的CComModule _Module全部删掉,特别是 stdafx.h 里的那一份。

5.5 LNK1104:无法打开 atlsd.lib

现象:Debug 编译正常,切到 Release 报LNK1104: cannot open file 'atlsd.lib'。

原因:有人手动在“附加依赖项”里写了 atlsd.lib,或者从旧工程复制的 .props 里带了 Debug 库名。Release 配置拿着 Debug 库名去找文件,自然找不到。

解决:打开工程属性,左边选 Release 配置,在“链接器 -> 输入 -> 附加依赖项”里删掉 atlsd.lib。如果确认只是 ATL 调试库缺失,去 Installer 补装 ATL 组件的调试版本。最干净的方案是让“ATL 的使用”自动选择 atlsd/atls,不写死库名。

6. 进阶:高 DPI 适配、自绘控件与老项目接手前的三个体检项

6.1 高 DPI:manifest 里只有 dpiAware 不够

WTL10 老工程在 4K 屏上发虚,大多数是因为系统按位图拉伸。在 vs2019 下改成 PerMonitorV2 最稳妥:项目里加一个 app.manifest,并在“清单工具 -> 输入和输出 -> 附加清单文件”中引用:

<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> </windowsSettings> </application>

PerMonitorV2 要求每个窗口自己响应WM_DPICHANGED,如果工程里没处理这个消息,至少文字不会糊,控件位置可能被系统缩放,但比完全不声明好得多。这个改动对老工程是侵入最小的一个入口。

6.2 自绘控件:从 WM_DRAWITEM 入手

按钮、列表控件要改外观,优先考虑WM_DRAWITEM而不是继承控件类重绘。在对话框消息映射里加一条:

MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem)

OnDrawItem里根据DRAWITEMSTRUCT::CtlID区分控件,先填充背景,再画边框和文字。注意按钮必须设BS_OWNERDRAW样式,否则系统不会发WM_DRAWITEM。这个方案不建议一上来就封装成自绘控件类,先在对话框里跑通,后续提取公共代码时不容易把消息反射和父子窗口的关系搞混。

6.3 接手老 WTL10 工程前只看三个地方

我拿到一个自称“WTL10 最终版在 vs2019 能编过”的工程,不会急着 Ctrl+F5。先看三处:stdafx.h 里有没有全局_Module和 WTL 头文件顺序;工程属性的“ATL 的使用”是不是动态链接;有没有声明 dpiAwareness。前两个决定能不能编过,第三个决定用户打开界面会不会看一眼就关掉。如果这三处全乱,修起来的工作量基本等于重搭,不如按第 2 章的方式重建壳,把业务代码迁移进来。这个习惯帮我避开了很多黑匣子工程。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询