ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MFC自绘按钮图标:CButtonST库在VC6老项目中的集成与避坑指南

MFC自绘按钮图标:CButtonST库在VC6老项目中的集成与避坑指南 简介面向VC/MFC开发者的按钮定制DEMO包专为需要在Windows桌面应用中美化控件、增强交互的开发者设计。包内基于CButtonST扩展库通过多个可运行示例演示了如何为普通按钮添加图标、切换不同状态下的文字颜色与背景、调整文字与图标相对位置以及实现透明、阴影等特殊视觉效果有效解决默认按钮样式单一的问题。压缩包仅227KB共89个文件其中包含35个ico图标与8个bmp位图作为图形素材18个h头文件与17个cpp源文件构成完整的MFC工程代码另有rc资源脚本、项目配置与说明文档目录结构清晰便于逐模块对照学习。已有107人学习浏览。除核心按钮扩展类外工程还附带菜单、超链接、阴影按钮等多个辅助类源码开发者可以从中掌握SetIcon动态设置图标、响应消息自定义绘制等关键技巧并观察这些技术在不同对话框中的实际组合运用快速迁移到自己的项目中提升界面美观度与交互体验也能获得从资源加载、控件自绘到消息处理的完整思路。1. CButtonST在 VC 里给按钮加图标为什么还要翻出这个老库最近在维护一批十年前的老系统客户提了个不算过分的要求把界面上几个“光秃秃”的按钮换成带图标的样式。打开工程一看VC6 MFC 的经典组合不想为这点功能上重框架也不想引一堆依赖于是想到了 CButtonST——这个在 CodeProject 上存活了十几年的按钮类库。CButtonST 不是给按钮贴张图那么简单它接管了按钮的绘制逻辑支持图标 文字混排、XP 风格、气泡提示和多状态切换。这次要说的 CButtonST_demo.rar 正是作者随源码附带的演示工程里面有完整的按钮图标示例从资源加载到状态切换全都有现成代码可抄。如果你也是用 VC 6.0 / VS2005 / VS2008 维护老项目或者新项目想用纯 MFC 实现按钮图标而不引入 UI 框架这篇笔记就是按“把 demo 跑起来 → 看懂关键参数 → 抽代码到正式项目 → 踩坑排查”的顺序写的。文章只基于我自己的使用经验不牵扯任何源码包内部细节重点是让你拿这个 demo 能真正落地。2. 拆开 CButtonST_demo消息映射与自绘机制是怎么串起来的2.1 为什么 CButtonST 能“一个类解决所有按钮样式”MFC 里写一个图标按钮常规思路是派生 CButton 重写 DrawItem或者干脆用 Owner-Draw 自绘。CButtonST 走的就是后者但它把“自绘”这件事封装得特别彻底。你不需要关心 WM_DRAWITEM 怎么处理也不需要手动处理 BM_SETIMAGE 和图片重绘之间的冲突因为 CButtonST 自己维护了九种样式位包括图标位置、文字对齐、背景透明、边框样式、气泡提示等全部通过成员变量和枚举来控制。它的内部结构大致是CButtonST 派生自 CButton重载了 DrawItem 和 OnPaint绘制时先判断当前状态正常 / 按下 / 悬浮 / 禁用再从成员变量里读图标句柄和文字内容。图标不是直接贴在按钮上而是通过 DrawIconEx 按指定位置和尺寸画到按钮客户区。所以它能在 XP 风格按钮上叠加图标而不破坏原有的圆角背景这点是单纯用 BitBlt 贴图做不到的。这个机制带来的直接好处是你在 demo 里看到的每个按钮代码差异往往只在设置图标和样式的那几行其余绘制逻辑全部是同一个类在兜底。2.2 从 demo 工程里找到“最小可抄代码”打开 CButtonST_demo.rar 解压后的工程先不要急着编译按下面几步定位关键代码用 VC6 打开 .dsw 或 .dsp 工程文件在资源视图里找到主对话框往界面上拖几个 CButton保持默认 ID如 IDC_BUTTON1在对话框头文件里包含 CButtonST.h然后把按钮成员变量类型从 CButton 改成 CButtonST在 OnInitDialog 里加图标加载和样式设置编译运行看按钮效果。这个流程里最容易出问题的是第三步MFC 的 DDX 绑定默认只认 CButton直接改成 CButtonST 后如果你的工程没有把 CButtonST.cpp 加进编译链接阶段会报 unresolved external symbol。所以先确认 CButtonST.cpp 已经在工程里。下面这段是 demo 里最常见的初始化代码我稍微整理过// 在对话框类的 OnInitDialog 中 // 设置图标按钮的关键参数不超过 5 行 m_btnImg.SetIcon(IDI_ICON_OPEN); // 从资源加载图标 m_btnImg.SetIconFixedSize(24, 24); // 固定图标绘制尺寸避免高 DPI 下变形 m_btnImg.SetText(打开文件); // 设置按钮文字和图标同时显示 m_btnImg.SetTooltipText(选择需要导入的源文件); // 悬浮提示可直接显示中文 m_btnImg.SetFlat(FALSE); // 关闭扁平样式保留按钮立体感这几行代码的逻辑是先给按钮赋图标资源然后告诉绘制引擎“图标不要按原图大小画按 24x24 画”这样换电脑、换图标资源都不会撑破按钮边框。SetText 在 MFC 里和 SetWindowText 效果类似但 CButtonST 内部会把字符串存起来绘制时优先使用内部值而不是直接读窗口文本避免和自绘逻辑冲突。SetFlat 是控制是否画成平面按钮的关键开关默认是 TRUE如果你发现按钮没有立体感大概率是这个参数没设。参数说明SetIconFixedSize的两个参数分别是宽高像素值。不调用这个函数时CButtonST 会按图标原始尺寸绘制但同一个按钮如果中途换了图标尺寸不一致会闪。第一次写代码就固定尺寸后面省事。2.3 编译链接失败的两个典型卡点把 demo 编译运行最常遇到的是 cl.exe 报错或链接时找不到符号。第一类问题是环境变量VC6 装在非默认路径或者 Win7 以上系统里打开老工程时IDE 找不到标准库目录报错形如Cannot open include file: afxwin.h。这个和代码无关进入 Tools - Options - Directories把所有 VC6 安装目录下的 Include 和 Lib 路径重新指定一遍就好。第二类问题是工程配置CButtonST_demo 解压后如果直接用高版本 VS 打开会因为 MBCS 字符集问题报编译错。VC6 默认多字节字符集VS2010 以上默认 Unicode。解决办法是工程属性 - 字符集 - 使用多字节字符集。如果项目必须用 Unicode就要把代码里的 CString 转成 CStringW或者用 SetTooltipText 时传入 UNICODE 字符串否则气泡提示显示乱码。链接阶段如果报CButtonST::SetIconunresolved先检查是不是漏加了 CButtonST.cpp再看是不是 VC6 的 GDI 库没链接。CButtonST 绘制气泡提示时需要 GDI把它依赖的库加进工程设置链接错误自然消失。3. 看懂按钮图标的绘制顺序才能调得对位置3.1 绘制顺序背景、边框、图标、文字前后有讲究用 CButtonST 做按钮图标最容易翻车的地方不是“图标不显示”而是图标显示位置不对、文字被图标挡住、或者图标在按下时消失。这些问题的根源在于不理解 CButtonST 的绘制顺序。CButtonST 的 DrawItem 执行顺序大致是先按当前状态正常/按下/禁用画按钮外观也就是背景和边框然后画图标最后画文字。图标和文字的绘制坐标是通过 CalculateRect 算出来的这个函数会读内部的边距成员变量比如 SetIconAlignment 控制的图标对齐方式以及文字对齐方式默认图标在左、文字在右。// demo 里调整图标位置的常用写法 m_btnImg.SetIconAlignment(CButtonST::BTNST_ALIGN_LEFT); // 图标靠左 m_btnImg.SetTextAlignment(CButtonST::BTNST_ALIGN_LEFT); // 文字靠左紧挨图标 m_btnImg.SetOffset(2, 2); // 整体往右下偏移 2 像素这段代码解决的是“图标贴到按钮左边框上”的问题。SetOffset 前两个参数是水平、垂直偏移在自绘按钮里等价于外边距。如果你想让图标居中、文字靠下就把图标对齐改为BTNST_ALIGN_CENTER文字对齐改为BTNST_ALIGN_BOTTOM。CButtonST 内部会根据按钮客户区尺寸自行计算绘制坐标不需要手工算像素位置这是它比直接用 DrawIcon 的方便之处。按压效果是另一个需要关注的细节按钮按下时 CButtonST 默认会把图标和文字整体向右下方偏移 1 像素模拟机械按压感。如果你觉得按下去“跳得太厉害”可以设置SetPressedOffset(0, 0)取消这个位移。这个参数 demo 里很少改但用在工具栏风格的扁平按钮上很管用。3.2 图标资源是 ICO 还是 BMP决定了你的调用方式CButtonST 的 SetIcon 有两个重载一个接受 HICON 句柄一个接受资源 ID。demo 里通常用资源 ID 版本。如果你把 PNG 或 BMP 转成 ICO 资源放进 .rc 文件调用 SetIcon(IDI_MYICON) 没反应先检查资源类型是否真的是 ICON。常见误解是资源管理器里显示“图标”的资源就是 ICON 类型。其实在 VC 的资源编辑器里你可以添加 ICO 文件为ICON类型也可以作为BITMAP类型。CButtonST 的 SetIcon 只认 ICON。如果手上只有 BMP有两条路用CImage::Load读入 BMP再ExtractIcon转成图标直接把 BMP 作为背景贴图用SetBitmap而不是 SetIcon。// 从磁盘加载 BMP 并转换成图标 CImage img; img.Load(_T(button_icon.bmp)); HICON hIcon img.ExtractIcon(); // 从 CImage 生成图标句柄 m_btnImg.SetIcon(hIcon); // 传入句柄版重载 m_btnImg.SetIconFixedSize(32, 32);注意ExtractIcon是 CImage 提供的成员不是系统 API。它生成的句柄由 CButtonST 内部管理不需要手动 DestroyIcon但如果你是在临时变量里生成的图标赋值给按钮后就不要再去销毁那同一个句柄否则下次重绘时按钮访问到野指针程序直接崩溃。这块是很多新手绕不过去的坑后面避坑章节再细说。3.3 悬浮、按下、禁用三种状态怎么让图标跟着变按钮图标不是静态画一张就完事。CButtonST 的完整能力是每个状态都可以配不同的图标和文字颜色。demo 里有一个专门的区域展示不同状态切换悬浮时图标变亮、按下时图标变暗、禁用时图标灰掉。// 状态图标设置的完整版本 m_btnImg.SetIcon(IDI_ICON_NORMAL); // 正常状态 m_btnImg.SetIcon(IDI_ICON_HOVER, CButtonST::BTNST_STATE_HOVER); // 悬浮状态 m_btnImg.SetIcon(IDI_ICON_PRESSED, CButtonST::BTNST_STATE_PRESSED); // 按下状态 m_btnImg.SetIcon(IDI_ICON_DISABLED, CButtonST::BTNST_STATE_DISABLED); // 禁用状态这个功能在工具栏按钮、播放器控制按钮里非常实用。CButtonST 内部根据当前鼠标是否在按钮区域、是否按下左键决定用哪套图标。但有个前提按钮必须启用了 BS_OWNERDRAW 样式。CButtonST 在 PreSubclassWindow 里会自动给按钮追加这个样式如果按钮是通过 DDX 绑定的这个过程是自动的。如果你发现设置了不同状态图标但切换不生效检查按钮是否被EnableWindow(FALSE)禁用了以及鼠标消息是不是被父窗口拦截了。还有个容易被忽略的CButtonST 默认不启用状态图标切换需要调用SetState(CButtonST::BTNST_STATE_HOVER)触发一次重绘或者干脆在初始化时把三个状态的图标全部设置完成它内部会自己判断当前状态并选择图标。实测发现设置完所有状态图标后调用Invalidate()一次效果最稳。4. 把 demo 的按钮图标逻辑抽进正式项目从对话框扩展到自定义控件4.1 集成 CButtonST 到现有工程的三个步骤避开“只抄到一半”的坑demo 跑通之后把代码往正式项目里搬是个体力活但有几个位置容易搬一半就出错。首先要确认你的正式工程用了什么消息映射体系。demo 是基于 MFC 对话框的而正式项目如果用了 CFormView 或者无模式对话框按钮的 DDX 绑定位置不同但 CButtonST 本身不挑宿主Bind 的过程是一致的。第一步把 CButtonST.h 和 CButtonST.cpp 复制到你的工程目录然后在 stdafx.h 里加#include CButtonST.h。如果工程用了预编译头必须放在 stdafx.h 中不能放在 CPP 文件头部否则编译时报 ActiveX 控件无法实例化之类的一堆奇怪错误。第二步头文件中声明成员变量。注意类型写 CButtonST不是 CButton// MainDlg.h 中 #include CButtonST.h class CMainDlg : public CDialog { protected: CButtonST m_btnOpen; // 直接声明即可不需要手工 SubclassDlgItem };第三步在 DoDataExchange 中把按钮和变量绑定跟绑 CButton 一模一样void CMainDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Control(pDX, IDC_BTN_OPEN, m_btnOpen); }这三步走完m_btnOpen 就可以直接调用 SetIcon 等成员函数。很多人漏掉第二步的类型替换只在资源文件里改了控件类型DDX_Control 照样绑但编译报错提示“不明确的 CButtonST::CButtonST 构造”原因就是头文件引用放错了位置。4.2 给按钮配图标资源资源 ID 的命名和 .rc 文件维护正式项目里图标资源管理是个容易被忽略但很重要的环节。demo 里图标资源少随便命名没关系正式项目按钮多起来资源文件里图标 ID 混乱会导致后期维护痛苦。我的习惯是用前缀区分用途IDI_BTN_OPEN、IDI_BTN_SAVE、IDI_BTN_CLOSE。在 .rc 文件里加上IDI_BTN_OPEN ICON res\\btn_open.ico IDI_BTN_SAVE ICON res\\btn_save.ico然后在 stdafx.h 或 resource.h 里定义资源 ID。CButtonST 直接按 ID 加载不需要额外代码。如果你在资源编辑器里改了图标文件路径记得在 .rc 文件里同步更新否则编译通过但运行时不显示图标因为资源没有真正编译进目标文件。图标资源的大小规范也很重要。CButtonST 配合 SetIconFixedSize可以加载任意尺寸的 ICO但建议在 16、24、32 之间选缩放效果最好。用过 48x48 的图标强制缩到 16x16文字模糊、锯齿严重视觉效果大打折扣。图标的 DPI 适配在 MFC 里本来就麻烦最省事的是直接准备多尺寸 ICO 文件VC6 支持在一个 .ico 里放多种尺寸运行时 Windows 自己选合适的那个。4.3 动态创建按钮不依赖资源对话框的图标按钮怎么做不是所有按钮都放在对话框模板里。如果你在代码里动态创建按钮CButtonST 的用法要稍微调整。核心问题是动态创建的窗口必须先 Create然后 CButtonST 的成员函数才能安全调用。// 动态创建带图标按钮的完整流程 m_btnDynamic.CButtonST::Create(_T(), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, CRect(10, 10, 110, 40), this, IDC_BTN_DYNAMIC); m_btnDynamic.SetIcon(IDI_BTN_ADD, CButtonST::BTNST_ALIGN_LEFT); m_btnDynamic.SetText(增加记录); m_btnDynamic.SetTooltipText(_T(新增一条记录));这段代码里m_btnDynamic必须先在栈上或堆上构造好再用带 CButtonST 作用域的 Create 创建窗口。注意我们没有手动加 BS_OWNERDRAW但 CButtonST 的 Create 内部会帮你处理样式追加不需要像 CButton 那样手动指定。动态创建模式下的资源释放同样要注意CButtonST 析构时会自动清理图标句柄。如果你在别处把图标句柄同时赋给了两个按钮第一个按钮销毁后第二个按钮就变成野句柄了。血泪经验是共享句柄要嘛用引用计数机制要嘛每个按钮单独加载一份资源不要图省事共用同一份。5. 按钮图标显示异常排查6 个必须提前知道的坑5.1 图标不显示加载成功但绘制不出来现象SetIcon 调用了资源 ID 也对编译不报错但运行起来按钮上只有文字没有图标。原因最常见的是按钮控件在资源模板里没有约定为自绘样式。CButtonST 虽然是 CButton 的子类但如果资源模板中按钮没有勾选 Owner Draw 属性DDX_Control 绑定后 PreSubclassWindow 可能会被 MFC 跳过导致控件没有被正确子类化。解决在每个 CButtonST 变量绑定后手动调用m_btnOpen.SubclassDlgItem(IDC_BTN_OPEN, this);主动替换控件类。注意这时候 DoDataExchange 中不要再用 DDX_Control 绑定同一个控件两者会冲突。代码改成在 OnInitDialog 里手动 Subclass。5.2 图标显示但背景发白和按钮颜色不融合现象图标画出来了但图标周围有一整块白色背景把 XP 风格的按钮背景挡住了一块。原因ICO 文件内部包含了颜色背景。很多图标制作工具导出的 32 位 ICO 实际上是“带背景色的矩形”中间的透明区域只有 8 位掩码导致 CButtonST 绘制时按掩码透明但掩码边缘有白边。解决换用真 32 位带 Alpha 通道的 ICO。检查方法是把 ICO 放到 Windows 资源管理器里放大 800%看透明区域是不是格子状如果是纯白/纯黑不规则背景就说明不是真透明。另外在代码里SetIcon的图标显示效果受 CButtonST 内部透明标志影响可以尝试SetTransparent(TRUE)强制让按钮背景参与混合。5.3 高 DPI 下图标变小按钮整体错位现象在 125% 或 150% 缩放的屏幕下按钮图标明显变小文字和图标挤在一起有时按钮高度都不对。原因MFC 高 DPI 适配本身就是个老大难CButtonST 的量算基于逻辑像素如果对话框没有调用 SetProcessDPIAware系统会按 96 DPI 缩放按钮尺寸图标按固定 24x24 绘制缩放后看起来就小了。解决第一个办法是SetIconFixedSize里把尺寸设置成和按钮高度的比例而不是固定值。第二个办法是在程序初始化时声明 DPI 感知// 在 WinMain 或 InitInstance 最前面 SetProcessDPIAware();如果项目不能牺牲高 DPI 兼容性那就老老实实用 DTU 动态计算根据 GetDeviceCaps 拿到的 LOGPIXELSX把 24 乘上缩放比例。5.4 禁用状态的图标没有自动变灰按钮看起来还像可点的现象按钮 EnableWindow(FALSE) 了但图标还是彩色的和正常态区别不大。原因CButtonST 只有在显式设置过禁用状态图标时才在禁用时切换图标否则它继续用正常状态图标绘制。它不是自动把彩色图标转成灰度的机制。解决准备一幅灰度版图标用 SetIcon 配合 BTNST_STATE_DISABLED 设置。或者更省事的方法在 EnableWindow 之前先m_btnImg.SetIcon(IDI_ICON_DISABLED, CButtonST::BTNST_STATE_DISABLED);设置好再禁用。如果不想准备额外图标可以 Hook WM_ENABLE 消息在收到消息时用 CButtonST::DrawTransparent 重新把按钮灰掉。5.5 点击按钮后图标闪烁或消失现象按钮能正常点击但在按下和弹起切换的瞬间图标会闪烁一下或者消失再出现。原因CButtonST 的按下状态图标没有预设内部处理是按下时不绘制图标或者绘制时因为状态切换导致绘制线程重入。常见触发条件是按下时调用了 SetIcon 动态换图标和绘制线程撞车。解决不要频繁设置图标按钮初始化时一次性把所有状态图标设置完。如果必须要动态换图标调用 SetRedraw(FALSE) 冻结绘制设置完成后再 SetRedraw(TRUE) 恢复。实测这种方法能消除 90% 的闪烁问题。5.6 气泡提示不显示Tooltip 没反应现象SetTooltipText 调用了但鼠标悬停在按钮上半秒钟没有文字提示。原因CButtonST 的气泡提示需要一个 GToolTip 的封装类它在类内部自动激活。但如果你在按钮上同时叠加了其他 Tooltip 控件消息会被截胡。解决给按钮加上 TTS_ALWAYSTIP 样式。还需要检查按钮是否设置了BS_NOTIFY样式因为 Tooltip 激活依赖 TTM_RELAYEVENT 传递鼠标消息没有 BS_NOTIFY 的按钮可能不转发相关消息。6. 把 CButtonST 的按钮图标做成一键换肤状态切换的最佳实践6.1 用图标数组 循环加载管理多套按钮图标正式项目里按钮一多逐个写 SetIcon 代码很啰嗦。可以做一个简单的表驱动方案定义按钮 ID 和对应颜色方案的映射用一个循环批量注册。// 批量初始化按钮图标 typedef struct _TAG_BTN_ICON { UINT nCtrlID; UINT nIconID; UINT nHoverID; } BTN_ICON_T; static BTN_ICON_T s_btnMap[] { { IDC_BTN_OPEN, IDI_BTN_OPEN, IDI_BTN_OPEN_HOVER }, { IDC_BTN_SAVE, IDI_BTN_SAVE, IDI_BTN_SAVE_HOVER }, { IDC_BTN_CLOSE, IDI_BTN_CLOSE, IDI_BTN_CLOSE_HOVER }, }; // 在 OnInitDialog 里遍历自动绑定 CButtonST* pBtn NULL; for (int i 0; i sizeof(s_btnMap) / sizeof(s_btnMap[0]); i) { pBtn static_castCButtonST*(GetDlgItem(s_btnMap[i].nCtrlID)); if (pBtn) { pBtn-SetIcon(s_btnMap[i].nIconID); pBtn-SetIcon(s_btnMap[i].nHoverID, CButtonST::BTNST_STATE_HOVER); } }这段代码解决的是“几十个按钮批量加图标”的场景。需要说明的是GetDlgItem 返回 CWnd*这里强转成 CButtonST* 是安全的因为资源模板里这些按钮已经通过 DDX_Control 绑定成 CButtonST 了。如果按钮还没绑定这行代码会踩空指针。所以批量初始化的前提是 DoDataExchange 已经绑定完毕。6.2 换肤时全局切换图标从按钮到框架的一致性换肤的本质是替换图标资源但如果每个按钮直接 SetIcon代码会散落各处。更好的做法是给对话框加一个统一入口void CMainDlg::ApplyTheme(UINT nThemeID) { // 先遍历所有按钮重置图标 CWnd* pChild GetWindow(GW_CHILD); while (pChild) { if (pChild-IsKindOf(RUNTIME_CLASS(CButtonST))) { CButtonST* pBtn static_castCButtonST*(pChild); // 按主题 ID 从资源加载对应图标 pBtn-SetIcon(GetIconIDForTheme(pBtn-GetDlgCtrlID(), nThemeID)); } pChild pChild-GetNextWindow(); } Invalidate(); }这段代码用了 IsKindOf 判断控件类型好处是 DP 上哪些按钮生效、哪些不生效一目了然。注意换肤函数不要在 WM_PAINT 里调用否则每帧都在重设图标性能跟不上。一般在菜单或配置页变化时调用一次。深色主题下还要同步修改按钮文字颜色用 SetTextColor 配合 CButtonST 的内部绘制机制它会在绘制文字时用这个颜色。6.3 验证绘制效果快速切换状态检查图标是否匹配最后一步是验证。CButtonST 的图标状态切换在非交互场景下肉眼不容易看全我一般会写一段短暂的自动演示代码让每个按钮按“正常 → 悬浮 → 按下 → 禁用”轮播一圈用截图对比图标是否切换正确。// 调试用自动触发状态切换 void CMainDlg::DebugCycleButtonStates() { CButtonST* pBtn m_btnOpen; pBtn-SetState(CButtonST::BTNST_STATE_HOVER); Sleep(500); pBtn-SetState(CButtonST::BTNST_STATE_PRESSED); Sleep(500); pBtn-SetState(CButtonST::BTNST_STATE_DISABLED); Sleep(500); pBtn-SetState(CButtonST::BTNST_STATE_NORMAL); }这段代码只用于调试发布前记得删除。SetState 是 CButtonST 显式设置当前模拟状态的方法它不会真的改变鼠标交互逻辑只是强制按某个状态绘制。如果你发现设置了悬浮相状态但按钮没有反应检查 CButtonST 是否启用了鼠标追踪这个需要 WM_MOUSEMOVE 消息的配合。实际上我常用的验证方法是把按钮放成一条鼠标依次滑过肉眼观察图标切换是否流畅。CButtonST 的切换本身不带过渡动画切换的一瞬间如果出现闪烁说明当前状态图标资源尺寸不一致或者 GDI 绘制出现了重影。把 SetIconFixedSize 统一一下基本能解决。CButtonST 这个库虽然老但在 VC/MFC 的体系下仍然是最省事的按钮图标方案。它不像 DirectUI 那样动辄上万行代码也不像贴背景图那样死板。只要理解了绘制顺序和状态机制它能做到的效果其实比新框架更细腻。我在项目里用下来最大的感触是这套“图标 文字 气泡提示 状态切换”的按钮框架对于传统 MFC 界面来说已经是性价比最高的升级路径了。希望这篇笔记能帮你在自己的工程里少走几步弯路。本文还有配套的精品资源点击获取
返回列表