VJOY
VJOY

Reputation: 3792

Slow performance in populating DataGridView with large data

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

I tried to set DoubleBuffered property to true, RowHeadersWidthSizeMode to disabled, AutoSizeColumnsMode to none. But still the same behavior.

How can I improve the performance of the Grid?

Upvotes: 28

Views: 104922

Answers (16)

rjs
rjs

Reputation: 1

If you have a databounded DataGridView also the data source can be the reason for this problem.

In my case a get property inside the data source always throws an exception because it depends on other properties which were null at the moment of populating.

class Data
{
    double? NominalValue { get; set; } = null;
    double? RealValue { get; set; } = null;
    double? ErrorPercent
    {
        get
        {
            try
            {
                return NominalValue / RealValue * 100;
            }
            catch (Exception ex)
            {
                return null;
            }
        }
    }
}
BindingList<Data> DataSource = new BindingList<Data>();

After preventing this exception the DataGridView was populated in a normal way:

    double? ErrorPercent
    {
        get
        {
            double? errPerc = null;
            if (NominalValue != null && RealValue != null)
            {
                errPrec = NominalValue / RealValue * 100;
            }
            return errPerc;
        }
    }

Hope this helps...

Upvotes: 0

Sachin Mohite
Sachin Mohite

Reputation: 11

The best way I found is simply set visible property of DataGridView to false before loading, after complete set to true.

    DataGridView1.Visible = False
    
    'fill your data here

    DataGridView1.Visible = True

Upvotes: 0

user18571071
user18571071

Reputation: 31

Setting AutoSizeColumnsMode to None and AutoSizeRowsMode to DisplayedCells fixed it for me.

Upvotes: 3

Michael Hutter
Michael Hutter

Reputation: 1532

This solved my problem:

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

Upvotes: 1

Jan
Jan

Reputation: 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 items, 16 seconds with .DataSource <> Nothing, 300 miliseconds with .DataSource = Nothing

Upvotes: -1

jatora
jatora

Reputation: 11

After troubleshooting this a while, I found that the main issue with speed still comes from changing the dataGridView from a non-UI thread. The code below has completely solved my datagridview populating slowly from a dataTable datasource

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

Upvotes: 0

Muhammad Waqas Aziz
Muhammad Waqas Aziz

Reputation: 851

I had the same problem and i resolved it by setting

AutoSizeRowsMode to DisplayedCellsExceptHeaders

And set the same for columns too

Upvotes: 0

Der
Der

Reputation: 757

@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

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

Upvotes: 1

okarpov
okarpov

Reputation: 874

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

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.

Upvotes: 44

Kevin
Kevin

Reputation: 18243

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:

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;

Upvotes: 8

saumil patel
saumil patel

Reputation: 247

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

i.e. don't do this:

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

Upvotes: 17

Danil
Danil

Reputation: 895

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

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

Everything became well.

Upvotes: 2

Bobby L
Bobby L

Reputation: 171

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.

I created this extension method for manually measuring and resizing columns in a DataGridView. Set the AutoSizeColumnsMode to DataGridViewAutoSizeColumnsMode.None and call this method after setting the 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 AutoResizeColumns method.

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

AutoResizeColumns = ~3000 ms

FastAutoSizeColumns = ~140 ms

Upvotes: 11

Tecman
Tecman

Reputation: 3009

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

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_SETREDRAW message also helps:

// *** 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.

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

Upvotes: 18

HischT
HischT

Reputation: 953

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:

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.

Upvotes: 3

Adrian Conlon
Adrian Conlon

Reputation: 3941

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.

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

Good luck,

Upvotes: 0

Related Questions