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

问题描述 投票:0回答:16

我正在使用

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

我尝试将

DoubleBuffered
属性设置为 true,将
RowHeadersWidthSizeMode
设置为禁用,将
AutoSizeColumnsMode
设置为无。但仍然是同样的行为。

如何提高网格的性能?

c# performance datagridview
16个回答
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;

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


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

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

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


17
投票

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

即不要这样做:

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


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

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

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

自动调整列大小 = ~3000 毫秒

FastAutoSizeColumns = ~140 毫秒


8
投票

我必须在一些地方禁用自动调整大小才能看到性能的最大改进。就我而言,我为

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;

3
投票

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

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

  • 它有一个版本(FastObjectListView),可以构建100,000个列表 物体在 0.1 秒内完成。
  • 它有一个版本(DataListView) 支持数据绑定,另一个(FastDataListView) 支持大型(100,000+)数据集上的数据绑定。

3
投票

设置 AutoSizeColumnsMode 设置为 None 并且 AutoSizeRowsMode 到 DisplayedCells 帮我修好了。


2
投票

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

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

一切都变好了。


1
投票

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

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

0
投票

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

您应该发现(特别是对于只有 1000 行左右的行)您的网格填充变得有效即时。

祝你好运,


0
投票

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

AutoSizeRowsMode to DisplayedCellsExceptHeaders

并对列也设置相同的


0
投票

解决这个问题一段时间后,我发现速度的主要问题仍然来自于从非 UI 线程更改 dataGridView。 下面的代码完全解决了我的 datagridview 从 dataTable 数据源缓慢填充的问题

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

0
投票

我发现的最好方法是在加载之前将 DataGridView 的可见属性设置为 false,在完成设置为 true 后。

    DataGridView1.Visible = False
    
    'fill your data here

    DataGridView1.Visible = True

0
投票

如果您有数据绑定的 DataGridView,数据源也可能是导致此问题的原因。

在我的例子中,数据源内的 get 属性总是抛出异常,因为它依赖于填充时为 null 的其他属性。我没有处理这个异常(我知道这很愚蠢),但我在控制台上看到了一条日志消息,它给了我正确的信息。

希望这有帮助...


-1
投票
        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 件商品, 16 秒,.DataSource <> Nothing, 300 毫秒,.DataSource = Nothing

© www.soinside.com 2019 - 2024. All rights reserved.