Virtual Paging in Silverlight

Silverlight 3 added the IPagedCollectionView interface (and concrete implementation PagedCollectionView) to support paging through a large result set. The DataPager control can work with an implementation of IPagedCollectionView to allow users to page through the large data set by clicking on a page number. It typically looks like this:

Data Pager

Unfortunately (you knew it was coming), these new types don’t offer support for virtual paging. Virtual paging is where you’re paging data behind the scenes, but the user isn’t explicitly changing pages as they do with the DataPager control. To them, the data just appears in a big ol’ list. As they scroll around the list, data is paged in as necessary. If they move slowly down the list, they would quite possibly never know that data is being paged.

In the WPF world, it is possible to achieve virtual paging by implementing a collection view that simply retrieves data lazily if and when it is requested. This works because WPF’s binding infrastructure will only request items in your source collection if it needs them. Alas, this is not the case in Silverlight. Silverlight will aggressively iterate over all items in your underlying collection regardless of whether they’re visible. Thus, if you attempt the WPF trick of lazily loading data on request, you’ll end up paging in all the data as soon as the binding kicks in. How annoying.

I tried a few things to make sure that Silverlight really was that dumb. For example, I tried providing an IList<T> implementation to see whether it would use the Count property to avoid the iteration over all the data, figuring perhaps it just needed to know how many items were in the data source in order to calculate the scrollable extent. No dice. I tried a few other things, but all attempts at thwarting Silverlight’s aggressive iteration over the data source came up empty-handed.

But the thing is, I feel really strongly about the usability benefits of virtual paging versus manual paging. The former is far more intuitive and simple to use. I was simply not prepared to forgo this feature in my app, so I pressed on.

I came up with something of a compromise that goes like this: fine then, Miss Silverlight, if you insist then you may iterate over my entire source, but I’ll just be giving you handles to the data. Those handles won’t result in data being loaded - that will only happen when the handle’s value is de-referenced. The value will only be de-referenced by bound UI components. And by virtue of UI virtualization, that will only happen when the user scrolls to within the region of that data.

So what do the handles look like? Initially I experimented with using Lazy<T> since it’s built-in and seemed apropos. It actually worked fine when used synchronously. But loading the data on the UI thread would be really bad, effectively causing the UI to hang when the user scrolls through the data. When I attempted to extend Lazy<T> and add asynchronous support, things got ugly. Therefore, I scrapped it altogether and instead wrote my own AsyncLazy<T> class. On the surface, it looks a lot like Lazy<T>:

Class Diagram

But is has some key differences in both behavior and API. Firstly, accessing Value when the value is yet to be loaded will instantly return null. But it will also start resolving the value on a background thread, after which the Value property is updated with the result. The IsValueCreated property will not return true until the background thread has done its thing and Value is set. Crucially, AsyncLazy<T> implements INotifyPropertyChanged so that any UI elements bound to Value or IsValueCreated will refresh accordingly as values load.

I then have a collection view called LazyAsyncCollectionView<T>. This is an abstraction over the large underlying data set that will be consumed by your view components. You tell it the total number of items in the collection, the size of each page, and give it a lambda with which it can load a specific page. It then takes care of ensuring that pages are loaded as and when they are required. Importantly, it’s actually an IEnumerable<AsyncLazy<T>> rather than IEnumerable<T>. This means that Silverlight can iterate over the entire view without us needing to load all the data. The data will only be loaded if the Value property is de-referenced, which is instigated by bindings in the view.

The view needs to be aware of the fact that it’s bound to an AsyncLazy<T> rather than a T. Ergo, bindings look like this:

<TextBlock Text="{Binding Value.Property}"/>

where Value is the aforementioned property on AsyncLazy<T>. If you have a lot of properties and you don’t like dereferencing Value all the time, you could bind a container’s DataContext like this:

<StackPanel DataContext="{Binding Value}">
    <TextBlock Text="{Binding SomeProperty}"/>
    <TextBlock Text="{Binding SomeOtherProperty}"/>
</StackPanel>

The demo I put together uses all this stuff to display a list of people, the specifics of which you control (being a demo). As pages are being loaded, it displays a simple animation as a placeholder for the data:

Sample Screenshot

I mentioned that this whole thing was a compromise. Here are the primary weaknesses of the approach:

It’s also incomplete. I’m literally blogging about this before I use it in anger, mostly because I’m just so excited I actually got it to work. However, my scenario may be simpler than yours. Here are some things that are missing/incomplete:

Download the sample project.

Enjoy!

comments powered by Disqus