使用大量数据填充DataGridView时性能缓慢

28

我正在使用一个 BindingSource 控件 (参考链接) 来填充我的 DataGridView 控件。约有1000多条记录在其中进行填充,我正在使用线程来完成这个过程。在这种情况下,DataGridView 的性能非常慢。

我尝试将 DoubleBuffered 属性设置为 true,RowHeadersWidthSizeMode 禁用,AutoSizeColumnsMode 设置为 none。但还是出现了同样的行为。

我该如何提高表格的性能?


考虑使用分页/筛选器来处理数据。 - Renatas M.
我经常遇到同样的问题;我也有几千条记录在一个列表中,必须使用分页来处理这些记录 - 这解决了问题,但同时也带来了新的问题! - Argeman
在SO上最常见的重复问题之一 - nawfal
没错,我也觉得用户会如何处理成千上万行的数据。但是这是我的客户目前的要求,所以我必须想方设法实现它 :) - VJOY
这个对我解决了同样的问题: https://dev59.com/LXA65IYBdhLWcg3w4C6j#37735997 - user2525774
15个回答

44
如果您有大量的行,比如10,000行或更多,请在数据绑定之前执行以下操作以避免性能泄漏:
dataGridView1.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.EnableResizing; 
// or even better, use .DisableResizing. Most time consuming enum is DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders

// set it to false if not needed
dataGridView1.RowHeadersVisible = false;

数据绑定后,您可以重新启用它。


2
我试图将你的代码放在我的file.Designer.vb文件中,并且尝试了运行,一切都很顺利。正如你所说,将RowHeadersVisible设置为False,然后在绑定之后,再将其设置为True。这是一个好的解决方案,它可以工作!但不幸的是,如果我对GUI(前端)进行了一些更改,我放在file.Designer.vb文件中的代码就会被更改为Visual Studio默认生成的代码。我该怎么处理?@okarpov? - gumuruh
@gumuruh 将禁用代码放在您的表单类构造函数中,并将启用代码放在绑定之后的某个地方。 - okarpov
当加载大量数据(100,000+)到DataGridView时,我遇到了ContentSwitchDeadlock问题。从“autoSizeAllHeaders”切换到“EnableResizing”解决了这个问题!谢谢。 - kaycee
大兄弟……非常非常感谢你……爱你,我尝试使用BackgroundWorker来提高性能,但无法做到,你节省了我很多时间。再次非常感谢你。 - Hussain Md

18

通常关闭自动调整大小并启用双缓冲可加快 DataGridView 的填充速度。请检查 DGV 的双缓冲是否已正确开启:

if (!System.Windows.Forms.SystemInformation.TerminalServerSession)
{
  Type dgvType = dataGridView1.GetType();
  PropertyInfo pi = dgvType.GetProperty("DoubleBuffered",
    BindingFlags.Instance | BindingFlags.NonPublic);
  pi.SetValue(dataGridView1, value, null);
}

使用WinAPI的WM_SETREDRAW消息禁用重绘也会有所帮助:

// *** API Declarations ***
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
private const int WM_SETREDRAW = 11;

// *** DataGridView population ***
SendMessage(dataGridView1.Handle, WM_SETREDRAW, false, 0);
// Add rows to DGV here
SendMessage(dataGridView1.Handle, WM_SETREDRAW, true, 0);
dataGridView1.Refresh();

如果您不需要双向数据绑定或BindingSource提供的某些功能(如过滤等),则可以考虑使用 DataGridView.Rows.AddRange() 方法一次添加多行。

源文章链接及示例: http://10tec.com/articles/why-datagridview-slow.aspx


17

确保您不要自动调整列大小,这可以提高性能。

也就是说,不要这样做:

Datagridview.Columns[I].AutoSizeMode = DataGridViewAutoSizeColumnMode.xxxxx;


1
或者我刚才所做的——在填充网格时关闭自动调整大小,然后再打开它。我总共有大约15,000个条目,我会说加速至少是100倍,可能更多。 - Loren Pechtel

11

我知道我来晚了,但最近我对DataGridView控件的自动调整大小速度感到非常不满,所以我觉得有人会从我的解决方案中受益。

我为手动测量和调整DataGridView列大小创建了这个扩展方法。将AutoSizeColumnsMode设置为DataGridViewAutoSizeColumnsMode.None,并在设置DataSource后调用此方法。

/// <summary>
/// Provides very fast and basic column sizing for large data sets.
/// </summary>
public static void FastAutoSizeColumns(this DataGridView targetGrid)
{
    // Cast out a DataTable from the target grid datasource.
    // We need to iterate through all the data in the grid and a DataTable supports enumeration.
    var gridTable = (DataTable)targetGrid.DataSource;

    // Create a graphics object from the target grid. Used for measuring text size.
    using (var gfx = targetGrid.CreateGraphics())
    {
        // Iterate through the columns.
        for (int i = 0; i < gridTable.Columns.Count; i++)
        {
            // Leverage Linq enumerator to rapidly collect all the rows into a string array, making sure to exclude null values.
            string[] colStringCollection = gridTable.AsEnumerable().Where(r => r.Field<object>(i) != null).Select(r => r.Field<object>(i).ToString()).ToArray();

            // Sort the string array by string lengths.
            colStringCollection = colStringCollection.OrderBy((x) => x.Length).ToArray();

            // Get the last and longest string in the array.
            string longestColString = colStringCollection.Last();

            // Use the graphics object to measure the string size.
            var colWidth = gfx.MeasureString(longestColString, targetGrid.Font);

            // If the calculated width is larger than the column header width, set the new column width.
            if (colWidth.Width > targetGrid.Columns[i].HeaderCell.Size.Width)
            {
                targetGrid.Columns[i].Width = (int)colWidth.Width;
            }
            else // Otherwise, set the column width to the header width.
            {
                targetGrid.Columns[i].Width = targetGrid.Columns[i].HeaderCell.Size.Width;
            }
        }
    }
}

