资讯动态

CButtonST修正版:VS2012下MFC按钮自绘与编译问题解析

发布时间:2026/10/13 11:32:39 来源:尧图企业网站定制
简介这是一套面向MFC开发者的CButtonST V3.9修正版资源包专门解决原始代码升级到Visual Studio 2012时出现的编译错误与兼容性问题。作为经典按钮控件扩展库的最终免费版本它支持多种按钮样式、自定义鼠标与键盘事件、状态反馈、图文自由布局以及多语言文本修正后可在VS2012下正常编译运行。压缩包共含93个文件、约617KB其中16个cpp源文件与18个h头文件构成核心代码另外35个ico图标、7个bmp位图、2个wav音效为界面资源并附有完整的Demo工程和解决方案、过滤器等文件适合直接打开参照或集成到现有项目。已有366人学习下载对于仍在使用VS2012的MFC开发者而言该修正版免去了自行排查旧代码兼容问题的麻烦可继续利用CButtonST丰富的界面特性增强Windows应用交互体验。1. CButtonST V3.9 到底是个啥老 MFC 项目里最好用的按钮自绘类接手一个老 MFC 工程界面还是 Windows 2000 那种灰底硬按钮客户提了一嘴“能不能把按钮做得跟现在 App 一样”。你第一个想到的往往不是去重写界面而是去找一个能直接塞进对话框的按钮类。CButtonST 就是这类需求里最常被翻出来的那个名字。它是一个基于 CButton 派生的自绘按钮类解决了普通按钮不能随意改背景色、不能同时显示图标和文字、不能做多状态切换的问题。V3.9 是流传得比较广的版本而这个“修正版”解决的问题很具体让它在 VS2012 的编译器底下能一口气编过不再需要为了一个老控件去降级工具链。如果你正在维护 C/C 写的桌面客户端又不想为了好看的按钮引入整个 UI 框架这个类依然是值得花半小时接进来的方案。2. 为什么原版 CButtonST 在 VS2012 下编译费劲先搞清它的工作方式2.1 CButtonST 的自绘机制不是贴图是接管了整条绘制链路普通 CButton 的绘制由系统完成你顶多设置一个背景色但控制不了按钮内部的细节。CButtonST 的思路是重写按钮的绘制流程它对外提供几十个设置方法内部把这些设置翻译成画笔、画刷、字体和几何计算然后一次性画到按钮客户区上。核心动作在于 MFC 的消息反射机制。按钮收到 WM_DRAWITEM 时消息会先回到父窗口再反射给按钮自己。CButtonST 在派生类里处理了 OnDrawItem并调用自己内部的 Draw 系列函数把普通按钮变成了一个完全可控的绘图表面。这个机制有两个直接后果。第一按钮必须拥有“自绘”属性否则系统根本不发 WM_DRAWITEM 给你。你在对话框资源里拖上去的普通按钮默认没有这个属性所以要么在资源编辑器里勾选 Owner Draw要么在代码里用 ModifyStyle 把 BS_OWNERDRAW 加上去。第二自绘之后按钮的边框、背景、文字颜色、焦点框全部由类自己负责任何一个绘制分支没处理干净按钮的显示就会缺一块。网上很多“按钮消失”“只有文字没背景”的问题本质上都是自绘代码里某个绘制分支在特定条件下没走到。2.2 原版 V3.9 在 VS2012 编译不过的三类典型原因VS2012 使用的是 Visual C 11 编译器它对 C 标准的支持比 VC6、VS2008 严格不少同时对 Windows SDK 里的宏定义也做了很多清理。原版 CButtonST V3.9 之所以编译不过集中在三类问题上。第一类是字符串处理。老代码里很多地方写的是 char* 或字符串字面量直接参与拼接而工程一旦设置了 Unicode 字符集LPCTSTR 展开为 const wchar_t*类型不匹配的报错会密集出现。常见的报错是“无法从 const char [N] 转换为 LPCWSTR”。这不是某一处的问题而是整个文件需要按 Unicode 习惯改写。第二类是新版 SDK 不再容忍某些旧写法。例如对 MessageBox 返回值与 IDYES、IDNO 的比较在新 SDK 里宏展开结果类型变化后会产生“有符号/无符号不匹配”的警告严重时直接报错。还有一些遗留 API 被标记为 deprecated编译器按错误级别处理时就会卡住。第三类与代码页有关。原版 V3.9 源码里带了不少非英文注释和字符串资源如果它们是用 ANSI 编码保存的在简体中文 Windows 上打开VS2012 经常报 C4819 警告。这个警告默认不中断编译但它往往铺满整个输出窗口把真正有用的错误信息全部淹没这也是“编译不过”的第一印象来源。2.3 选型判断为什么还用 CButtonST而不是自己画或者换 UI 库你可能想问既然要改代码为什么不干脆自己写一个自绘按钮或者直接引入某个现代 UI 库。这要分场景看。如果是新项目确实不应该用 CButtonSTEWindow、DirectUI 那一套或者 Qt 都更稳妥。但绝大多数接触 CButtonST 的开发者手上是一个已经运行了多年的 MFC 项目对话框、消息映射、资源脚本都是现成的。这种情况下把一个按钮类接进来只改控件类型和少量映射代码风险远小于重写界面。CButtonST 的接口设计又是围绕“按钮”这个词展开的一个按钮一个对象方法名直观不需要理解复杂的视图树。我自己在这样的老项目里接过几次 CButtonST基本路径都是把源文件加到工程、包含头文件、把控件变量类型从 CButton 换成 CButtonST、在 OnInitDialog 里调用几个设置方法编译运行。整个过程通常在半小时内完成出问题的概率也集中在字符串和字符集上。这种可控性正是老项目愿意继续使用它的主要原因。3. 把修正版接进 VS2012 工程从添加文件到第一个自绘按钮3.1 文件放入和工程设置先让源码能干净地编译拿到修正版源码后通常会看到这样几个文件CButtonST.h、CButtonST.cpp部分版本还带一个消息映射文件或者位图资源。先把它们拷贝到工程目录下的一个独立子目录比如Libs/CButtonST不要让源文件跟对话框代码混在一起方便后续维护。然后在 VS2012 里右键工程选择“添加 现有项目”把两个文件加进工程。这一步做完先直接按 F7 编译一次不要急着写业务代码。如果编译立刻报错十有八九是字符集问题。你需要在工程属性里确认“配置属性 常规 字符集”这一项。修正版一般在 VS2012 下默认按 Unicode 处理如果你的工程用的是多字节字符集可能会反过来出现 LPCWSTR 转换到 LPCSTR 的错误。这里没有标准答案以你的工程现有设置为主CButtonST 自身要做的就是跟随工程的字符集。如果修正版的代码里已经全面使用_T()宏和TCHAR这种跟随是能做到的。完成基础编译后建议顺手把 CButtonST.cpp 里的警告级别调低一点。在文件属性注意是文件属性不是工程属性里把“警告等级”改成 Level3C4819 这类代码页警告可以单独屏蔽掉。这不影响业务代码的警告输出只会让 CButtonST 编译时不再刷屏。这一步很实用能让你在后续排查问题时不至于迷失在一堆警告里。3.2 第一个接口调用把对话框里的按钮替换成自绘按钮假设你在一张登录对话框上放了一个 ID 为IDC_BTN_LOGIN的按钮。原来的代码可能是这样// Dialog.h 中的声明 CButton m_btnLogin; // Dialog.cpp 中的绑定 DDX_Control(pDX, IDC_BTN_LOGIN, m_btnLogin);现在要做两件事。第一在头文件里把CButton改为CButtonST同时包含头文件#include CButtonST.h // 类成员声明 CButtonST m_btnLogin;第二在 OnInitDialog 里调用修改样式的代码让按钮开启自绘属性并设置一个简单的平面样式BOOL CLoginDialog::OnInitDialog() { CDialogEx::OnInitDialog(); // 设置按钮为自绘模式 m_btnLogin.ModifyStyle(0, BS_OWNERDRAW); // 使用平面样式去掉系统默认的立体边框 m_btnLogin.SetFlat(TRUE); // 设置文字与背景色 m_btnLogin.SetColor(CButtonST::BTNST_COLOR_BK_OUT, RGB(20, 120, 210)); m_btnLogin.SetColor(CButtonST::BTNST_COLOR_TEXT_OUT, RGB(255, 255, 255)); return TRUE; }ModifyStyle那行不是可选项它负责把按钮变成自绘类型。少了这行SetFlat、SetColor这些设置全部无效控件看起来跟普通按钮没有任何区别而且不会有任何报错。SetFlat(TRUE)让按钮以平面风格绘制鼠标悬停时才显示立体边缘登录界面通常会这样处理。SetColor的第一个参数指定颜色类型BTNST_COLOR_BK_OUT表示按钮普通状态下的背景色后面跟着的_OVER、_DOWN、_DISABLED分别控制悬停、按下和禁用状态的独立颜色。这里要强调一点以上代码适用于你在对话框上预先放好了按钮的场景。如果你完全通过代码创建按钮不依赖对话框资源那就需要在创建时指定BS_OWNERDRAW样式。用Create函数创建时把样式参数写成这样即可// 运行时动态创建自绘按钮 m_btnLogin.Create(_T(登 录), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW | WS_TABSTOP, CRect(200, 100, 280, 135), this, IDC_BTN_LOGIN);WS_TABSTOP让按钮能通过 Tab 键聚焦自绘按钮如果不处理焦点状态视觉上看不出来聚焦但键盘用户会困惑。建议所有动态创建的按钮都保留这个样式。3.3 加一个图标试试SetIcon 与资源加载的坑CButtonST 的一个特色是支持在同一个按钮里同时显示图标和文字。给登录按钮加一个钥匙图标代码很简单// 加载图标资源IDI_KEY 是你在资源里加的图标 HICON hIcon (HICON)::LoadImage( AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_KEY), IMAGE_ICON, 16, 16, // 小图标16x16 LR_DEFAULTCOLOR ); // 设置图标位置在文字左边 m_btnLogin.SetIcon(hIcon, 0);SetIcon的第二个参数是图标位置0 表示放在按钮左侧常用于标准图标按钮。这里的坑在于LR_DEFAULTCOLOR。如果你的图标是 256 色或更多色LoadImage在加载时会按系统当前显示模式做颜色匹配可能出现颜色偏暗或透明区域变黑的情况。更稳妥的做法是加LR_SHARED标志让系统使用共享图标资源避免重复加载时产生 GDI 泄漏。改写如下HICON hIcon (HICON)::LoadImage( AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_KEY), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR | LR_SHARED );使用LR_SHARED后图标资源归系统管理你不需要手动销毁。反之如果不加这个标志hIcon变成了你手上的私有句柄在按钮销毁时需要调用::DestroyIcon释放否则每次对话框关闭都会泄漏一点 GDI 资源。运行时的表现就是程序长时间开关窗口后按钮图标逐渐消失甚至整个界面绘制异常。很多老项目在 Windows 7 上用得好好的换到 Windows 10 后频繁出现界面刷新问题其实不是 CButtonST 的错而是资源句柄用完了。4. 常用接口与样式组合把按钮调整到你要的观感4.1 SetColor、SetFlat、SetFont三个最常用的设置器CButtonST 的接口很多但日常项目里高频使用的往往只有几个。SetColor负责颜色它的颜色类型参数决定你设置的是哪一部分。常见的状态包括颜色类型作用对象典型使用时机BTNST_COLOR_BK_OUT普通状态背景色默认按钮背景用于品牌色BTNST_COLOR_BK_OVER鼠标悬停时背景色增加交互反馈BTNST_COLOR_BK_DOWN鼠标按下时背景色模拟按钮被按压BTNST_COLOR_BK_FOCUS键盘聚焦时背景色无障碍场景BTNST_COLOR_BK_DISABLED禁用时背景色灰色置灰BTNST_COLOR_TEXT_OUT普通状态文字颜色保证文字可读性SetFlat控制的是边框行为。SetFlat(TRUE)时按钮默认没有边框悬停时才画出边框SetFlat(FALSE)时按钮始终有一个平面边框但不会像老式按钮那样有立体凸起。两者在视觉差异上比较微妙我一般把前者用于工具栏按钮后者用于对话框主操作按钮。SetFont用于修改按钮文字字体。CButtonST 的字体设置会影响所有状态的文字但不会影响图标位置。一个常见的问题是设置了漂亮的文字字体后按钮高度不够文字被截断。这是因为设置字体后按钮的客户区不会自动调整。你需要自己用MoveWindow或SetWindowPos把按钮稍微加高几个像素或者在设计对话框时预留足够高度。这个问题在 XP 时代还不明显因为默认字体比较小到了现代 Windows 上系统字体变大按钮高度不足的问题会频繁冒出来。4.2 位图按钮的透明处理普通 BMP 与 PNG 的差别很多教程会提到SetBitmaps函数用它给按钮设置三张位图分别对应普通、按下和禁用状态。这个方法在旧项目里很常见但它有天然的局限位图只能是 BMP 格式BMP 不支持 alpha 通道透明。结果就是按钮背景图如果不和对话框背景完全一致就会出现一块方形的底色边界。修正版在 VS2012 下编译通过的场景里如果你要处理的是带透明效果的图标按钮我的建议是放弃SetBitmaps改用SetIcon配合透明的 ICO 文件或者直接在按钮上贴 PNG 并用自绘代码处理 alpha 混合。ICO 可以包含 alpha 通道透明度能完美呈现而且LoadImage加载 ICO 比解析 PNG 简单得多不依赖额外库。如果必须用位图注意SetBitmaps的三个句柄参数它们分别是默认位图、按下位图和禁用位图。所有位图都应该是同一尺寸否则按钮会按照第一张位图的大小进行拉伸第二、第三张会被缩放观感不一致。位图句柄的生命周期跟图标一样要么用LR_SHARED加载要么在按钮销毁时手动释放。4.3 与 VS2012 MFC 功能包控件的取舍VS2012 自带 MFC 功能包里面也有按钮美化能力CMFCButton 支持直接设置颜色、图标、平面样式而且集成度更高不需要手动处理 BS_OWNERDRAW。既然微软官方给了方案为什么还要用 CButtonST差别在于两点。CMFCButton 的样式变化虽然方便但它依赖 MFC 功能包的整体初始化环境。如果项目里还有其他地方禁用了视觉管理器CMFCButton 的外观会退化得很难看。而 CButtonST 是纯 GDI 绘制不依赖 visual manager在任何 Windows 版本上的表现都相对稳定。另一层是代码迁移成本。老项目里已经有大量基于 CButtonST 的代码方法名和参数格式都是现成的换成 CMFCButton 需要重写所有调用。对于修正版已经解决了编译问题的场景继续用 CButtonST 是最小改动方案。但如果全新开始写一个对话框我会直接上 CMFCButton毕竟是官方维护的控件未来升级编译器时的兼容成本更低。这个选择不关乎技术优劣纯粹看工程历史包袱。5. CButtonST 修正版避坑指南VS2012 编译与运行期的踩坑记录5.1 编译时报 C4819 警告刷屏真正的错误被淹没现象一编译 CButtonST.cpp输出窗口瞬间几百条 C4819 警告都是同一个文件内容指向“无法将 ANSI 字符转换为 Unicode”但程序本身还能编译通过只是输出窗口没法看了。原因源码文件以 ANSI 编码保存且包含非英文字符。VS2012 默认按当前系统代码页解析源文件当代码页切换或文件里的字符不在系统代码页内时编译器逐字符报警告。解决最干净的做法是让源码文件带 BOM 的 UTF-8 格式。在 VS2012 里打开 CButtonST.cpp选择“文件 高级保存选项”把编码改为“Unicode (UTF-8 带签名) - 代码页 65001”。改完后再编译C4819 会消失。如果不方便改文件编码也可以在文件开头加一行#pragma warning(disable: 4819)把它放在包含其他头文件之前。注意这个 pragma 只管当前编译单元不影响工程的其它文件。5.2 错误 C2664无法将“const char *”转换为“LPCTSTR”现象编译到代码里出现大量 C2664 错误都是字符串参数不匹配。明明原版代码在 VC6 下跑得好好的到 VS2012 直接编不过。原因工程字符集是 UnicodeLPCTSTR 展开为const wchar_t*而源码里直接写了中文字符串字面量类型是const char[]。老版编译器对隐式转换比较宽容VS2012 不再容忍。解决把 CButtonST.cpp 里所有的字符串字面量包上_T()宏。例如SetWindowText(Login)改成SetWindowText(_T(Login))。如果修改范围太大可以临时把工程字符集改成“多字节字符集”但这会导致界面上的宽字符处理变得麻烦不是长久之计。最稳妥的做法是一处处替换干净。修正版源码如果已经处理过这个问题你拿到手后直接编译应该不会遇到遇到的话就要逐一排查。5.3 链接错误 LNK2005一个变量被定义了两次现象编译通过但链接时报 LNK2005通常指向某个 buffer 或字符串变量提示存在多个定义。原因CButtonST.cpp 里某个全局变量被定义在头文件中而且这个头文件被多处包含。老版工程里这种事很常见因为早期编译器允许一定程度上的重复定义延后处理VS2012 的链接器按严格规则报错。解决检查头文件中的全局变量声明改为extern声明在 .cpp 中集中定义。如果变量是 const 字符串可以用static const代替给每个编译单元一份内部副本避免跨文件共享。修改后重新编译整个工程链接错误会消失。这个问题在修正版里一般已经处理掉了但在你拿到任意一个基于 V3.9 魔改的版本时仍需警惕。5.4 按钮销毁时程序崩溃或 GDI 泄漏现象程序正常使用按钮也能显示但关闭对话框时偶尔崩溃或者长时间运行后按钮图标消失、界面刷新异常。原因按钮设置过的图标或位图句柄没有被释放。CButtonST 的析构函数默认会释放它自己创建的一些内部资源但通过SetIcon从外部传入的句柄它不会擅自帮你销毁。加没加LR_SHARED直接决定这个句柄能否由系统回收。解决使用SetIcon时统一用LR_SHARED加载图标这样句柄由系统管理无需手动释放也不会 GDI 泄漏。如果你明确知道某个 hIcon 需要自己释放那就在按钮销毁前也就是对话框的OnDestroy中调用::DestroyIcon然后置空句柄。这里没有中间选项两种方式选一种必须执行到底否则都是隐患。5.5 按钮看起来还是老样子没有任何自绘效果现象所有设置代码都写了编译运行都正常但界面上按钮跟普通按钮一样没有颜色变化也没有平面效果。原因典型的BS_OWNERDRAW没设置成功。可能有几种情况你在代码里调用了ModifyStyle但按钮控件变量绑定错误实际拿到的是另一个控件的句柄或者按钮在资源脚本里已经被设置了BS_OWNERDRAW但类变量没有正确执行DDX_Control导致 SetFlat 的代码作用在一个无效对象上。解决先在OnInitDialog里加一个断言验证控件句柄ASSERT(m_btnLogin.GetSafeHwnd() ! NULL); // 确认按钮样式包含自绘标志 UINT nStyle m_btnLogin.GetButtonStyle(); TRACE(_T(Button style 0x%X\n), nStyle);运行后在输出窗口确认nStyle是否包含BS_OWNERDRAW数值上对应 0x0000000B 与其它样式位的组合。如果样式正确依然没有自绘效果检查是不是按钮类变量没有在DoDataExchange里绑定直接用GetDlgItem获取指针或改用SubclassDlgItem绑定问题一般就会解决。6. 进阶用法继承修正版写自己的按钮类避开最后一处暗伤6.1 继承 CButtonST把交互逻辑封装起来CButtonST 本身是一个可继承的类。在项目里如果有多处地方用到同一种按钮外观比如统一的主题色背景、统一的悬停加深效果与其在每个对话框里写一堆SetColor不如定义一个派生类一次性搞定外观。#pragma once #include CButtonST.h // 自定义主题按钮统一品牌色背景与悬停反馈 class CThemeButton : public CButtonST { public: CThemeButton() { // 普通状态深蓝色背景、白字 SetColor(BTNST_COLOR_BK_OUT, RGB(20, 100, 200)); SetColor(BTNST_COLOR_TEXT_OUT, RGB(255, 255, 255)); // 悬停状态比普通状态亮的蓝 SetColor(BTNST_COLOR_BK_OVER, RGB(40, 120, 220)); // 按下状态更深的蓝 SetColor(BTNST_COLOR_BK_DOWN, RGB(10, 70, 160)); // 禁用状态灰色文字淡一些 SetColor(BTNST_COLOR_BK_DISABLED, RGB(200, 200, 200)); SetColor(BTNST_COLOR_TEXT_DISABLED, RGB(120, 120, 120)); // 全部按钮统一使用平面样式 SetFlat(TRUE); } };在构造函数里直接调用 SetColor 这样的方法是可以的因为 CButtonST 的颜色值存在内部成员变量中不依赖窗口创建。但要注意SetFont不要放在构造函数里因为字体设置需要窗口句柄。放到OnInitDialog阶段或PreSubclassWindow中处理更合适。派生类的好处是你在对话框里只需要把控件变量声明为CThemeButton代码立刻从繁琐的设置序列变成一行声明。如果后续要改整个产品的主题色只需要改这一个类配合全局搜索按钮变量类型改动量可以压缩到很小。6.2 处理键盘焦点自绘按钮容易忽略的可访问性细节自绘按钮在视觉上如果处理不好焦点框用户按 Tab 键时根本分不清焦点落在哪个按钮上。CButtonST 默认会画一个焦点虚线框但这个框有时不太明显尤其当按钮是平面样式且背景色较深时。可以重写OnDrawFocusBorder部分版本叫DrawFocusRect来强化它void CThemeButton::OnDrawFocusBorder(CDC* pDC, CRect rcClient) { // 用一个浅色虚线框替代默认样式 CBrush br(RGB(255, 255, 0)); pDC-FrameRect(rcClient, br); }这样焦点状态会非常醒目视觉障碍用户在使用 Tab 键操作时能清晰看到当前聚焦位置。这块代码在老项目的审计要求里经常被点名因为很多自绘按钮完全忽略了焦点可视化。你的按钮不画焦点框不算 bug但算体验缺陷客户验收时容易挑刺。6.3 验证自绘是否真生效Spy 看窗口类名不算按钮消息才是关键界面效果跑出来了但怎么确认代码逻辑确实执行了两个办法。第一在 OnDrawItem 里加一个临时断点调试运行时如果断点命中说明消息反射链路是通的。第二用 Spy 或者 VS2012 自带的消息监视工具查看鼠标移动到按钮上时控件窗口是否收到 WM_MOUSEHOVER以及父窗口是否收到 WM_DRAWITEM。这里有一个我自己的习惯不在 OnDrawItem 里做太重的计算。自绘按钮的 OnDrawItem 在鼠标移动、窗口重绘、遮挡变化时都会触发频率很高。如果你在绘制代码里做了 GDI 对象创建、字体创建、字符串格式化性能会肉眼可见地下降。CButtonST 修正版里很多绘制优化做得不错但你自己额外加的绘制逻辑要遵循同一个原则能预计算的不要在绘制函数里做。实际项目里我曾遇到过一个离奇问题按钮绘制偶尔闪烁后来排查发现是在 OnDrawItem 里调用了RedrawWindow等于绘制过程触发自我重绘直接死循环。后来把这个调用去掉改为在状态变化时请求重绘问题消失。这个教训比较典型写在这里给你提个醒自绘代码里不要主动触发跟当前绘制同一窗口的重绘函数否则表现就是间歇性闪烁时好时坏很难排查。CButtonST 这套东西说不上新但它在一个相当长的周期里帮老项目解决按钮观感的问题而且修正版能在 VS2012 下顺利编译至少省去了你为老控件更换编译器的折腾。用它的核心不是看懂每一行绘制代码而是掌握那几个设置接口、理解句柄生命周期、知道自绘依赖 BS_OWNERDRAW 样式。把这三点做好按钮自绘这条路基本就通了。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