C# 用大数据填充 DataGridView 时性能低下

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10226992/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 12:58:03  来源:igfitidea点击:

Slow performance in populating DataGridView with large data

c#performancedatagridview

提问by Vijay Balkawade

I am using a BindingSourcecontrol (reference here) to populate my DataGridViewcontrol. There are around 1000+ records populating on it. I am using threading to do so. The DataGridView performs very slow in this case.

我正在使用BindingSource控件(参考此处)来填充我的DataGridView控件。上面有大约 1000 多条记录。我正在使用线程来这样做。在这种情况下,DataGridView 的执行速度非常慢。

I tried to set DoubleBufferedproperty to true, RowHeadersWidthSizeModeto disabled, AutoSizeColumnsModeto none. But still the same behavior.

我试图将DoubleBuffered属性设置为 true、RowHeadersWidthSizeModedisabled、AutoSizeColumnsModenone。但仍然是相同的行为。

Please assist me in this. How can I improve the performance of the Grid.

请在这方面帮助我。如何提高网格的性能。

Thanks in advance,
Vijay

提前致谢,
维杰

采纳答案by okarpov

If you have a huge amount of rows, like 10,000 and more, to avoid performance leaks - do the following before data binding:

如果您有大量行(例如 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;

After the data is bound, you may re-enable it.

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

回答by Adrian Conlon

I think you need to consider using your data grid in virtual mode. Basically, you set the extents of the grid up front, then override "OnCellValueNeeded" as required.

我认为您需要考虑在虚拟模式下使用您的数据网格。基本上,您预先设置网格的范围,然后根据需要覆盖“OnCellValueNeeded”。

You should find (especially for only 1000 or so rows) that your grid population becomes effectively instant.

您应该会发现(尤其是对于仅 1000 行左右的行)您的网格填充立即变得有效。

Good luck,

祝你好运,

回答by HischT

If you don't want to override the virtual mode required methods of the DataGridView there is another alternative if you could consider using Listview:

如果您不想覆盖 DataGridView 的虚拟模式所需的方法,如果您可以考虑使用 Listview,还有另一种选择:

http://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView

http://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView

  • It has a version (FastObjectListView) that can build a list of 100,000 objects in less than 0.1 seconds.
  • It has a version (DataListView) that supports data binding, and another (FastDataListView) that supports data binding on large (100,000+) data sets.
  • 它有一个版本 (FastObjectListView),可以在不到 0.1 秒的时间内构建一个包含 100,000 个对象的列表。
  • 它有一个支持数据绑定的版本 (DataListView) 和另一个支持大型 (100,000+) 数据集数据绑定的版本 (FastDataListView)。

回答by saumil patel

Make sure u don't auto-size columns, it improves performance.

确保您不要自动调整列大小,它会提高性能。

i.e. don't do this:

即不要这样做:

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

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

回答by TecMan

Generally turning auto-sizing off and double buffering help to speed up DataGridView population. Check whether the DGV double buffering is turned on properly:

通常关闭自动调整大小和双缓冲有助于加快 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);
}

Disabling the redrawing with the WinAPI WM_SETREDRAWmessage also helps:

使用 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();

If you do not need 2-way data-binding or some features provided by BindingSource (filtering, etc.), you may consider adding rows at one go with the DataGridView.Rows.AddRange()method.

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

The link to the source article with the sample: http://10tec.com/articles/why-datagridview-slow.aspx

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

回答by Bobby L

I know I'm late to the party, but I recently got fed up with how slow the auto resizing was for the the DataGridView control, and felt someone somewhere might benefit from my solution.

我知道我迟到了,但我最近厌倦了 DataGridView 控件自动调整大小的速度有多慢,并且觉得某个地方的某个人可能会从我的解决方案中受益。

I created this extension method for manually measuring and resizing columns in a DataGridView. Set the AutoSizeColumnsModeto DataGridViewAutoSizeColumnsMode.Noneand call this method after setting the DataSource.

我创建了这个扩展方法,用于在 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;
            }
        }
    }
}

While I certainly would never recommend populating a DGV with 1000+ rows, this method results in a huge performance benefit while producing very similar results to the AutoResizeColumnsmethod.

虽然我绝对不会推荐用 1000 多行填充 DGV,但这种方法会带来巨大的性能优势,同时产生与AutoResizeColumns方法非常相似的结果。

For 10k Rows: (10K rows * 12 columns.)

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

AutoResizeColumns= ~3000 ms

AutoResizeColumns= ~3000 毫秒

FastAutoSizeColumns= ~140 ms

FastAutoSizeColumns= ~140 毫秒

回答by Danil

I had problem with performance when user load 10000 items or sort them. When I commented line:

当用户加载 10000 个项目或对它们进行排序时,我遇到了性能问题。当我评论这行时:

this.dataEvents.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.AllCells;

Everything became well.

一切都变得很好。

回答by squareskittles

I had to disable auto-sizing in a few places to see the greatest improvement in performance. In my case, I had auto-size modes enabled for AutoSizeRowsMode, AutoSizeColumnsMode, and ColumnHeadersHeightSizeMode. So I had to disable each of these before binding the data to the DataGridView:

我不得不在一些地方禁用自动调整大小才能看到性能的最大改进。就我而言,我不得不启用了自动大小模式AutoSizeRowsModeAutoSizeColumnsModeColumnHeadersHeightSizeMode。因此,在将数据绑定到以下内容之前,我必须禁用其中的每一个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;

回答by Déric Dallaire

@Bobby L answer is great, but blocks the UI thread. Here's my adaptation which calculates the widths of the column in a BackgroundWorker before applying the calculated values to the UI thread

@Bobby L 的回答很棒,但会阻塞 UI 线程。这是我的改编,它在将计算值应用到 UI 线程之前计算 BackgroundWorker 中的列的宽度

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];
        }
    }
}