Data Virtualisation in Silverlight and WP7

Silverlight has some great UI virtualisation features. You can have thousands of items in an ItemsControl, and so long as you are using a VirtualizingStackPanel the UI will remain snappy and responsive. However, what if the thousands of items that you want to display in the list come from a database or web service? You certainly don’t want to request them all at once, and UI virtualisation won’t help you here – what you need is data virtualisation, and that is something that Silverlight doesn’t provide. In this post I’ll be explaining how this can be achieved (or you can just skip to the end and download my implementation).

How to do it

In WPF, data virtualisation is relatively easy (or at least well known). An excellent summary of the topic can be found on the fantastic Bea Stollnitz’s blog here. In short, you create a custom IList<T> implementation, and whenever the indexer for the collection is accessed, you asynchronously load those items from your data source, usually in chunks. The collection raises change notifications, the UI is updated, and everyone is happy. In Silverlight it is a little bit trickier, as your indexer will be called for every item in your collection as soon as you bind it to an ItemsControl, causing all the chunks to be fetched at once – right back to square one. It’s not possible to get around this restriction completely, but we can minimise its impact with a bit of trickery.

Because Silverlight always accesses the entire collection as soon as you bind to it, we can’t use the indexer as the trigger to retrieve data from the back end. So, what else can we use? UI virtualisation does pretty much what we want – it only creates the UI for the data we want to show. The trick is to use this to request data only when it is shown in the UI. Once we’ve realised this, it’s actually quite simple.

What we do is create a wrapper class that looks something like this (implementation removed for brevity):

public class VirtualItem<T> : INotifyPropertyChanged where T : class
{
    ...

    public T Item
    {
        get;
    }

    public bool IsLoading
    {
        get;
    }

    ....
}

We wrap every item (including items that we haven’t actually retrieved yet – to start off with they will be wrapping nothing, and the Item property will return null or some other default value) in the collection with one of these objects, so that we are now implementing IList<VirtualItem<T>> instead of IList<T>. Silverlight will still access the indexer for every index, forcing creation of a VirtualItem<T> for every index, but the data source is not accessed until a UI element accesses the Item property, at which point we tell the list to retrieve a chunk of items containing that item. When items are retrieved, the Item property for the relevant VirtualItem<T> objects changes to the newly retrieved item and the UI detects the property change and updates. We get several benefits from this:

  1. Firstly and most importantly, we now know when the UI needs to display a particular item, because the item must be accessed via the Item property; the getter for the Item property triggers the asynchronous request for items if they have not already been retrieved. This circumvents the problem with Silverlight accessing the indexer for every index.
  2. In an implementation without this wrapper, the collection must raise collection reset change notifications whenever a new chunk of items is retrieved, using INotifyCollectionChanged. This causes any UI bound to the collection to reset as well, losing the selected item(s). With the wrapper in place new chunks now result in simple property change notifications via INotifyPropertyChanged (most significantly the Item property), meaning that the UI can handle these changes more efficiently and with no loss of selection.
  3. The wrapper object itself can provide properties related to virtualisation that the UI can bind to – for example the UI can bind to the IsLoading property to show which items are still loading.

This is still not true data virtualisation, as Silverlight does still access the indexer for every item, forcing the creation of all the wrapper objects – if you have a massive collection, you may still see performance and memory issues when creating the collection – but it is an effective means of requesting data on demand from a web service or some other potentially slow data source.

Sample implementation

At the end of this article is a link to an asynchronous virtual list implementation that uses the method described above. To use it, you need to provide it with a data source, which we do by creating a class that implements IAsyncItemProvider<T> and passing it to the constructor of the AsyncVirtualList<T>. IAsyncItemProvider<T> is designed according to the event-based asynchronous pattern. You can then bind the list to any ItemsControl using a VirtualizingStackPanel.

The below example shows how you might use an AsyncVirtualList<T>. First, the IAsyncItemProvider<T> implementation, which just generates a string based on the index of the item (with an artificial delay to emphasise the asynchronous behaviour, and no cancellation support):

private class DummyItemProvider : IAsyncItemProvider<string>
{
    public event EventHandler<ProvideItemCountEventArgs> ProvideItemCountCompleted;
    public event EventHandler<ProvideItemsEventArgs<string>> ProvideItemsCompleted;

