WinForms中DataGridView渲染时ProgressBar冻结的解决方法咨询
问题:DataGridView绑定大数据集时UI冻结,进度条无法正常显示
我正在开发一个基于.NET Framework 4.7.2的C# Windows Forms项目。主窗体包含三个控件:dataGridView1、button1和内部带有progressBar1的panel1,DataGridView的行列均未设置自动调整大小。
点击按钮时,会通过异步方式从数据库获取一个超大DataTable,随后将其绑定到DataGridView,但此时用户界面会冻结,而我希望UI保持可交互状态。
初始代码如下:
public Form1() { InitializeComponent(); dataGridView1.Dock = DockStyle.Fill; panel1.Visible = false; panel1.Dock = DockStyle.Bottom; button1.Dock = DockStyle.Top; //progressBar1 is inside panel1 progressBar1.Style = ProgressBarStyle.Marquee; progressBar1.Dock = DockStyle.Fill; panel1.BringToFront(); dataGridView1.BringToFront(); } private DataTable GetDataTable() { //Datatable is retrieved from a database indeed, with long-time queries. DataTable dt = new DataTable(); for (int k = 0; k < 600; k++) dt.Columns.Add(k.ToString()); for (int r = 0; r < 100; r++) { DataRow newRow = dt.NewRow(); for (int c = 0; c < 600; c++) newRow[c] = r.ToString() + c.ToString(); dt.Rows.Add(newRow); } return dt; } private async void button1_Click(object sender, EventArgs e) { panel1.Visible = true; DataTable dt = null; await Task.Run(() => { dt = GetDataTable(); }); dataGridView1.DataSource = dt; //it takes time to draw (many cells). UI is frozen: progressbar value remain fixed panel1.Visible = false; }
我尝试使用System.Windows.Forms.Timer来刷新进度条,但DataGridView渲染时UI仍冻结,进度条无法正常运行,补充代码如下:
private async void button1_Click(object sender, EventArgs e) { panel1.Visible = true; Timer timer = new Timer(); timer.Interval = (10); // 10 millisecs timer.Tick += new EventHandler(timer_Tick); timer.Start(); DataTable dt = null; await Task.Run(() => { dt = GetDataTable(); //it takes time but it is on a different task. UI is not frozen }); dataGridView1.DataSource = dt; //it takes time to draw (many cells). UI is frozen: progressbar value remain fixed. How to view progressbar active while datagridview is rendering? timer.Stop(); timer.Tick -= new EventHandler(timer_Tick); panel1.Visible = false; } private void timer_Tick(object sender, EventArgs e) { panel1.Refresh(); }
解决方案
核心原因
dataGridView1.DataSource = dt;操作会在UI线程中一次性渲染所有单元格,这是导致UI冻结的根本原因——之前的Task.Run仅处理了数据获取,绑定后的渲染仍阻塞UI线程,Timer的消息会被排队无法触发。
1. 启用DataGridView虚拟模式
虚拟模式是标准DataGridView处理大数据集的最优方案,它仅渲染当前可见区域的单元格,大幅降低UI线程负载,从根源解决冻结问题。
修改代码步骤:
- 在窗体构造函数中启用虚拟模式并绑定
CellValueNeeded事件:
private DataTable _boundDataTable; // 存储数据集的全局变量 public Form1() { InitializeComponent(); dataGridView1.Dock = DockStyle.Fill; panel1.Visible = false; panel1.Dock = DockStyle.Bottom; button1.Dock = DockStyle.Top; progressBar1.Style = ProgressBarStyle.Marquee; progressBar1.Dock = DockStyle.Fill; panel1.BringToFront(); dataGridView1.BringToFront(); // 启用虚拟模式 dataGridView1.VirtualMode = true; dataGridView1.CellValueNeeded += DataGridView1_CellValueNeeded; // 禁用自动调整大小(保持原有设置) dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None; dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None; } // 按需返回单元格数据 private void DataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e) { if (_boundDataTable != null && e.RowIndex >= 0 && e.RowIndex < _boundDataTable.Rows.Count && e.ColumnIndex >= 0 && e.ColumnIndex < _boundDataTable.Columns.Count) { e.Value = _boundDataTable.Rows[e.RowIndex][e.ColumnIndex]; } }
- 修改按钮点击事件,替换直接绑定数据源的逻辑:
private async void button1_Click(object sender, EventArgs e) { panel1.Visible = true; DataTable dt = null; // 后台获取数据 await Task.Run(() => { dt = GetDataTable(); }); // UI线程中仅设置行列数,不一次性渲染所有单元格 _boundDataTable = dt; dataGridView1.SuspendLayout(); // 暂停布局提升性能 dataGridView1.ColumnCount = dt.Columns.Count; // 设置列名 for (int i = 0; i < dt.Columns.Count; i++) { dataGridView1.Columns[i].Name = dt.Columns[i].ColumnName; } dataGridView1.RowCount = dt.Rows.Count; dataGridView1.ResumeLayout(); // 恢复布局 panel1.Visible = false; }
2. 额外性能优化
- 启用双缓冲减少闪烁:
// 通过反射设置双缓冲(默认DataGridView的DoubleBuffered为protected) typeof(DataGridView).InvokeMember("DoubleBuffered", BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic, null, dataGridView1, new object[] { true });
- 禁用表头视觉样式:
dataGridView1.EnableHeadersVisualStyles = false;
效果说明
虚拟模式下,DataGridView仅在滚动时渲染可见行,UI线程不会被长时间占用,进度条的Marquee样式会正常运行,整个界面保持可交互状态。
内容的提问来源于stack exchange,提问作者user1630809
相关产品推荐
相关产品推荐

