Implementing a virtualized panel in WPF (Avalon)
Displaying large sets of data can be challenging to do performantly. If you have a scrolling list of data, one technique to improve performance is to only create the UI elements that are visible. This is refered to as UI virtualization (as opposed to data virtualization, which is the technique of not materializing the data that isn’t visible). WPF has a built in virtualizing panel called VirtualizingStackPanel that supports UI virtualization and lays out its children like StackPanel. ListBox uses this panel by default. However, if you want to lay out your children differently, you need to write your own Panel that supports virtualization. WPF has a VirtualizingPanel class you can descend from to do this, but it’s still a fair amount of work. I’ll describe how to write your own virtualizing panel in this series of posts.
The basic approach is as follows:
· In your Panel’s MeasureCore, you need to figure out which range of data items are visible. Then, you generate the UI for any of the visible data items that haven’t previously been generated. This is referred to as realizing the UI for these items. Finally, you destroy the UI for any data items that are no longer visible. This is referred to as revirtualizing the item. The revirtualization can actually be deferred until idle time for better performance.
· In your Panel’s ArrangeCore, you simply arrange the UI elements that are visible to the appropriate locations.
Sounds simple, right? Well, there are a couple of complications.
The first complication is that your MeasureCore needs to know how much is visible. If you just subclass VirtualizingPanel and put your panel in an ItemsControl, you’ll find that you get measured with infinite constraints and you can’t really tell what is visible. While it might be possible to jump through some hoops to figure out what’s visible, the right way to do this is to implement IScrollInfo. Ben Constable has an excellent series of posts about IScrollInfo, so I won’t be writing much about that implementation here. Once you’ve implemented IScrollInfo, your MeasureCore will be called with the visible region as the constraints.
The second complication is that you need to generate the UI elements in the proper way that takes WPF’s data model into account (finding the proper DataTemplate, etc). Beatriz Costa has an excellent blog on dealing with data in Avalon/WPF. VirtualizingPanel exposes an object that implements IItemContainerGenerator which lets you properly generate the UI for elements.
And, the last complication is that you need to do the book keeping to keep track of which UI elements correspond to which data items. The IItemContainerGenerator helps you do this book keeping if you keep the child UI elements in the same order as the data items (which is a natural thing to do). VirtualizingPanel exposes methods to insert/add/remove the child UI elements.
In part 2, I’ll dig into the IItemContainerGenerator and explain how it is used to generate the child UI elements and how it can be used to map data items to UI elements.