0
votes

I have a scenario in which I want to present a lot of items within an ItemsControl. Due to the way the items are layed out (using a canvas), I can't use the standard virtualizing panels, and so it takes a long time for the control to load.

Rather than incurring the load time of all the items in one go, I was wondering how I could load the items in batches?

So for example, if I was using a ListBox, with it's itemsSource set to some large list, how could I create the ListBoxItems in batches of say 10 items, deferring the remaining to run on the next Dispatcher event (Background or AppIdle)?

This problem could be solved quite easily from the ViewModel by having the ItemsSource be an Observable collection that grows with each new batch, but I'm wanting to add this at the View level.

I also don't want it implemented using the ItemContainers Visibility property, as that will most likely already be being used.

1

1 Answers

0
votes

That's all going to depend on where the data comes from. But I can tell you loading from the view is just a bad plan.

  1. Set your ItemsSource to an ObservableCollection in your ViewModel.
  2. Update the ObservableCollection from a background thread so it doesn't impact your UI.
  3. Make sure when updating that you do not reinstantiate the instance, but rather clear and repopulate your collection.

I believe WPF now allows cross-thread updates to an ObservableCollection. If not, you can always use a library like Caliburn for it's BindableCollection.

You can also just create your own like this article.

The code:

/// <summary>
/// Initializes a new instance of the 
/// <see cref="ObservableCollectionEx{T}"/> class.
/// </summary> 
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
    #region Constructors

    /// <summary>
    /// Initializes a new instance of the
    /// <see cref="ObservableCollectionEx{T}" /> class.
    /// </summary>
    public ObservableCollectionEx()
    {
    }

    ///
    /// Initializes a new instance of the
    ///  class.
    ///
    ///The collection.
    public ObservableCollectionEx(IEnumerable<T> collection) : this()
    {
        this.AddRange(collection);
    }

    #endregion

    #region Events

    /// <summary>
    /// Source: New Things I Learned
    /// Title: Have worker thread update ObservableCollection that is bound to a ListCollectionView
    /// http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/have-worker-thread-update-observablecollection-that-is-bound-to-a.aspx
    /// Note: Improved for clarity and the following of proper coding standards.
    /// </summary>
    /// <param name="e"></param>
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        // Use BlockReentrancy
        using (BlockReentrancy())
        {
            var eventHandler = CollectionChanged;
            if (eventHandler == null) return;

            // Only proceed if handler exists.
            Delegate[] delegates = eventHandler.GetInvocationList();

            // Walk through invocation list.
            foreach (var @delegate in delegates)
            {
                var handler = (NotifyCollectionChangedEventHandler)@delegate;
                var currentDispatcher = handler.Target as DispatcherObject;

                // If the subscriber is a DispatcherObject and different thread.
                if ((currentDispatcher != null) &amp;&amp; (!currentDispatcher.CheckAccess()))
                {
                    // Invoke handler in the target dispatcher's thread.
                    currentDispatcher.Dispatcher.Invoke(
                        DispatcherPriority.DataBind, handler, this, e);
                }

                else
                {
                    // Execute as-is
                    handler(this, e);
                }
            }
        }
    }

    /// <summary>
    /// Overridden NotifyCollectionChangedEventHandler event.
    /// </summary>
    public override event NotifyCollectionChangedEventHandler CollectionChanged;

    #endregion
}