    public int PreferredPageSize
    {
        get
        {
            return 10;
        }
    }

    public string CreatePlaceholderValue()
    {
        return null;
    }

    public void ProvideItemCountAsync(object state)
    {
        ThreadPool.QueueUserWorkItem(
            delegate
            {
                Thread.Sleep(1000);
                OnProvideItemCountCompleted(new ProvideItemCountEventArgs(null, false, state, 3000));
            });
    }

    public void ProvideItemsAsync(int offset, int count, object state)
    {
        ThreadPool.QueueUserWorkItem(
            delegate
            {
                Thread.Sleep(500);
                var items = new string[count];
                for (int i = 0; i < count; i++)
                {
                    items[i] = string.Format("Item {0}", i + offset);
                }

                OnProvideItemsCompleted(new ProvideItemsEventArgs<string>(null, false, state, items, offset));
            });
    }

    private void OnProvideItemCountCompleted(ProvideItemCountEventArgs args)
    {
        var handler = ProvideItemCountCompleted;
        if (handler != null)
        {
            handler(this, args);
        }
    }

    private void OnProvideItemsCompleted(ProvideItemsEventArgs<string> args)
    {
        var handler = ProvideItemsCompleted;
        if (handler != null)
        {
            handler(this, args);
        }
    }

    ...
}

Now creating an AsyncVirtualList<T> using the item provider (this shows setting the DataContext of a UserControl named MainPage in its codebehind – but you’d do it using a ViewModel class, right?):

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();

        DataContext = new AsyncVirtualList<string>(new DummyItemProvider(), Dispatcher);
    }
}

And finally, binding a ListBox to the virtual list in XAML. This shows a list whose items show the dummy string that the item provider generates, and are highlighted in blue until they are finished loading:

<ListBox ItemsSource="{Binding}">
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        </Style>
    </ListBox.ItemContainerStyle>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid>
                <TextBlock Text="{Binding Item}" />
                <Grid
                    Opacity="0.1"
                    Background="Blue"
                    Visibility="{Binding IsLoading, Converter={StaticResource visibilityConverter}}" />
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

The sample project shows the above example in full. The project is a Silverlight 4 web project, but the AsyncVirtualList<T> works just as well in WP7.

A couple of final notes about the asynchronous list implementation:

  • IAsyncItemProvider<T> can be implemented synchronously if desired – just raise the ProvideItemCountCompleted or ProvideItemsCompleted event directly from within the ProvideItemCountAsync or ProvideItemsAsync method.
  • Items are never released once they have been retrieved. This is something that could be added – let me know if you do!

You can download the sample project here.

This entry was posted in Development and tagged , , . Bookmark the permalink.

7 Responses to Data Virtualisation in Silverlight and WP7

  1. Andrew says:

    Awesome post – really easy to read and makes a lot of sense!

  2. Mattias says:

    Excellent post. I’ve been looking for a solution to this for quite some time.

    Many thanks.

  3. JasonBSteele says:

    With RTM of WP7 I believe we now have UI Virtualization. The ListBox does it by default and an ItemsControl will do it if you set its ItemsPanel to a VirtualizingStackPanel.

    You can then get data virtualization by implementing IList and providing Count and Item.

    I can see it working by putting a Debug.WriteLine in my Item get.

    Hope this helps someone,
    Jason

    • G Forty says:

      Hi Jason, have you got an example of the VirtualizingStackpanel?

      • Ezra says:

        Yeah,the listbox has got the Virtualization default.However,if you use the wrappanel in the itempaneltemplate,the default virtuallization will disappear.The Delay’s Blog give me a good solution.But that hasn’t solve when the image load into the wp7 screen,after scrolling below the last screen image should destory automaticly.Do you have some good solution about it?

  4. Fallon Massey says:

    Your code works fine when it’s a ListBox, but when I replace it with a ItemsControl(with virtualizing Stackpanel), it requests all of the data immediately.

    Do you know why it doesn’t work?

  5. .URjdkpYSPvZ says:

    I’m really inspired along with your writing skills and also with the layout for your weblog. Is this a paid subject or did you modify it yourself? Anyway keep up the nice quality writing, it is rare to look a nice blog like this one these days..

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>