WinForm 中 DataGridView 实现进度条列

194 阅读7分钟

前言

在日常的桌面应用开发中,数据表格(DataGridView)是展示结构化数据的核心控件。然而,当需要展示任务进度、完成度等动态数据时,传统的数字显示方式往往显得单调乏味。用户更期待直观的可视化反馈——进度条的出现,不仅能让数据更易理解,还能显著提升应用的专业感。

本文将通过两种实用方案,详细讲解如何在WinForm的DataGridView中实现进度条列,并提供完整代码示例和优化建议。

效果预览

CellPainting事件自绘进度条

自定义进度条列

动态更新进度条

为什么需要进度条列?

在任务管理、数据处理、文件传输等场景中,用户需要实时了解任务的执行状态。例如:

  • 批量文件下载:显示每个文件的下载进度(0%-100%)

  • 数据处理流水线:展示各环节的完成百分比

  • 系统监控:实时显示CPU、内存等资源占用率

传统方案通常使用数字或简单的文本标签(如"已完成"进行中"),但存在以下问题:

1、信息密度低:用户需自行换算百分比与实际进度

2、缺乏对比性:难以快速判断多个任务的相对进度

3、视觉体验差:单调的数字难以吸引用户注意力

进度条通过图形化方式直观展示数据,能立即解决上述痛点。

从静态到动态的全面支持

本文实现的进度条列具备以下核心功能:

1、可视化进度展示:以水平条形图直观显示0%-100%的进度

2、动态颜色映射:根据进度值自动切换颜色(如红色表示低进度,绿色表示高进度)

3、百分比文本叠加:在进度条上显示具体数值,避免二次计算

4、实时更新机制:支持通过代码动态修改进度值并刷新显示

5、高度可定制化:允许调整进度条高度、边距、圆角等样式属性

兼顾灵活性与易用性

1、两种实现方案

  • CellPainting事件自绘:适合需要精细控制绘制逻辑的场景

  • 自定义列类:代码结构更清晰,便于复用和维护

2、性能优化

  • 使用using语句管理GDI+资源,避免内存泄漏

  • 通过e.Handled = true阻止默认绘制,减少重绘次数 3、异常处理

  • 自动校验进度值范围(0-100),防止越界

  • 兼容空值情况,避免程序崩溃

核心实现原理

方案一:CellPainting事件自绘

通过订阅CellPainting事件,在单元格绘制阶段完全接管渲染逻辑。

关键步骤如下:

1、判断目标列:仅对"进度”列(索引为1)进行自定义绘制

2、绘制背景:使用LightGray填充单元格底色

3、绘制进度条:

  • 计算填充宽度(progressRect.Width * (progressValue / 100.0)

  • 根据进度值选择颜色(红→黄→绿→蓝渐变)

4、叠加文本:在进度条中央显示百分比,使用黑色字体

5、绘制边框:添加灰色外边框增强视觉层次

方案二:自定义进度条列类

通过继承DataGridViewColumnDataGridViewCell,创建专用的ProgressBarColumn类。

核心优势:

  • 封装性:将绘制逻辑集中管理,主窗体代码更简洁

  • 复用性:可在多个项目中直接引用,无需重复实现

  • 扩展性:支持通过属性面板配置进度条样式(如高度、颜色映射规则)

项目源码

方案一核心代码(CellPainting事件)

private void DataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) {
    if (e.ColumnIndex == 1 && e.RowIndex >= 0) { // 仅处理进度列
        if (int.TryParse(e.Value?.ToString(), out int progressValue)) {
            progressValue = Math.Max(0, Math.Min(100, progressValue)); // 限制范围
            e.PaintBackground(e.CellBounds, true);

            // 绘制进度条背景
            Rectangle progressRect = new Rectangle(
                e.CellBounds.X + 2, e.CellBounds.Y + 2,
                e.CellBounds.Width - 4, e.CellBounds.Height - 4);
            using (Brush bgBrush = new SolidBrush(Color.LightGray)) {
                e.Graphics.FillRectangle(bgBrush, progressRect);
            }

            // 绘制进度条填充
            int fillWidth = (int)(progressRect.Width * (progressValue / 100.0));
            if (fillWidth > 0) {
                Rectangle fillRect = new Rectangle(
                    progressRect.X, progressRect.Y,
                    fillWidth, progressRect.Height);
                using (Brush fillBrush = new SolidBrush(GetProgressColor(progressValue))) {
                    e.Graphics.FillRectangle(fillBrush, fillRect);
                }
            }

            // 绘制百分比文本
            string text = $"{progressValue}%";
            using (Brush textBrush = new SolidBrush(Color.Black)) {
                StringFormat sf = new StringFormat() {
                    Alignment = StringAlignment.Center,
                    LineAlignment = StringAlignment.Center
                };
                e.Graphics.DrawString(text, e.CellStyle.Font, textBrush, e.CellBounds, sf);
            }

            // 绘制边框
            using (Pen borderPen = new Pen(Color.Gray)) {
                e.Graphics.DrawRectangle(borderPen, progressRect);
            }

            e.Handled = true; // 阻止默认绘制
        }
    }
}

private Color GetProgressColor(int progress) {
    if (progress < 30) return Color.FromArgb(220, 53, 69); // 红色
    if (progress < 70) return Color.FromArgb(255, 193, 7);  // 黄色
    if (progress < 100) return Color.FromArgb(40, 167, 69);  // 绿色
    return Color.FromArgb(23, 162, 184); // 蓝色
}

方案二核心代码(自定义列类)

public class ProgressBarColumn : DataGridViewColumn {
    public ProgressBarColumn() : base(new ProgressBarCell()) { }
}

public class ProgressBarCell : DataGridViewTextBoxCell {
    protected override void Paint(Graphics graphics, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) {
        // 调用基类绘制边框和背景
        base.Paint(graphics, cellBounds, rowIndex, elementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts & ~DataGridViewPaintParts.ContentForeground);

        if (int.TryParse(formattedValue?.ToString(), out int progressValue)) {
            progressValue = Math.Max(0, Math.Min(100, progressValue));

            // 绘制进度条
            Rectangle progressRect = new Rectangle(
                cellBounds.X + 2, cellBounds.Y + 2,
                cellBounds.Width - 4, cellBounds.Height - 4);
            using (Brush bgBrush = new SolidBrush(Color.LightGray)) {
                graphics.FillRectangle(bgBrush, progressRect);
            }

            int fillWidth = (int)(progressRect.Width * (progressValue / 100.0));
            if (fillWidth > 0) {
                Rectangle fillRect = new Rectangle(
                    progressRect.X, progressRect.Y,
                    fillWidth, progressRect.Height);
                using (Brush fillBrush = new SolidBrush(GetProgressColor(progressValue))) {
                    graphics.FillRectangle(fillBrush, fillRect);
                }
            }

            // 绘制文本
            string text = $"{progressValue}%";
            using (Brush textBrush = new SolidBrush(Color.Black)) {
                TextRenderer.DrawText(
                    graphics, text, cellStyle.Font,
                    cellBounds, textBrush,
                    TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
            }
        }
    }

    private Color GetProgressColor(int progress) { /* 同方案一 */ }
}

方案三核心代码(动态更新进度条)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Timer = System.Windows.Forms.Timer;

namespace AppDataGridBar
{
    public partial class Form3 : Form
    {
        private DataTable dataTable;
        private Timer updateTimer;
        private Random random = new Random();

        public Form3()
        {
            InitializeComponent();
            InitializeDataGridView();
        }

        private void InitializeDataGridView()
        {
            // 创建数据源
            dataTable = new DataTable();
            dataTable.Columns.Add("任务名称", typeof(string));
            dataTable.Columns.Add("进度", typeof(int)); // 存储0-100的进度值
            dataTable.Columns.Add("状态", typeof(string));

            // 添加示例数据
            dataTable.Rows.Add("文件下载", 75, "进行中");
            dataTable.Rows.Add("数据同步", 45, "进行中");
            dataTable.Rows.Add("报表生成", 100, "已完成");
            dataTable.Rows.Add("邮件发送", 30, "进行中");
            dataTable.Rows.Add("备份数据", 0, "待开始");

            // 绑定数据源
            dataGridView1.DataSource = dataTable;

            // 设置列属性
            dataGridView1.Columns["任务名称"].Width = 120;
            dataGridView1.Columns["进度"].Width = 250; // 增加宽度以容纳进度条
            dataGridView1.Columns["状态"].Width = 80;

            // 关键:绑定CellPainting事件
            dataGridView1.CellPainting += DataGridView1_CellPainting;

            // 优化显示效果
            dataGridView1.RowTemplate.Height = 30;
            dataGridView1.AllowUserToAddRows = false;
            dataGridView1.AllowUserToDeleteRows = false;
            dataGridView1.ReadOnly = true;
            dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;

            // 设置表格样式
            dataGridView1.EnableHeadersVisualStyles = false;
            dataGridView1.ColumnHeadersDefaultCellStyle.BackColor = Color.FromArgb(64, 64, 64);
            dataGridView1.ColumnHeadersDefaultCellStyle.ForeColor = Color.White;
            dataGridView1.ColumnHeadersHeight = 35;

            // 设置行样式
            dataGridView1.DefaultCellStyle.SelectionBackColor = Color.FromArgb(51, 122, 183);
            dataGridView1.AlternatingRowsDefaultCellStyle.BackColor = Color.FromArgb(248, 248, 248);
        }

        // 关键的绘制方法
        private void DataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
        {
            // 只处理进度列(第1列,索引为1)
            if (e.ColumnIndex == 1 && e.RowIndex >= 0)
            {
                // 获取进度值
                int progress = Convert.ToInt32(dataGridView1.Rows[e.RowIndex].Cells["进度"].Value);

                // 绘制背景
                e.PaintBackground(e.CellBounds, true);

                // 计算进度条区域
                Rectangle progressRect = e.CellBounds;
                progressRect.Inflate(-3, -3); // 留出边距

                // 绘制进度条背景
                using (SolidBrush backgroundBrush = new SolidBrush(Color.FromArgb(230, 230, 230)))
                {
                    e.Graphics.FillRectangle(backgroundBrush, progressRect);
                }

                // 绘制进度条边框
                using (Pen borderPen = new Pen(Color.FromArgb(180, 180, 180)))
                {
                    e.Graphics.DrawRectangle(borderPen, progressRect);
                }

                // 计算进度条填充区域
                int fillWidth = (int)(progressRect.Width * progress / 100.0);
                Rectangle fillRect = new Rectangle(progressRect.X, progressRect.Y, fillWidth, progressRect.Height);

                // 根据进度选择颜色
                Color progressColor = GetProgressColor(progress);

                // 绘制进度填充
                if (fillWidth > 0)
                {
                    using (LinearGradientBrush fillBrush = new LinearGradientBrush(
                        fillRect,
                        Color.FromArgb(255, progressColor.R, progressColor.G, progressColor.B),
                        Color.FromArgb(200, progressColor.R, progressColor.G, progressColor.B),
                        LinearGradientMode.Vertical))
                    {
                        e.Graphics.FillRectangle(fillBrush, fillRect);
                    }
                }

                // 绘制进度文本
                string progressText = $"{progress}%";
                using (Font font = new Font("Microsoft YaHei", 9, FontStyle.Bold))
                {
                    SizeF textSize = e.Graphics.MeasureString(progressText, font);
                    PointF textPoint = new PointF(
                        progressRect.X + (progressRect.Width - textSize.Width) / 2,
                        progressRect.Y + (progressRect.Height - textSize.Height) / 2
                    );

                    // 根据背景选择文字颜色
                    Color textColor = progress > 50 ? Color.White : Color.Black;
                    using (SolidBrush textBrush = new SolidBrush(textColor))
                    {
                        e.Graphics.DrawString(progressText, font, textBrush, textPoint);
                    }
                }

                e.Handled = true;
            }
        }

        // 根据进度值返回对应的颜色
        private Color GetProgressColor(int progress)
        {
            if (progress == 100)
                return Color.FromArgb(40, 167, 69); // 绿色-完成
            else if (progress >= 70)
                return Color.FromArgb(23, 162, 184); // 蓝色-接近完成
            else if (progress >= 40)
                return Color.FromArgb(255, 193, 7); // 黄色-进行中
            else if (progress > 0)
                return Color.FromArgb(255, 87, 34); // 橙色-刚开始
            else
                return Color.FromArgb(108, 117, 125); // 灰色-未开始
        }

        // 按钮事件处理
        private void BtnStart_Click(object sender, EventArgs e)
        {
            StartProgressSimulation();
        }

        private void BtnStop_Click(object sender, EventArgs e)
        {
            StopProgressSimulation();
        }

        private void BtnReset_Click(object sender, EventArgs e)
        {
            ResetProgress();
        }

        private void BtnAddTask_Click(object sender, EventArgs e)
        {
            AddNewTask();
        }

        private void StartProgressSimulation()
        {
            if (updateTimer == null)
            {
                // 创建定时器,每200毫秒更新一次进度
                updateTimer = new Timer();
                updateTimer.Interval = 200;
                updateTimer.Tick += UpdateTimer_Tick;
            }

            if (!updateTimer.Enabled)
            {
                updateTimer.Start();
            }
        }

        private void StopProgressSimulation()
        {
            if (updateTimer != null && updateTimer.Enabled)
            {
                updateTimer.Stop();
            }
        }

        private void ResetProgress()
        {
            StopProgressSimulation();

            // 重置所有进度
            foreach (DataRow row in dataTable.Rows)
            {
                row["进度"] = 0;
                row["状态"] = "待开始";
            }

            dataGridView1.Invalidate();
        }

        private void AddNewTask()
        {
            string taskName = $"新任务{dataTable.Rows.Count + 1}";
            dataTable.Rows.Add(taskName, 0, "待开始");
        }

        private void UpdateTimer_Tick(object sender, EventArgs e)
        {
            // 模拟进度更新
            bool hasUpdates = false;

            foreach (DataRow row in dataTable.Rows)
            {
                int currentProgress = Convert.ToInt32(row["进度"]);

                // 如果未完成,随机增加进度
                if (currentProgress < 100)
                {
                    int increment = random.Next(1, 8); // 随机增加1-7
                    int newProgress = Math.Min(100, currentProgress + increment);
                    row["进度"] = newProgress;
                    hasUpdates = true;

                    // 更新状态
                    if (newProgress == 100)
                    {
                        row["状态"] = "已完成";
                    }
                    else if (newProgress > 0)
                    {
                        row["状态"] = "进行中";
                    }
                }
            }

            // 只在有更新时刷新
            if (hasUpdates)
            {
                dataGridView1.InvalidateColumn(1); // 只刷新进度列
                dataGridView1.InvalidateColumn(2); // 刷新状态列
            }

            // 检查是否所有任务都完成
            bool allCompleted = true;
            foreach (DataRow row in dataTable.Rows)
            {
                if (Convert.ToInt32(row["进度"]) < 100)
                {
                    allCompleted = false;
                    break;
                }
            }

            if (allCompleted)
            {
                updateTimer.Stop();
                MessageBox.Show("所有任务已完成!", "提示", MessageBoxButtons.OK,
                    MessageBoxIcon.Information);
            }
        }

        // 手动更新特定行的进度
        public void UpdateProgress(int rowIndex, int newProgress)
        {
            if (rowIndex >= 0 && rowIndex < dataTable.Rows.Count)
            {
                int clampedProgress = Math.Max(0, Math.Min(100, newProgress));
                dataTable.Rows[rowIndex]["进度"] = clampedProgress;

                // 更新状态
                if (clampedProgress == 100)
                    dataTable.Rows[rowIndex]["状态"] = "已完成";
                else if (clampedProgress > 0)
                    dataTable.Rows[rowIndex]["状态"] = "进行中";
                else
                    dataTable.Rows[rowIndex]["状态"] = "待开始";

                // 只刷新指定行
                dataGridView1.InvalidateRow(rowIndex);
            }
        }

        // 批量更新进度
        public void UpdateMultipleProgress(Dictionary<int, int> progressUpdates)
        {
            foreach (var update in progressUpdates)
            {
                UpdateProgress(update.Key, update.Value);
            }
        }

        // 获取所有任务的进度信息
        public List<TaskProgress> GetAllProgress()
        {
            List<TaskProgress> progressList = new List<TaskProgress>();

            for (int i = 0; i < dataTable.Rows.Count; i++)
            {
                DataRow row = dataTable.Rows[i];
                progressList.Add(new TaskProgress
                {
                    TaskName = row["任务名称"].ToString(),
                    Progress = Convert.ToInt32(row["进度"]),
                    Status = row["状态"].ToString(),
                    RowIndex = i
                });
            }

            return progressList;
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            StopProgressSimulation();
            base.OnFormClosing(e);
        }
    }

    // 任务进度信息类
    public class TaskProgress
    {
        public string TaskName { get; set; }
        public int Progress { get; set; }
        public string Status { get; set; }
        public int RowIndex { get; set; }
    }
}

总结

1、快速实现:优先选择CellPainting事件方案,无需创建新类

2、长期维护:推荐自定义列类方案,代码结构更清晰

3、性能敏感场景:两种方案性能接近,但自定义列类更易优化

无论选择哪种方案,核心原则都是:保持绘制逻辑简洁,合理管理资源。通过本文的学习,你已掌握将枯燥的数字转化为生动进度条的技巧,这将为你的WinForm应用增添专业级的数据可视化能力。

关键词

WinForm、DataGridView、进度条列、CellPainting、自定义列、数据可视化、C#开发、GDI+绘制、动态更新、性能优化

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:技术老小子

出处:mp.weixin.qq.com/s/wsj3Z-ZvriQxMfhbNojxeA

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!