MFC 自定义纯色居中文字进度条控件

一、前言

在 MFC 原生开发中,系统自带的CProgressCtrl进度条样式固定、自定义空间极小,很难实现自定义边框、纯色填充、进度文字居中显示 这类定制 UI 需求。 本文通过继承CStatic静态文本控件,从零封装一个高复用自定义进度条,实现效果:

  1. 黑色实线外边框,空白区域白色底色
  2. 进度区域纯绿色填充,支持 0~100 区间动态切换
  3. 百分比文字自动水平 + 垂直精准居中,适配 0%/70%/100% 不同长度文本
  4. 对外提供极简SetProgress接口,一行代码切换进度值
  5. 兼容对话框工程,仅需绑定 Static 控件即可使用

最终界面效果:底部按钮点击切换进度,进度条实时重绘更新文字与填充区域。

二、整体设计思路

  1. 基类选择 :继承CStatic,对话框资源直接拖入 Static 文本控件,无需自定义资源 ID 模板,上手简单;
  2. 绘图逻辑 :重写WM_PAINT消息OnPaint函数,分层绘制:外框底色→进度填充→居中文字;
  3. 进度约束 :接口内部自动截断数值到0~100,避免越界绘制;
  4. 文字居中核心 :使用GetTextExtent获取文字真实宽高,通过矩形中心坐标减去文字半宽 / 半高,实现完全居中,摒弃固定像素偏移写法;
  5. 刷新机制 :修改进度值后调用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 对话框资源布局

  1. 打开对话框资源编辑器,拖拽一个Static Text静态控件到界面,修改 ID 为IDC_PROGRESS_BAR,拉大控件矩形作为进度条容器;
  2. 在控件下方添加 4 个按钮,ID 与文本对应:
    • IDC_BTN_100:显示文字100%
    • IDC_BTN_70:显示文字70%
    • IDC_BTN_10:显示文字10%
    • IDC_BTN_0:显示文字0%

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),存在严重缺陷:

  1. 文本长度变化时(0% / 100% 宽度不同)文字会左右偏移;
  2. 控件高度修改、字体大小调整后,垂直方向不再居中。

本文解决方案: dc.GetTextExtent(strText) 获取当前 DC 下文字真实宽高,用矩形中心点减去文字一半尺寸,无论文本、字体、控件尺寸如何变化,文字永远水平 + 垂直居中。

5.3 数值越界防护

SetProgress 中使用max(0, min(100, nPercent)) 强制约束进度范围:

  • 传入负数自动修正为 0;
  • 传入大于 100 的数值自动修正为 100; 避免填充区域计算时出现负宽度、超出控件范围等绘图异常。

5.4 重绘刷新机制

调用Invalidate()会向控件发送WM_PAINT消息,触发OnPaint重新执行全套绘图逻辑,修改进度后画面实时同步更新,无卡顿。

六、扩展优化方向(可自行拓展)

  1. 圆角进度条 :将Rectangle替换为RoundRect(rect, 8, 8),设置圆角半径实现圆角边框与填充;
  2. 渐变填充 :使用 Windows API GradientFill 实现绿色渐变进度;
  3. 自定义颜色接口 :新增SetFillColorSetTextColor函数,外部自由修改填充、文字颜色;
  4. 自适应字体 :在OnPaint中创建指定大小字体,适配高分辨率界面;
  5. 平滑动画:添加定时器,分段递增进度值,实现进度平滑滚动动画;
  6. 垂直进度条:增加标志位,切换水平 / 垂直填充计算逻辑。

七、踩坑记录(常见错误解决方案)

  1. 报错:CRect 不存在 CenterX 成员 错误写法:rect.CenterX(),该 API 为 Qt 接口,MFC 原生 CRect 无此函数,必须手动计算rect.left + rect.Width() / 2
  2. 文字背景白色遮挡绿色填充 忘记调用dc.SetBkMode(TRANSPARENT),文字背景不透明会覆盖底层绿色区域,必须设置透明背景模式;
  3. 修改进度后界面无变化 SetProgress函数遗漏Invalidate(),没有触发重绘,画面不会刷新;
  4. DDX_Control 绑定失败 头文件未添加#include "ProgressStatic.h",或资源控件 ID 与DoDataExchange中 ID 不匹配。

八、总结

本文基于 MFC CStatic 封装的自定义进度条控件,代码简洁、无第三方依赖、复用性极强,完美解决原生进度条自定义样式不足的痛点。核心绘图逻辑分层清晰,居中文字算法通用性强,可直接移植到任意 MFC 对话框工程中使用,同时预留了充足扩展空间,可根据项目 UI 需求自由修改颜色、圆角、动画等效果。

相关推荐
researcher-Jiang1 小时前
Manacher算法学习与应用
c++·学习·算法
码完就睡1 小时前
数据结构——串的模式匹配算法(BF算法、KMP算法)
数据结构·算法
tachibana21 小时前
hot100 排序链表(148)
java·数据结构·算法·leetcode·链表
林泽毅1 小时前
Qwen3.5 DPO 模型对齐实战(pytrio训练版)
人工智能·python·算法
灯澜忆梦1 小时前
【MySQL1】| DDL 数据定义语言
数据库·sql·mysql
imuliuliang1 小时前
算法中的随机化思想及其复杂度收益评估的技术7
算法
2601_963771372 小时前
Speeding Up Creative Agency Portfolios: A Performance Guide
前端·数据库·php
运维大师2 小时前
【安全与故障排查】03-【复盘】数据库被入侵:从发现到溯源全过程
数据库·安全·adb
不能跑的代码不是好代码2 小时前
二叉树从基础概念到LeetCode实战
算法·leetcode