虽然我绝对不建议在DGV中填充1000+行,但这种方法可以大大提高性能,同时产生与AutoResizeColumns方法非常相似的结果。

对于10k行:(10k行*12列)

AutoResizeColumns=约3000毫秒

FastAutoSizeColumns=约140毫秒


2
我意识到我晚了近两年才评论,但只是想说这确实帮了我很大的忙。 - WSC

6

为了获得更好的性能提升,我不得不在几个地方禁用自动调整大小。在我的情况下,我启用了 AutoSizeRowsMode, AutoSizeColumnsMode, 和 ColumnHeadersHeightSizeMode 的自动调整大小模式。因此,在将数据绑定到 DataGridView 之前,我需要禁用每个自动调整大小模式:

dataGridView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None;
dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
dataGridView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;

// ... Bind the data here ...

// Set the DataGridView auto-size modes back to their original settings.
dataGridView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
dataGridView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;

在我的情况下,这解决了问题,因为一个数据网格视图需要很长时间才能显示2000条记录,禁用后速度变快了。 - Matthias
这是唯一一个对我有效的解决方案,将10000行的加载时间从约12秒减少到约3秒。我只需要使用ColumnHeadersHeightSizeMode,其他选项已经在此处禁用。此页面提供的所有其他解决方案都没有效果或使初始启动时的加载更糟(但仍然始终在1秒内完成)。 - ygoe

3
如果您不想覆盖DataGridView所需的虚拟模式方法,那么还有另一种选择,如果您可以考虑使用Listview:http://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView
  • 它有一个版本(FastObjectListView),可以在不到0.1秒的时间内构建100,000个对象的列表。
  • 它有一个版本(DataListView)支持数据绑定,另一个(FastDataListView)支持大型(100,000+)数据集上的数据绑定。

3

将AutoSizeColumnsMode设置为None,将AutoSizeRowsMode设置为DisplayedCells,这对我有所帮助。


这对我解决了速度问题。 - Mike Williams

2
我在用户加载10000个项目或对它们进行排序时遇到了性能问题。当我注释掉这一行时:
this.dataEvents.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.AllCells;

一切变得很好。


我还发现将AutoSizeColumnsMode设置为all也会严重影响性能。当然,OP提到他们已经禁用了它,但值得注意的是,这确实有助于提高性能。 - Caleb Vear
是的,你是正确的。我没有注意到源主题已经完成了。 - Danil

1
这解决了我的问题:

for (int z = 0; z < dataGridView1.Columns.Count; z++)
{
   dataGridView1.Columns[z].AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
}
... Code where I change the content of dataGridView1 in a loop ...
for (int z = 0; z < dataGridView1.Columns.Count; z++)
{
   dataGridView1.Columns[z].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
}

1
@Bobby L的回答很好,但会阻塞UI线程。这是我的改编,它在BackgroundWorker中计算列的宽度,然后将计算出的值应用于UI线程。
public partial class Form1 : Form
{
    private BackgroundWorker _worker;

    public Form1()
    {
        InitializeComponent();

        _worker = new BackgroundWorker();
        _worker.DoWork += _worker_DoWork;
        _worker.RunWorkerCompleted += _worker_RunWorkerCompleted;
    }

    private void _worker_DoWork(object sender, DoWorkEventArgs e)
    {
        e.Result = GetAutoSizeColumnsWidth(dataGridView1);
    }

    private void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        SetAutoSizeColumnsWidth(dataGridView1, (int[])e.Result);
    }

    private int[] GetAutoSizeColumnsWidth(DataGridView grid)
    {
        var src = ((IEnumerable)grid.DataSource)
            .Cast<object>()
            .Select(x => x.GetType()
                .GetProperties()
                .Select(p => p.GetValue(x, null)?.ToString() ?? string.Empty)
                .ToArray()
            );

        int[] widths = new int[grid.Columns.Count];
        // Iterate through the columns.
        for (int i = 0; i < grid.Columns.Count; i++)
        {
            // Leverage Linq enumerator to rapidly collect all the rows into a string array, making sure to exclude null values.
            string[] colStringCollection = src.Where(r => r[i] != null).Select(r => r[i].ToString()).ToArray();

            // Sort the string array by string lengths.
            colStringCollection = colStringCollection.OrderBy((x) => x.Length).ToArray();

            // Get the last and longest string in the array.
            string longestColString = colStringCollection.Last();

            // Use the graphics object to measure the string size.
            var colWidth = TextRenderer.MeasureText(longestColString, grid.Font);

            // If the calculated width is larger than the column header width, set the new column width.
            if (colWidth.Width > grid.Columns[i].HeaderCell.Size.Width)
            {
                widths[i] = (int)colWidth.Width;
            }
            else // Otherwise, set the column width to the header width.
            {
                widths[i] = grid.Columns[i].HeaderCell.Size.Width;
            }
        }

        return widths;
    }

    public void SetAutoSizeColumnsWidth(DataGridView grid, int[] widths)
    {
        for (int i = 0; i < grid.Columns.Count; i++)
        {
            grid.Columns[i].Width = widths[i];
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接