Reputation: 3792
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
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
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
Reputation: 31
Setting AutoSizeColumnsMode to None and AutoSizeRowsMode to DisplayedCells fixed it for me.
Upvotes: 3
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
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
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
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
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
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
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
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
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
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
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
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
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