关于c#:在使用大数据填充DataGridView时性能降低

 2021-04-27 

Slow performance in populating DataGridView with large data

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

我尝试将DoubleBuffered属性设置为true,将RowHeadersWidthSizeMode设置为禁用,将AutoSizeColumnsMode设置为none。但是仍然是相同的行为。

请协助我。如何提高Grid的性能。

预先感谢,
维杰


如果您有大量的行,例如10,000或更多,
为避免性能泄漏-在数据绑定之前执行以下操作:

1
2
3
4
5
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;

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


通常,关闭自动调整大小和双重缓冲有助于加快DataGridView的填充速度。检查DGV双缓冲是否正确打开:

1
2
3
4
5
6
7
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消息禁用重绘也有帮助:

1
2
3
4
5
6
7
8
9
10
// *** 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();

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

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


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

即不要这样做:

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


我知道我要参加聚会很晚,但是最近我对DataGridView控件的自动调整大小太慢感到厌烦,并感到有人可能会从我的解决方案中受益。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/// <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;
            }
        }
    }
}

尽管我当然绝不建议使用1000行填充DGV,但这种方法在产生与AutoResizeColumns方法非常相似的结果的同时,还带来了巨大的性能优势。

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

AutoResizeColumns =?3000毫秒

FastAutoSizeColumns =?140毫秒


如果您不想覆盖DataGridView的虚拟模式所需的方法,则可以考虑使用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.

我不得不在几个地方禁用自动大小调整,以查看性能上的最大改进。就我而言,我为AutoSizeRowsModeAutoSizeColumnsModeColumnHeadersHeightSizeMode启用了自动调整大小模式。因此,在将数据绑定到DataGridView

之前,我必须禁用它们中的每一个

1
2
3
4
5
6
7
8
9
10
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;


当用户加载10000个项目或对其进行排序时,我的性能出现问题。
当我评论以下行时:

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

一切都变好了。


经过一段时间的故障排除后,我发现速度的主要问题仍然来自从非UI线程更改dataGridView。下面的代码已完全解决了我的datagridview从dataTable datasource

缓慢填充的问题

1
2
3
dataGridView1.Invoke(new MethodInvoker(() =>
                dataGridView1.DataSource = table)
                        );

我遇到了同样的问题,我通过设置

解决了它

1
AutoSizeRowsMode to DisplayedCellsExceptHeaders

并为列设置相同的


@Bobby L的答案很好,但会阻止UI线程。这是我的修改,在将计算出的值应用于UI线程之前,先计算BackgroundWorker中列的宽度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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];
        }
    }
}

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

您应该发现(尤其是仅用于1000左右的行)网格填充实际上是即时的。

祝你好运,


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        Dim asrm = DataGridView1.AutoSizeRowsMode
        Dim ascm = DataGridView1.AutoSizeColumnsMode
        Dim chhs = DataGridView1.ColumnHeadersHeightSizeMode

        DataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None
        DataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None
        DataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing

        DataGridView1.SuspendLayout()
        bs.SuspendBinding()
        DataGridView1.DataSource = Nothing

        For Each t As obj In lt_list
            bs.Add(t)
        Next

        DataGridView1.DataSource = bs

        DataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCellsExceptHeaders
        DataGridView1.AutoSizeColumnsMode = ascm
        DataGridView1.ColumnHeadersHeightSizeMode = chhs

        bs.ResumeBinding()
        DataGridView1.ResumeLayout()

16000个项目,
使用.DataSource <>没什么,需要16秒
.DataSource = Nothing

时为300毫秒