Data and UI Virtualization in WPF
The topic of virtualization is somewhat complex but at the same time pretty well covered on MSDN and blogs. I am quite sure I won't be able to contribute anything new to the general pool of knowledge on this topic. Instead I would like to summarize some of the key points I've recently learnt while trying to implement different levels of virtualization for one of our WPF components and list some resources which I found helpful.
First of all, some clarification on the terminology - Data virtualization vs. UI virtualization. How are they different?
Generally when we say data virtualization, we mean that the data source itself is not loaded fully, but instead we only load the data items required to display to the user. With data virtualization, data is often subdivided into pages. When a particular item is requested, the appropriate page is loaded. Often there's caching mechanism involved (pages of data are cached to avoid going back to the data source).
UI virtualization, on the other hand does not imply anything about data source being partially or fully loaded. UI virtualization is an optimization to avoid constructing and rendering the container objects for items that are not visible. The container objects are those ListBoxItem's or ListViewItem's or whatever is your custom implementation for them. Practically it means that while the data source itself may be fully loaded, not all containers are created, but only the ones that need to be rendered. As the user scrolls the list the containers are created dynamically.
There's no support for data virtualization out-of-the-box. However based on my findings, most custom data virtualization implementations utilize a single very useful property of WPF collection view - that is, when the data source implements IList interface, the items will be requested using IList.Item indexer property and the collection will never be iterated over. Which means that as a long as collection reports accurate total count of items and Item[index] returns correct item, the collection doesn't need to load all the items at once. With this in mind, can one create a custom collection that virtualizes the underlying data source by loading the pages of items on demand.
public class MyVirtualizedCollection<T> : IList<T>, IList, INotifyCollectionChanged
public T this[int index]
// 1) validate index argument
// 2) determine the page that the item belongs to
// 3) if the page is already loaded retrieve the item from the page
// 4) if the page is not loaded you can
// a: load the page now and return the item
// b: return null or empty item, then schedule loading of this page on a background thread or idle UI loop
// and update the item when it becomes available. A CollectionChanged can be fired after that
public int Count
//return actual count
These are some of the working examples that implement data virtualization based on this technique
To turn on UI virtualization, set the following property on your WPF ItemsControl: VirtualizingStackPanel.IsVirtualizing = "True". Note, that it assumes that your items panel is a VirtualizingStackPanel, which is the default option, but obviously it won't have any effect if you, say, specified DockPanel as ItemsPanel.
Additionally, you can specify VirtualizingStackPanel.VirtualizingMode = "Recycling". This option ensures that containers (those ListViewItem or ListBoxItem objects) are reused when their bound data items are displayed again.
While overall I found out-of-the-box UI virtualization effective and would recommend it as a default setting when displaying a dataset with even a couple of hundred of items, there are two subtle (or not so subtle depending on your requirements) limitations:
- Virtualization is not supported when grouping is on. If you decide to use grouping on your ItemsControl, virtualization turns of automatically. A workaround is implementing a custom CollectionView which is sufficiently complex to reconsider using virtualization or grouping. However there's an example of how it can be done: http://code.msdn.microsoft.com/getwpfcode/Release/ProjectReleases.aspx?ReleaseId=1142
- Logical scrolling and item height. With UI virtualization turned, ItemsControl switches into a different scrolling mode, called 'logical scrolling'. The difference is that with regular, physical (or pixel-based) scrolling the item is shifted up or down (assuming vertical orientation) proportionally to the combined height of all items, so when one item is larger, than the other it takes longer to scroll through it. However with UI virtualization on, the panel doesn't know the total height of items, so it uses logical scrolling, which means it always scrolls one item at a time regardless of its height. Even worse, the scrollbar thumb will jump and change size depending on whether we are scrolling over large or small item.
For more information on UI virtualization I recommend the following blog post: http://bea.stollnitz.com/blog/?p=338