Introduction
Using the DataGrid
for millions of rows with a slow datasource will cause the user interface to freeze and give the end user a bad experience. UI virtualization comes automatically with the VirtualizationStackPanel
inside the DataGrid
, but Data Virtualization is not provided out of the box. Data Virtualization is the feature of providing only the data needed to present for the end user.
Typically, around 20-100 rows will be shown at a time. When the user starts scrolling, data virtualization is used to provide the requested rows. In this example, the pagesize
is set to 20
. What it means is that when the DataGrid
is requesting a row, the next 20 rows will actually be loaded and prepared for presentation.
The idea is to use a CollectionView
to encapsulate a DataTable
. For the purpose, I have created a CollectionView
called DataGridCollectionView
. The DataGridCollectionView
will be used as datasource
for the ItemsSource
property of the DataGrid
. When the DataGrid
is loaded, it will call out for row numbers 0
, 1
, 2
, 3
, etc. until the available space is filled. To be more effective than loading just one row at the time, the example here is using a pagesize
so you batch load your rows.
Using the Code
The DataGridCollectionView
comes with two events:
The idea is that the ItemsCount
is raised to tell the DataGrid
how many rows can be expected.
The ItemsRequest
is the actual request for items. The request is done in a background thread and has a startindex
(the row number) and the number of items to provide. An Item
is a DataRow
in this example.
You hook it up like this:
DataGridCollectionView dataGridCollectionView = new DataGridCollectionView(dataTable);
dataGridCollectionView.ItemsCount += OnItemsCount;
dataGridCollectionView.ItemsRequest += OnItemsRequest;
In the event handlers for this example, the count
and DataRows
are provided like this:
private void OnItemsCount(DataGridCollectionView arg1, CountEventArgs arg2)
{
arg2.Count = Count;
}
private void OnItemsRequest(DataGridCollectionView arg1, ItemsEventArgs arg2)
{
int startIndex = arg2.StartIndex;
int count = arg2.RequestedItemsCount;
List<object> items = new List<object>();
for (int i = startIndex; i < startIndex + count; i++)
{
DataRow row = dataTable.NewRow();
row[0] = i.ToString();
row[1] = rnd.Next(1000).ToString();
row[2] = rnd.Next(1000).ToString();
items.Add(row);
Thread.Sleep(10);
}
arg2.SetItems(items);
}
As can be seen, you give back the rows in the SetItems
method. The DataTable
as you probably have guessed has 3 columns since the DataRow
consists of 3 cells. You should easily be able to adapt this to your own needs.
The example is using the standard Model-View-ViewModel pattern.
The main XAML code defining the DataGrid
.
<DataGrid ItemsSource="{Binding TableSource, Mode=OneWay}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=[0]}" Header="Row number" />
<DataGridTextColumn Binding="{Binding Path=[1]}" Header="column 2"/>
<DataGridTextColumn Binding="{Binding Path=[2]}" Header="column 3"/>
</DataGrid.Columns>
</DataGrid>
The ViewModel
contains a property called TableSource
that is the DataGridCollectionView
.
A demo project is attached that should clear up most questions.
History
- 29th October, 2018: Initial version