Why is DataGridView Slow at Scrolling, Rendering, Filtering and Populating?
by Igor Katenov, the lead developer at 10Tec
Almost every .NET developer using the WinForms package faced situations when the stock DataGridView was slow. When developers try to use it in a more or less serious application, they see that DataGridView can be slow in rendering, scrolling, filtering or populating it with data.
In this article we gathered well-known approaches allowing you to speed up DataGridView. The article is accompanied with a sample demonstrating how these tricks improve DataGridView performance when you populate it with data:
Slow DataGridView population
Slow DataGridView population is a popular discussion topic on many forums for programmers. But DataGridView can load and display a large amount of data fast enough if you use the proper approach.
The fastest way to populate an instance of the DataGridView control is to use data binding through its DataSource
property. In the simplest case you can prepare your data by putting them into an ADO.NET DataTable and display it very fast using a statement like this:
dataGridView1.DataSource = dataTable1;
The other way of using DataGridView is unbound mode in which you create rows row-by-row using the Rows.Add()
method. You will feel no difference between this way of DataGridView population and data binding if you work with several rows. But if your grid has hundreds or thousands of rows, the performance degrades dramatically when the number of rows increases. As you may have already noticed from the above screenshot of the DataGridView population test app, adding rows in unbound mode may work up to 50 times slower for 10’000 rows.
However, we can speed up DataGridView in this case. If you solved the task of unbound DataGridView population for the first time, you could write code like this to create the DataGridView on the screenshot above:
dataGridView1.ColumnCount = 3;
dataGridView1.Columns[0].Name = "Text";
dataGridView1.Columns[1].Name = "Integer";
dataGridView1.Columns[2].Name = "Date";
for (int i = 1; i <= numRowCount.Value; i++)
{
object[] row = {String.Format("Text {0}", i), i, DateTime.Now};
dataGridView1.Rows.Add(row);
}
The problem is in the loop and the Rows.Add()
method – they both make the DataGridView slow. DataGridView works much faster if you prepare an array of rows and "feed" them to the AddRange()
method of the Rows
collection. Thus, a better version of our code could look like the following one:
dataGridView1.ColumnCount = 3;
dataGridView1.Columns[0].Name = "Text";
dataGridView1.Columns[1].Name = "Integer";
dataGridView1.Columns[2].Name = "Date";
List<DataGridViewRow> rows = new List<DataGridViewRow>();
for (int i = 1; i <= numRowCount.Value; i++)
{
DataGridViewRow row = new DataGridViewRow();
row.CreateCells(dataGridView1);
row.Cells[0].Value = String.Format("Text {0}", i);
row.Cells[1].Value = i;
row.Cells[2].Value = DateTime.Now;
rows.Add(row);
}
dataGridView1.Rows.AddRange(rows.ToArray());
You should know about two more tricks that can be used to improve the situation with DataGridView slow population even more.
First, check whether you do not use an auto-size mode for DataGridView columns: the AutoSizeColumnsMode
property of the whole DataGridView control should be set to DataGridViewAutoSizeColumnsMode.None
. Just in case, check also the corresponding setting for DataGridView columns (the DataGridViewColumn class): their AutoSizeMode
property should be DataGridViewAutoSizeColumnMode.None
too.
Second, slow DataGridView population may be not so slow if you disable redrawing in the control while you are adding data to it. Some developers suggest using the BeginInit()/EndInit()
method calls as this approach is used in the Windows Form Designer code generated automatically. To call these methods, you need to look at your DataGridView through the prism of the ISupportInitialize
interface as those methods are not available directly:
((ISupportInitialize)dataGridView1).BeginInit();
// populate DataGridView
((ISupportInitialize)dataGridView1).EndInit();
Unfortunately, our tests showed no noticeable difference when we used these methods. What works much better, especially if you need to add rows in unbound mode row-by-row, is the WM_SETREDRAW
Windows API message. To use it with DataGridView, add the following definitions to your code:
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
private const int WM_SETREDRAW = 11;
Having this, you can disable DataGridView updates before adding rows or other operations using this call:
SendMessage(dataGridView1.Handle, WM_SETREDRAW, false, 0);
After all rows are added, enable redrawing and redraw the contents of the control using these statements:
SendMessage(dataGridView1.Handle, WM_SETREDRAW, true, 0);
dataGridView1.Refresh();
Slow DataGridView scrolling and rendering
DataGridView can be slow not only when you add data to it, but even when the platform needs to repaint DataGridView. Slow DataGridView rendering can be fixed using so called double buffering. It is a technique used to draw a control’s contents to a temporary buffer and then copying the whole control image on the screen, which may greatly reduce or prevent flickering. Many .NET controls have the public Boolean DoubleBuffered
property that allows the developer to easily enable double buffering, but for some unknown reasons the authors of the .NET Framework decided to hide it for DataGridView.
Fortunately, there are several ways to access this functionality in DataGridView to improve its slow rendering. One of them is using the .NET reflection:
// Double buffering can make DGV slow in remote desktop
if (!System.Windows.Forms.SystemInformation.TerminalServerSession)
{
Type dgvType = dataGridView1.GetType();
PropertyInfo pi = dgvType.GetProperty("DoubleBuffered",
BindingFlags.Instance | BindingFlags.NonPublic);
pi.SetValue(dataGridView1, value, null);
}
Note that we recommend that you check whether DataGridView is not used in a client application under remote desktop as enabling double buffering in this environment may make DataGridView rendering even slower.
Double buffering in DataGridView also helps to solve the problem of slow DataGridView scrolling.
Filtering DataGridView (hiding rows)
As you have seen, we can improve the situation with slow data loading into DataGridView. But don’t be very optimistic if you need to implement filtering in the control. Hiding DataGridView rows is extremely slow, and it seems the developer community has not found the solution for this problem yet.
If DataGridView is bound to a DataTable using its DataSource property, implementing a simple filter like substring search for a column is trivial. We can exploit the DataTable functionality for that, and this filtering will work very fast – even for thousands of rows:
dataTable1.DefaultView.RowFilter = string.Format("Text LIKE '%{0}%'", textBoxFilterString.Text);
The situation is much worse if your DataGridView works in unbound mode and you need to hide or show its rows calculating a condition for every row. The same filter by substring in unbound DataGridView works enormously slow even for several thousands of rows:
string substring = textBoxFilterDGV.Text;
foreach (DataGridViewRow row in dataGridView1.Rows)
{
row.Visible = ((string)row.Cells[1].Value).IndexOf(substring, 0, StringComparison.CurrentCultureIgnoreCase) != -1;
}
If you google something like "datagridview slow filtering", you can find recipes like this post that suggests wrapping the filtering code with the SuspendLayout()/ResumeLayout()
methods of DataGridView. Unfortunately, we could not see any impact of doing this. The tricks with turning off redrawing in DataGridView described in the first part of this article also did not help.
Mark Rideout, a DataGridView program manager from Microsoft, explains in the post mentioned above that DataGridView is not optimized for the case when you switch the visibility of rows. He suggests using virtual mode instead. Some developers also recommend that if you need to filter DataGridView rows, it will be much more efficient to copy the full row set into a temporary collection, exclude required rows from it and then upload the new set of rows back into DataGridView. For more info, see this StackOverflow discussion thread:
C# Datagridview performance suffers when hiding many rows
When we designed our iGrid for WinForms, we knew that filtering rows is a frequent task in everyday life, and we optimized our grid control for this scenario. Unbound mode is the main work mode of iGrid, and to filter iGrid in this mode, you simply set the visibility of each row to True or False depending on a condition. This works very fast without any tricks, such as copying the data into a temporary storage to filter them there. You can download a demo application demonstrating that DataGridView hides rows slowly, especially in comparison with iGrid, using the download link at the bottom:
As you can see, iGrid beats DataGridView when filtering rows and can be also a good replacement for the slow DataGridView control in many other scenarios. Follow the link below to discover more features of iGrid, such as auto-filter functionality, built-in grouping, etc.