编辑
2025-09-18
C#
00

目录

💡 问题分析:为什么需要进度条列?
🛠️ 解决方案一:使用CellPainting事件自绘进度条
🎨 解决方案二:创建自定义进度条列类
⚡ 解决方案三:动态更新进度条
💬 互动交流
🎉 总结要点

你有没有遇到过这样的场景:在开发桌面应用时,需要在DataGridView中显示任务执行进度、文件下载状态、或者数据处理完成度?传统的百分比数字显示方式让用户体验大打折扣,而且很难直观地看出当前状态。

用户更喜欢可视化的进度展示,而不是冰冷的数字。一个直观的进度条不仅能提升用户体验,还能让你的应用看起来更加专业。

本文将手把手教你在WinForm的DataGridView中实现进度条列,让你的数据展示瞬间提升几个档次!

💡 问题分析:为什么需要进度条列?

在日常开发中,我们经常需要在表格中展示以下类型的数据:

  • 🔄 任务执行进度:批量数据处理、文件上传下载
  • 📊 完成度指标:项目进度、学习进度、销售达成率
  • 实时状态:系统资源占用、网络传输状态

传统的文本显示方式存在以下问题:

  1. 可读性差:用户需要费力理解数字含义
  2. 对比困难:无法快速比较不同行的进度差异
  3. 用户体验差:界面单调,缺乏现代感

🛠️ 解决方案一:使用CellPainting事件自绘进度条

这是最灵活的实现方式,完全可控制进度条的外观和行为。

C#
public partial class Form1 : Form { private DataTable dataTable; public Form1() { 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, "进行中"); // 绑定数据源 dataGridView1.DataSource = dataTable; // 设置列属性 dataGridView1.Columns["任务名称"].Width = 120; dataGridView1.Columns["进度"].Width = 200; dataGridView1.Columns["状态"].Width = 80; // 关键:绑定CellPainting事件 dataGridView1.CellPainting += DataGridView1_CellPainting; // 优化显示效果 dataGridView1.RowTemplate.Height = 25; dataGridView1.AllowUserToAddRows = false; } private void DataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { // 只对"进度"列进行自定义绘制 if (e.ColumnIndex == 1 && e.RowIndex >= 0) // 进度列的索引为1 { // 获取进度值 if (int.TryParse(e.Value?.ToString(), out int progressValue)) { // 确保进度值在0-100范围内 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 backgroundBrush = new SolidBrush(Color.LightGray)) { e.Graphics.FillRectangle(backgroundBrush, progressRect); } // 计算填充宽度 int fillWidth = (int)(progressRect.Width * (progressValue / 100.0)); if (fillWidth > 0) { Rectangle fillRect = new Rectangle( progressRect.X, progressRect.Y, fillWidth, progressRect.Height ); // 根据进度值选择颜色 Color fillColor = GetProgressColor(progressValue); using (Brush fillBrush = new SolidBrush(fillColor)) { e.Graphics.FillRectangle(fillBrush, fillRect); } } // 绘制文字(进度百分比) string text = $"{progressValue}%"; using (Brush textBrush = new SolidBrush(Color.Black)) { StringFormat sf = new StringFormat(); sf.Alignment = StringAlignment.Center; sf.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; } } } /// <summary> /// 根据进度值返回对应颜色 /// </summary> private Color GetProgressColor(int progress) { if (progress < 30) return Color.FromArgb(220, 53, 69); // 红色 else if (progress < 70) return Color.FromArgb(255, 193, 7); // 黄色 else if (progress < 100) return Color.FromArgb(40, 167, 69); // 绿色 else return Color.FromArgb(23, 162, 184); // 蓝色(完成) } }

image.png

💡 应用场景说明:

  • 适用于需要高度自定义外观的场景
  • 可以实现渐变色、动画效果等高级特性
  • 性能良好,适合大数据量显示

⚠️ 常见坑点提醒:

