一、前言
在 MFC 原生开发中,系统自带的CProgressCtrl进度条样式固定、自定义空间极小,很难实现自定义边框、纯色填充、进度文字居中显示 这类定制 UI 需求。 本文通过继承CStatic静态文本控件,从零封装一个高复用自定义进度条,实现效果:
- 黑色实线外边框,空白区域白色底色
- 进度区域纯绿色填充,支持 0~100 区间动态切换
- 百分比文字自动水平 + 垂直精准居中,适配 0%/70%/100% 不同长度文本
- 对外提供极简
SetProgress接口,一行代码切换进度值 - 兼容对话框工程,仅需绑定 Static 控件即可使用
最终界面效果:底部按钮点击切换进度,进度条实时重绘更新文字与填充区域。
二、整体设计思路
- 基类选择 :继承
CStatic,对话框资源直接拖入 Static 文本控件,无需自定义资源 ID 模板,上手简单; - 绘图逻辑 :重写
WM_PAINT消息OnPaint函数,分层绘制:外框底色→进度填充→居中文字; - 进度约束 :接口内部自动截断数值到
0~100,避免越界绘制; - 文字居中核心 :使用
GetTextExtent获取文字真实宽高,通过矩形中心坐标减去文字半宽 / 半高,实现完全居中,摒弃固定像素偏移写法; - 刷新机制 :修改进度值后调用
Invalidate()触发重绘,界面实时更新。
三、完整控件源码
3.1 头文件 ProgressStatic.h
cpp
#pragma once
#include <afxwin.h>
// 自定义进度条控件,继承CStatic
class CProgressStatic : public CStatic
{
// MFC动态创建宏,支持DDX_Control绑定对话框控件
DECLARE_DYNCREATE(CProgressStatic)
public:
CProgressStatic();
virtual ~CProgressStatic();
// 对外接口:设置进度值,范围0~100
void SetProgress(int nPercent);
protected:
int m_nPercent; // 存储当前进度数值 0~100
// 消息映射声明
DECLARE_MESSAGE_MAP()
// 重绘消息响应函数
afx_msg void OnPaint();
};
3.2 实现文件 ProgressStatic.cpp
cpp
#include "StdAfx.h"
#include "ProgressStatic.h"
IMPLEMENT_DYNCREATE(CProgressStatic, CStatic)
CProgressStatic::CProgressStatic()
{
m_nPercent = 0;
}
CProgressStatic::~CProgressStatic()
{
}
BEGIN_MESSAGE_MAP(CProgressStatic, CStatic)
ON_WM_PAINT()
END_MESSAGE_MAP()
void CProgressStatic::SetProgress(int nPercent)
{
// 限制区间 0~100
m_nPercent = max(0, min(100, nPercent));
Invalidate(); // 重绘控件
}
void CProgressStatic::OnPaint()
{
CPaintDC dc(this);
CRect rect;
GetClientRect(&rect);
// 1. 绘制完整外黑框+白底
dc.SelectStockObject(PS_SOLID);
dc.SelectStockObject(WHITE_BRUSH);
dc.Rectangle(&rect);
// 关键:填充区域向内收缩1像素,避免覆盖边框
CRect fillRect = rect;
fillRect.left += 1;
fillRect.top += 1;
fillRect.right -= 1;
fillRect.bottom -= 1;
// 根据百分比计算右侧宽度
fillRect.right = fillRect.left + (fillRect.Width() * m_nPercent) / 100;
// 2. 只填充绿色,不绘制边线
CBrush greenBrush(RGB(0, 255, 0));
dc.SelectObject(&greenBrush);
dc.SelectStockObject(NULL_PEN);
dc.FillRect(&fillRect, &greenBrush);
// 3. 居中文字逻辑不变
CString strText;
strText.Format(_T("%d%%"), m_nPercent);
dc.SetTextColor(RGB(128, 0, 128));
dc.SetBkMode(TRANSPARENT);
CSize textSize = dc.GetTextExtent(strText);
int nCenterX = rect.left + rect.Width() / 2;
int nCenterY = rect.top + rect.Height() / 2;
int textX = nCenterX - textSize.cx / 2;
int textY = nCenterY - textSize.cy / 2;
dc.TextOut(textX, textY, strText);
}
四、对话框中使用教程
4.1 对话框资源布局
- 打开对话框资源编辑器,拖拽一个
Static Text静态控件到界面,修改 ID 为IDC_PROGRESS_BAR,拉大控件矩形作为进度条容器; - 在控件下方添加 4 个按钮,ID 与文本对应:
- IDC_BTN_100:显示文字
100% - IDC_BTN_70:显示文字
70% - IDC_BTN_10:显示文字
10% - IDC_BTN_0:显示文字
0%
- IDC_BTN_100:显示文字
4.2 对话框头文件引入控件并声明成员
cpp
#include "ProgressStatic.h"
class CMainDlg : public CDialogEx
{
// ... 系统自动生成代码省略 ...
private:
// 自定义进度条控件对象
CProgressStatic m_progressBar;
protected:
// 控件绑定交换函数
virtual void DoDataExchange(CDataExchange* pDX);
// 按钮点击消息响应函数声明
afx_msg void OnBnClickedBtn100();
afx_msg void OnBnClickedBtn70();
afx_msg void OnBnClickedBtn10();
afx_msg void OnBnClickedBtn0();
DECLARE_MESSAGE_MAP()
};
4.3 对话框实现文件绑定控件 + 按钮事件
cpp
// 控件绑定,将资源Static控件关联到自定义CProgressStatic对象
void CMainDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_PROGRESS_BAR, m_progressBar);
}
BEGIN_MESSAGE_MAP(CMainDlg, CDialogEx)
// 绑定四个按钮点击事件
ON_BN_CLICKED(IDC_BTN_100, &CMainDlg::OnBnClickedBtn100)
ON_BN_CLICKED(IDC_BTN_70, &CMainDlg::OnBnClickedBtn70)
ON_BN_CLICKED(IDC_BTN_10, &CMainDlg::OnBnClickedBtn10)
ON_BN_CLICKED(IDC_BTN_0, &CMainDlg::OnBnClickedBtn0)
END_MESSAGE_MAP()
// 切换至100%进度
void CMainDlg::OnBnClickedBtn100()
{
m_progressBar.SetProgress(100);
}
// 切换至70%进度(默认示例效果)
void CMainDlg::OnBnClickedBtn70()
{
m_progressBar.SetProgress(70);
}
// 切换至10%进度
void CMainDlg::OnBnClickedBtn10()
{
m_progressBar.SetProgress(10);
}
// 切换至0%进度
void CMainDlg::OnBnClickedBtn0()
{
m_progressBar.SetProgress(0);
}
五、关键技术点解析
5.1 为什么不用原生 CProgressCtrl?
MFC 自带CProgressCtrl仅支持水平 / 垂直简单进度,无法自定义边框、填充色,且原生控件不支持直接在进度条上绘制居中文字;想要实现文字叠加,需要额外绘图逻辑,封装成本高于继承CStatic自定义绘制。
5.2 文字精准居中实现原理
很多新手会直接写固定像素偏移 TextOut(centerX - 20, centerY - 12, str),存在严重缺陷:
- 文本长度变化时(0% / 100% 宽度不同)文字会左右偏移;
- 控件高度修改、字体大小调整后,垂直方向不再居中。
本文解决方案: dc.GetTextExtent(strText) 获取当前 DC 下文字真实宽高,用矩形中心点减去文字一半尺寸,无论文本、字体、控件尺寸如何变化,文字永远水平 + 垂直居中。
5.3 数值越界防护
SetProgress 中使用max(0, min(100, nPercent)) 强制约束进度范围:
- 传入负数自动修正为 0;
- 传入大于 100 的数值自动修正为 100; 避免填充区域计算时出现负宽度、超出控件范围等绘图异常。
5.4 重绘刷新机制
调用Invalidate()会向控件发送WM_PAINT消息,触发OnPaint重新执行全套绘图逻辑,修改进度后画面实时同步更新,无卡顿。
六、扩展优化方向(可自行拓展)
- 圆角进度条 :将
Rectangle替换为RoundRect(rect, 8, 8),设置圆角半径实现圆角边框与填充; - 渐变填充 :使用 Windows API
GradientFill实现绿色渐变进度; - 自定义颜色接口 :新增
SetFillColor、SetTextColor函数,外部自由修改填充、文字颜色; - 自适应字体 :在
OnPaint中创建指定大小字体,适配高分辨率界面; - 平滑动画:添加定时器,分段递增进度值,实现进度平滑滚动动画;
- 垂直进度条:增加标志位,切换水平 / 垂直填充计算逻辑。
七、踩坑记录(常见错误解决方案)
- 报错:CRect 不存在 CenterX 成员 错误写法:
rect.CenterX(),该 API 为 Qt 接口,MFC 原生 CRect 无此函数,必须手动计算rect.left + rect.Width() / 2; - 文字背景白色遮挡绿色填充 忘记调用
dc.SetBkMode(TRANSPARENT),文字背景不透明会覆盖底层绿色区域,必须设置透明背景模式; - 修改进度后界面无变化
SetProgress函数遗漏Invalidate(),没有触发重绘,画面不会刷新; - DDX_Control 绑定失败 头文件未添加
#include "ProgressStatic.h",或资源控件 ID 与DoDataExchange中 ID 不匹配。
八、总结
本文基于 MFC CStatic 封装的自定义进度条控件,代码简洁、无第三方依赖、复用性极强,完美解决原生进度条自定义样式不足的痛点。核心绘图逻辑分层清晰,居中文字算法通用性强,可直接移植到任意 MFC 对话框工程中使用,同时预留了充足扩展空间,可根据项目 UI 需求自由修改颜色、圆角、动画等效果。
