How to Optimize Performance for FlexGrid in WinForms
When displaying a large quantity of tabular data with the help of a grid control, you've probably run in a position where the grid becomes extremely slow in rendering, scrolling, filtering, or populating the data. But if you can overcome these limitations, it will help increase the user experience.
Download ComponentOne today and try it yourself!
This blog will go through well-known techniques for optimizing and creating the fastest WinForms DataGrid using our flagship control FlexGrid for WinForms.
Using Data Virtualization
Data Virtualization enables efficient processing of data by allowing large amounts of data to be loaded in a lesser periodFlexGrid for WinForms supports data virtualization by using C1DataCollection, which enables data virtualization for larger data sets with enhanced performance. With this approach, the grid recognizes the total number of rows but loads and displays only the rows that the user scrolls down a list in real-time. This powerful feature is called on-demand or Incremental loading.
To implement this, you need to create a class that inherits from the C1VirtualDataCollection and implement the GetPageAsync method, which returns one page of data from the data source you have set. The piece of code looks like this:
public class VirtualModeCollectionView : C1VirtualDataCollection<Customer>
{
public int TotalCount { get; set; } = 1000;
protected override async Task<Tuple<int, IReadOnlyList<Customer>>> GetPageAsync(int pageIndex, int startingIndex, int count, IReadOnlyList<SortDescription> sortDescriptions = null, FilterExpression filterExpression = null, CancellationToken cancellationToken = default(CancellationToken))
{
await Task.Delay(500, cancellationToken);//Simulates network traffic.
return new Tuple<int, IReadOnlyList<Customer>>(TotalCount, Enumerable.Range(startingIndex, count).Select(i => new Customer(i)).ToList());
}
}
The following GIF displays FlexGrid's virtual scrolling mode.
This documentation page has more information on Data Virtualization.
Using BeginUpdate and EndUpdate Methods
The grid's performance can be improved by utilizing the BeginUpdate and EndUpdate methods. Before making significant changes, you should call the BeginUpdate method to stop the repaint process by locking the view. Once you're finished, you should call the EndUpdate method to unlock the views and resume the repainting process.
This optimization is beneficial when adding several rows to the grid because it recalculates ranges and updates scrollbars. This results in a decrease in flicker while increasing the performance.
With the code below, you can quickly add many rows to a WinForms FlexGrid and notice how the EndUpdate method is called inside a "finally" block to ensure that repainting is done correctly.
void UpdateGrid(C1FlexGrid c1FlexGrid1)
{
// suspend painting to avoid flicker
c1FlexGrid1.BeginUpdate();
c1FlexGrid1.Rows.Count = 1;
for (int i = 1; i < 1000000; i++)
c1FlexGrid1.AddItem("Row " + i.ToString());
// always restore painting
c1FlexGrid1.EndUpdate()
}
Keep AutoResize Property to False
When new data is read from the data source, and the AutoResize property for a bound grid is set to true, the control automatically resizes its columns to fit the widest entry. If there are a lot of rows and columns in the data source, the automatic resizing could take a long time. In these circumstances, you might try disabling AutoResize and adjusting the column widths directly in the source code.
Assign Styles Dynamically
FlexGrid enables the creation and assignment of cell styles to rows, columns, and arbitrary cell ranges. With this feature, you can change how the grid cells look based on certain conditions. Generally, you use the SetCellStyle() method to do this, but in this case, you'd have to change the style every time the cell value is changed. If the grid is connected to a data source, the styles will be lost once the data source is reset following any actions like sorting or filtering the data.
The OwnerDrawCell event is used to modify how each cell is rendered or to override the painting entirely.
private void Form1_Load(object sender, EventArgs e)
{
// Fill a column with random values.
c1FlexGrid1.Cols[1].DataType = typeof(int);
Random rnd = new Random();
for (int r = 1; r < c1FlexGrid1.Rows.Count; r++)
{
c1FlexGrid1[r, 1] = rnd.Next(-10000, 10000);
}
// Create style used to show negative values.
c1FlexGrid1.Styles.Add("Red").ForeColor = Color.Red;
// Enable OwnerDraw by setting the DrawMode property.
c1FlexGrid1.DrawMode = C1.Win.C1FlexGrid.DrawModeEnum.OwnerDraw;
c1FlexGrid1.OwnerDrawCell += new C1.Win.C1FlexGrid.OwnerDrawCellEventHandler(C1FlexGrid1_OwnerDrawCell);
}
private void C1FlexGrid1_OwnerDrawCell(object sender, OwnerDrawCellEventArgs e)
{
if(!e.Measuring)
{
// Check that the row and column contain integer data.
if (e.Row > 0 && c1FlexGrid1.Cols[e.Col].DataType == typeof(int))
{
// Apply the style "Red"
e.Style = c1FlexGrid1.Styles["Red"];
}
}
}
Avoid Modifying Styles in the OwnerDrawCell Event
Another option to boost efficiency is to avoid modifying the CellStyle object supplied as a parameter in the OwnerDrawCell event. As an alternative, you can change the e.Style option to a new value. This is crucial because the CellStyle returned by the event handler is frequently used by other cells.
For example, you could mistakenly change a Normal style of the WinForms FlexGrid, which would affect other similar cells in the grid.
// ** CORRECT APPROACH:
private void C1FlexGrid1_OwnerDrawCell(object sender, C1.Win.C1FlexGrid.OwnerDrawCellEventArgs e)
{
// Select style to use when painting this cell:
e.Style = MyStyleSelector(e.Row, e.Col);
}
// ** WRONG APPROACH:
private void C1FlexGrid1_OwnerDrawCell(object sender, C1.Win.C1FlexGrid.OwnerDrawCellEventArgs e)
{
// Select style to use when painting this cell:
// This is wrong because changing any CellStyle objects invalidates the
// grid, which would cause this event handler to be called over and
// over again.
e.Style.Color = MyColorSelector(e.Row, e.Col);
}
Show Ellipses in a Single Column
The grid will draw quickly if the amount of data to be loaded is short, so in the case of long string values, simply display an ellipses symbol at the end according to the column width. You should utilize the Trimming property to see ellipses in a single column of your grid. The Trimming property can be set to either None, Character, Word, EllipsisCharacter, EllipsisWord, or EllipsisPath to define how long strings are clipped to fit the cell.
To display ellipses at the end of the WinForms FlexGrid column, the Trimming property must be set as follows:
c1FlexGrid1.Cols[columnIndex].StyleNew.Trimming =StringTrimming.EllipsisCharacter;
Show Multi-line Text in a Cell
You can use the WordWrap and Height properties to display many lines of text in a single cell. To determine whether the grid breaks long strings with spaces into multiple lines, the WordWrap property is used.
Refer to the code below to see how a multi-line text should be effectively displayed in the WinForms FlexGrid.
// Set the WordWrap property.
c1FlexGrid1.Styles["Normal"].WordWrap = true;
// Set the row height.
c1FlexGrid1.Rows[1].Height = 2 * fg.Rows.DefaultSize;
// Add text to the cell.
c1FlexGrid1[1, 2] = "This is the first line. \r\n This is the second line.";
Retrieve Data Sorting When Bound to a Data Table
Setting the sort expression at the run time immediately reflects the changes in the data view; for this, you can utilize the Sort attribute of the default view and a sort expression. The Sort property uses a string with the column name and either ASC (the default) or DESC to sort the column in ascending or descending order. Separating column names with commas makes it possible to sort several columns.
The below code shows how to use the sort expression with the Sort property in the WinForms FlexGrid.
// Sort the data by the UnitsInStock column then by the ProductID column.
this.productsBindingSource.Sort = "UnitsInStock ASC, ProductID ASC";
Use UseScrollWindow Property
The ScrollWindow feature enables the redrawing of only the cells with dirty data when scrolling and redrawing only the cells that are now being scrolled. To considerably increase the performance of scrolling, the value of this property should be True, and it is set by default, so you need not use any additional code to enable it.
There may be some scenarios in which visual artifacts can occur with the complex OwnerDraw where other controls or images are drawn on top of the FlexGrid, so in this case, you need to disable it by setting protected property UseScrollWindow as shown below:
public class MyFlexGrid : C1FlexGrid
{
public MyFlexGrid()
{
UseScrollWindow = false;
}
}
You can implement all the above-given approaches in your project accordingly to make your FlexGrid render faster. We are hoping that you enjoy the optimization features that are offered with the C1FlexGrid control.
Download ComponentOne today and try it yourself!
Try it out and leave your feedback or questions in the comments section.
Happy Coding!