You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 02:21:15