Skip to main content Skip to footer

How to Apply Data Virtualization in FlexGrid for WPF

FlexGrid is a powerful, high-performance WPF datagrid that can load large data sets quickly. One reason FlexGrid is excellent with large data sets is through its support for data virtualization. With data virtualization, you can load data on-demand as the user scrolls.

The data virtualization feature is implemented in the C1DataCollection library. This article will show you how to implement the virtual collection feature using FlexGrid for WPF. I will also demonstrate how to display this:

 

What is Data Virtualization

Data Virtualization enables efficient processing of data by allowing large amounts of data to be loaded in a shorter time. It works by loading data on-demand in chunks as the user scrolls, also known as incremental loading. In most scenarios, the data comes from different remote source systems. The data source impacts the time required to display the data and brings an inherent cost in network usage. The data is divided and sent to the client in chunks or pages to help solve this problem.

How to Implement Data Virtualization

When working with ComponentOne, you'll want to use our latest data collection library, C1DataCollection, which provides data virtualization for any datagrid or list control.

The C1DataCollection library provides the base class C1VirtualDataCollection. This class has an abstract GetPageAsync() method that returns the items that will populate the collection. The virtual data collection caches data and dispatches the pages' requests according to a series of parameters that prevents several pages from being requested at once.

Below is a sample extension of the C1VirtualDataCollection base class.

public class VirtualModeCollectionView : C1VirtualDataCollection<Customer>  
{  

public VirtualModeCollectionView()  
    {  

PageSize = 50;  
    }  

public int TotalCount { get; set; } = 250_000;

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)  
    {  

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

    }  
}

You can set the page size in the constructor. Even though we use the term "page" here, we aren't implementing classic paging with next and previous buttons. We are essentially paging, but it's happening entirely as the user scrolls up and down the list of items.

The GetPageAsync method returns the items on the page as well as a token to the next page. This method uses pageIndex, startingIndex, count, sortDescriptions, filterExpression, and cancellation Token as parameters. The pageIndex is the index of the requesting page, startingIndex is the index where the returned items will be inserted, and the count is the number of things to be returned.

The code above simulates getting data from the server. For another sample, check out this topic, which gets virtualized data from an OData source.

Adding Data Virtualization to FlexGrid

To populate FlexGrid with this virtual data collection, we set its ItemsSource to a C1CollectionView passing in the instance of our VirtualModeCollectionView. The C1CollectionView adds the common data transformation, such as sorting, filtering, and grouping.

private VirtualModeCollectionView _virtualCollection;

public MainWindow()  
{  

InitializeComponent();  

UpdateData();  
}

private void UpdateData()  
{  

try  
    {  

_virtualCollection = new VirtualModeCollectionView() { Mode = C1.DataCollection.VirtualDataCollectionMode.Manual };  

c1FlexGrid1.ItemsSource = new C1CollectionView(_virtualCollection);  

c1FlexGrid1.ScrollPositionChanged += C1FlexGrid1_ScrollPositionChanged;  

_virtualCollection.LoadAsync(c1FlexGrid1.ViewRange.Row, c1FlexGrid1.ViewRange.Row2);

    }  

catch(Exception e)  
    {  

MessageBox.Show(e.Message);  
    }  
}

private void C1FlexGrid1_ScrollPositionChanged(object sender, EventArgs e)  
{  

_virtualCollection.LoadAsync(c1FlexGrid1.ViewRange.Row, c1FlexGrid1.ViewRange.Row2);  
}

The virtual data collection has a mode to determine how the data will be loaded: Automatic or Manual. Automatic loads data as the items are accessed, but it requires the UI control to support it. The manual mode allows you to use data virtualization with any UI control by calling the LoadAsync method yourself.

As you can see in the code above, FlexGrid is using the manual mode. FlexGrid is using the manual mode because the .NET 4.5.2 version of FlexGrid is now relatively old compared to the new C1DataCollection. At the time of this writing, FlexGrid hasn't been updated to support automatic, but the .NET 5 version will support automatic and therefore require even less code.

With automatic, the .NET 4.5.2 version of FlexGrid for WPF, and the MS DataGrid control, iterates the collection, which breaks the virtualization of C1VirtualDataCollection. In this case, we must use the manual mode to handle the ScrollChanged event to load more pages when scrolling to the bottom. In the code above, when the ScrollChanged event is called, the next record for the new viewport range is loaded asynchronously using the LoadAsync method of C1VirtualDataCollection.

How to Apply Data Virtualization in FlexGrid for WPF

Displaying Preview Text in Cells

One of the issues with data virtualization is that the user may scroll faster than data can be physically downloaded from the server. In this case, rather than block the UI, we can display preview text in the cells such as "Loading…" so that the datagrid doesn't appear as empty. The preview text is achieved by creating a sentinel item in the virtual collection.

public override Customer this[int index]  
{  

get  
    {  

var item = base[index];  

  if (item != null)  

return item;  

return new Customer() { FirstName = "Loading...", LastName = "Loading...", Address = "Loading...", City = "Loading...", Email = "Loading..." };  
    }  
} 

All the code examples above can be downloaded in the attached sample.

ComponentOne Product Manager Greg Lutz

Greg Lutz

comments powered by Disqus