  1. 必须设置e.Handled = true,否则会出现重复绘制
  2. 注意边界检查,避免进度值超出0-100范围
  3. 合理使用using语句,及时释放GDI+资源

🎨 解决方案二:创建自定义进度条列类

通过继承DataGridViewColumn创建专用的进度条列,代码更加清晰和可复用,其实就是重绘TextBoxCell。

C#
// 自定义进度条单元格类 public class DataGridViewProgressCell : DataGridViewTextBoxCell { public override Type ValueType => typeof(int); protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle borderStyle, DataGridViewPaintParts paintParts) { // 获取进度值 int progressVal = 0; if (value != null && int.TryParse(value.ToString(), out int tempVal)) { progressVal = Math.Max(0, Math.Min(100, tempVal)); } // 绘制单元格背景 if ((paintParts & DataGridViewPaintParts.Background) == DataGridViewPaintParts.Background) { using (var backColorBrush = new SolidBrush(cellStyle.BackColor)) { graphics.FillRectangle(backColorBrush, cellBounds); } } // 绘制进度条 if ((paintParts & DataGridViewPaintParts.ContentForeground) == DataGridViewPaintParts.ContentForeground) { Rectangle progressRect = new Rectangle( cellBounds.X + 2, cellBounds.Y + 2, cellBounds.Width - 4, cellBounds.Height - 4 ); // 进度条背景 using (var progressBackBrush = new SolidBrush(Color.FromArgb(240, 240, 240))) { graphics.FillRectangle(progressBackBrush, progressRect); } // 进度条填充 if (progressVal > 0) { int fillWidth = (int)(progressRect.Width * (progressVal / 100.0)); Rectangle fillRect = new Rectangle(progressRect.X, progressRect.Y, fillWidth, progressRect.Height); // 使用线性渐变效果 using (var fillBrush = new LinearGradientBrush(fillRect, GetProgressStartColor(progressVal), GetProgressEndColor(progressVal), LinearGradientMode.Horizontal)) { graphics.FillRectangle(fillBrush, fillRect); } } // 绘制进度文字 string progressText = $"{progressVal}%"; using (var textBrush = new SolidBrush(Color.Black)) { StringFormat stringFormat = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }; graphics.DrawString(progressText, cellStyle.Font, textBrush, cellBounds, stringFormat); } // 绘制边框 using (var borderPen = new Pen(Color.FromArgb(200, 200, 200))) { graphics.DrawRectangle(borderPen, progressRect); } } // 绘制单元格边框 if ((paintParts & DataGridViewPaintParts.Border) == DataGridViewPaintParts.Border) { PaintBorder(graphics, clipBounds, cellBounds, cellStyle, borderStyle); } } private Color GetProgressStartColor(int progress) { if (progress < 30) return Color.FromArgb(255, 99, 132); if (progress < 70) return Color.FromArgb(255, 205, 86); if (progress < 100) return Color.FromArgb(75, 192, 192); return Color.FromArgb(54, 162, 235); } private Color GetProgressEndColor(int progress) { if (progress < 30) return Color.FromArgb(255, 159, 164); if (progress < 70) return Color.FromArgb(255, 226, 139); if (progress < 100) return Color.FromArgb(129, 210, 210); return Color.FromArgb(116, 185, 255); } } // 自定义进度条列类 public class DataGridViewProgressColumn : DataGridViewColumn { public DataGridViewProgressColumn() : base(new DataGridViewProgressCell()) { } public override DataGridViewCell CellTemplate { get => base.CellTemplate; set { if (value != null && !value.GetType().IsAssignableFrom(typeof(DataGridViewProgressCell))) { throw new InvalidCastException("必须是DataGridViewProgressCell类型"); } base.CellTemplate = value; } } }

使用自定义进度条列:

C#
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace AppDataGridBar { public partial class Form2 : Form { public Form2() { InitializeComponent(); InitializeCustomProgressColumn(); } private void InitializeCustomProgressColumn() { // 清除自动生成的列 dataGridView1.AutoGenerateColumns = false; dataGridView1.Columns.Clear(); // 添加普通列 - 关键是要设置DataPropertyName DataGridViewTextBoxColumn taskNameColumn = new DataGridViewTextBoxColumn { Name = "TaskName", HeaderText = "任务名称", DataPropertyName = "TaskName" // 绑定到TaskInfo.TaskName属性 }; dataGridView1.Columns.Add(taskNameColumn); // 如果要添加进度条列,取消注释这部分 DataGridViewProgressColumn progressColumn = new DataGridViewProgressColumn { Name = "Progress", HeaderText = "完成进度", Width = 200, DataPropertyName = "Progress" }; dataGridView1.Columns.Add(progressColumn); // 添加状态列 DataGridViewTextBoxColumn statusColumn = new DataGridViewTextBoxColumn { Name = "Status", HeaderText = "状态", DataPropertyName = "Status" // 绑定到TaskInfo.Status属性 }; dataGridView1.Columns.Add(statusColumn); // 绑定数据源 List<TaskInfo> tasks = new List<TaskInfo> { new TaskInfo { TaskName = "系统备份", Progress = 85, Status = "进行中" }, new TaskInfo { TaskName = "数据清理", Progress = 60, Status = "进行中" }, new TaskInfo { TaskName = "日志归档", Progress = 100, Status = "已完成" } }; dataGridView1.DataSource = tasks; } // 数据模型类 public class TaskInfo { public string TaskName { get; set; } public int Progress { get; set; } public string Status { get; set; } } } }

image.png

⚡ 解决方案三:动态更新进度条

实际应用中,进度条需要实时更新。以下展示如何实现动态进度更新:

C#
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; } } }

image.png

💬 互动交流

你在项目中还遇到过哪些DataGridView的显示需求? 比如图表列、图片列、或者其他自定义控件列?

有没有在进度条显示上遇到性能问题? 特别是大数据量情况下的优化经验,欢迎在评论区分享!

🎉 总结要点

通过本文的学习,你已经掌握了在WinForm DataGridView中实现进度条列的三大核心方法

  1. 🎨 CellPainting事件自绘:灵活度最高,适合复杂定制需求
  2. 🔧 自定义列类:代码清晰,可复用性强,推荐用于产品级开发
  3. ⚡ 动态更新机制:实现实时进度显示,提升用户体验

掌握这些技巧后,你的WinForm应用将拥有更加现代化和专业的界面表现。记住性能优化的关键:合理使用SuspendLayoutResumeLayout,避免频繁的单个单元格刷新。

觉得这些技巧有用吗?请转发给更多需要的同行,让我们一起提升C#开发技能! 🚀

相关信息

通过网盘分享的文件:AppDataGridBar.zip 链接: https://pan.baidu.com/s/1FznKbuM804ZWEVqU5bFEWg?pwd=rcgh 提取码: rcgh --来自百度网盘超级会员v9的分享:::

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!