https://github.com/chaoses-ib/devzest.datavirtualization
Component for displaying and interacting a large data set in WPF application.
https://github.com/chaoses-ib/devzest.datavirtualization
collection-view data-grid data-virtualization grid-view wpf
Last synced: 6 months ago
JSON representation
Component for displaying and interacting a large data set in WPF application.
- Host: GitHub
- URL: https://github.com/chaoses-ib/devzest.datavirtualization
- Owner: Chaoses-Ib
- License: mit
- Created: 2023-08-25T18:56:04.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2023-08-26T22:08:05.000Z (over 2 years ago)
- Last Synced: 2025-06-04T21:44:46.704Z (7 months ago)
- Topics: collection-view, data-grid, data-virtualization, grid-view, wpf
- Language: C#
- Homepage:
- Size: 155 KB
- Stars: 4
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: License.txt
Awesome Lists containing this project
README
# DevZest.DataVirtualization
[](https://www.nuget.org/packages/DevZest.DataVirtualization)
> The original post can be accessed from Internet Archive: [The DevZest Blog | WPF Data Virtualization](http://web.archive.org/web/20180814144210/http://www.devzest.com:80/blog/post/WPF-Data-Virtualization.aspx)

## Introduction
In a typical WPF + WCF LOB application, displaying and interacting a large data set is not a trivial task. While several posts on internet forums discuss data virtualization, none of them has all the following:
- Selection, sorting and filtering works well as if all data are stored locally;
- Data loading as needed, in a separate thread, without blocking the UI;
- Visual feedback when data is loading; if failed, user can retry the last failed attempt.
The attached source code contains a highly reusable component which resolves all the above issues, together with a demo WPF application.
## Built-in WPF UI Virtualization
First of all, your UI should only demand data items actually visible on screen. As of .NET 3.5 SP1, this is what you can do for `ItemsControl` and derivatives:
- Make the number of UI elements to be created proportional to what is visible on screen using [`VirtualizingStackPanel.IsVirtualizing="True"`](https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.virtualizingstackpanel.isvirtualizing?view=netframework-4.0).
- Have the framework recycle item containers instead of (re)creating them each time, by setting [`VirtualizingStackPanel.VirtualizationMode="Recycling"`](https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.virtualizationmode?view=netframework-4.8).
- Defer scrolling while the scrollbar is in action by using [`ScrollViewer.IsDeferredScrollingEnabled="True"`](https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.scrollviewer.isdeferredscrollingenabled?view=netframework-4.8). This improves *perceived* performance, by waiting until the user releases the scrollbar thumb to update the content.
For more information, please read [this post](http://web.archive.org/web/20180814144210/http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/98090161-0abf-4799-bbcb-852dcc0f0608).
## `VirtualList` & `VirtualListItem`
`VirtualList` is the `IList` implementation that performs the data virtualization. To create a new instance of `VirtualList`, you need an `IVirtualListLoader` instance, to load small chunks (or pages) and return the total number of the entire collection:
```csharp
public interface IVirtualListLoader
{
bool CanSort { get; }
IList LoadRange(int startIndex, int count, SortDescriptionCollection sortDescriptions, out int overallCount);
}
...
public partial class VirtualList : IList>, ...
{
...
public VirtualList(IVirtualListLoader loader)
...
}
```
`VirtualList` contains an array of `VirtualListItem`, which is used as proxy of the real data:
```csharp
public sealed class VirtualListItem : VirtualListItemBase, INotifyPropertyChanged
{
public VirtualList List
{
get;
}
public int Index
{
get;
}
public bool IsLoaded
{
get;
}
public T Data
{
get;
}
public void Load();
public void LoadAsync();
}
```
You can data binding `VirtualListItem` just like any other object. The following XAML code binds ListView column to `VirtualList` object (please note the binding path `Data.PropertyName` is used):
```xaml
```
Data is loaded explicitly: unless `Load` or `LoadAsync` is called, the `IsLoaded` will always be `false` and `Data` will always be `default(T)`. Once data loaded, it will remain unchanged until the whole list is refreshed. Calling `Load` or `LoadAsync` will load one page of data items if not already loaded, and the first page will be loaded asynchronously when the collection is created or refreshed.
By setting `ContentControl`'s `VirtualListItemBase.AutoLoad` attached property to `true`, `LoadAsync` will be called once `VirtualListItem` object is set as `ContentControl.Content`. The following XAML code will load data automatically when `VirtualListItem` object is attached to `ListViewItem`:
```xaml
<Setter Property="dz:VirtualListItemBase.AutoLoad" Value="true" />
```
**Summary:** `VirtualListItem` object acts as proxy of the real data. Selection works well as if all data stored locally, and data is loaded as needed, in a separate thread, without blocking the UI.
## Data Loading Visual Feedback
The UI should display a "Loading..." animation when data is loading, to tell end user what is going on. When something is wrong, an error message should be displayed and allows end user to retry the last failed attempt. Well a picture is worth a thousand words:

The `VirtualListLoadingIndicator` control is provided for this purpose. It has a `IsAttached` attached property, when set to `true`, display itself at the adorner layer of attached `ItemsControl` with its `ItemsSource` is set to a `VirtualList`. The following XAML code enables the data loading visual feedback for the ListView:
```xaml
...
```
You can customize `VirtualListLoadingIndicator` control by overriding the default control template just like any other WPF control; and since the `VirtualListLoadingIndicator` control is loosely coupled with other classes, you can replace it completely with your own class without changing any other existing class.
## Sorting and Filtering
WPF never binds directly to a collection, but to a view. The view can be obtained by calling:
```csharp
CollectionViewSource.GetDefaultView(listView.ItemsSource);
```
The return value is a type implementing `ICollectionView`. If the `ItemsSource` is bound to a `List`, which implements `System.Collections.IList`, `GetDefaultView` will choose to return `ListCollectionView` if `ItemsSource` does not implement `ICollectionViewFactory`. When sorting and filtering against `ListCollectionView` (the default implementation), it will demand all data items from the original `IList` first, and do the sorting and filtering locally. Apparently this will destroy all our effect of data virtualization -- we need to implement our own `ICollectionView`, delegating the sorting and filtering to the underlying `IVirtualListLoader`.
Due to the nature that data is virtualized, providing multiple view for the same `VirtualList` does not make too much sense -- imagine sorting on one view needs to actually sorting on underlying `IVirtualListLoader`, so all other views get updated. We choose to implement `ICollectionView` and `ICollectionViewFactory` for `VirtualList`, to return itself as the view (thanks to the code of Vincent Van Den Berghe which can be downloaded at [Bea's blog post](http://web.archive.org/web/20180814144210/http://bea.stollnitz.com/blog/?p=344)):
```csharp
partial class VirtualList : ICollectionView, ICollectionViewFactory
{
...
#region ICollectionViewFactory Members
ICollectionView ICollectionViewFactory.CreateView()
{
return this;
}
#endregion
}
```
Please note current `ICollectionView` implementation does not support filtering, because `ICollectionView.Filter` is designed for client side filtering (it accepts a `Predicate` delegate and as far as I can tell, it's hard to be serialized and deserialized which is required in a scenario such as WCF). To filter, you can implement the logic on your `VirtualListLoader` implementation, and refresh the collection.
## Sorting GridView Column
Strictly speaking, this is independent of data virtualization -- it's a bonus from developing the demo application -- we want the GridView automatically sorted when the column is clicked, and a sort glyph in the column header to show which column is sorted. Instead of wiring the `Click` event of the `GridViewColumnHeader` class, we provide a generic solution through attached property so that no code behind is required (thanks for [Thomas Levesque's blog post](http://web.archive.org/web/20180814144210/http://tomlev2.wordpress.com/2009/08/04/wpf-automatically-sort-a-gridview-continued/)):
The `GridViewSort` class has `AutoSort` and `PropertyName` attached properties which you can use to setup on `ListView` and `GridViewColumn`:
```xaml
...
```
`GridViewSort` class handles column header clicking and sorts the `ListView`, then sets back the value for `GridViewSort.SortOrder` attached property of clicked `GridViewColumnHeader`, which you can use to show a sort glyph, or any other visual feedback:
```xaml
Value="M 0 4 L 4 0 L 8 4 Z" />
...
...
```
## Putting it Altogether
A simple demo project is created to demonstrate this solution. The full source code is available in the downloadable attachment.
Firstly, an implementation of `IVirtualListLoader` was created, which provides dummy `Person` data with a thread sleep used to simulate delays such as calling a WCF service via internet, and throws exception when `SimulateDataLoadingError` property is set to `true`:
```csharp
#region IVirtualListLoader Members
public bool CanSort
{
get { return true; }
}
public IList LoadRange(int startIndex, int count, SortDescriptionCollection sortDescriptions, out int overallCount)
{
int creationOverhead = Invoke(() => { return CreationOverhead; });
Thread.Sleep(creationOverhead);
bool simulateError = Invoke(() => { return SimulateDataLoadingError; });
if (simulateError)
throw new ApplicationException("An simulated data loading error occured. Clear the \"Simulate Data Loading Error\" checkbox and retry.");
overallCount = Invoke(() => { return ItemCount; });
// because the all fields are sorted ascending, the PropertyName is ignored in this sample
// only Direction is considered.
SortDescription sortDescription = sortDescriptions == null || sortDescriptions.Count == 0 ? new SortDescription() : sortDescriptions[0];
ListSortDirection direction = string.IsNullOrEmpty(sortDescription.PropertyName) ? ListSortDirection.Ascending : sortDescription.Direction;
count = Math.Min(count, overallCount - startIndex);
Person[] persons = new Person[count];
for (int i = 0; i < count; i++)
{
int index;
if (direction == ListSortDirection.Ascending)
index = startIndex + i;
else
index = overallCount - 1 - startIndex - i;
persons[i] = new Person(index);
}
return persons;
}
#endregion
```
A simple WPF window with a `ListView` was created to allow the user to experiment the features:
```xaml
<Setter Property="dz:VirtualListItemBase.AutoLoad" Value="true" />
Simulate Data Loading Error
Refresh
```
That's all! Hope you will enjoy using this component.
## Appendix: DataGrid
`VirtualList` can also be applied to `DataGrid`:
```xaml
```
However, [`DataGridRow`](https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.datagridrow?view=netframework-4.8) is not a `ContentControl`. `AutoLoad` needs to depend on `FrameworkElement.DataContextProperty` (or `DataGridRow.Item`) instead of `ContentControl.ContentProperty` to work.
## License
This component and the demo application, is under MIT license:
Copyright (c) 2010 DevZest ([http://www.devzest.com](http://web.archive.org/web/20180814144210/http://www.devzest.com/))
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## See Also
- [How to sort data virtualized items in WPF](https://github.com/bstollnitz/old-wpf-blog/tree/master/64-DataVirtualizationFilteringSorting)