WrapPanel and ISupportIncrementalLoading

Feb 6, 2015 at 4:22 PM
Hi Everyone,

Is it possible to implement ISupportIncrementalLoading and bind it to the WarpPanel? I can bind my ISupportIncrementalLoading collection to a ListView without problem but as soon as I change the ListView.ItemsPanel to the toolkits WrapPanel the functionally of ISupportIncrementalLoading stops working.

Thanks in advance!
Coordinator
Feb 6, 2015 at 5:22 PM
I believe ISupportIncrementalLoading is meant to be used with virtualizing panels and WrapPanel isn't virtualizing and can't be virtualizing.

If you're thinking of loading more items when you scroll down - you could still handle ScrollViewer.ViewChanged events and add more items to your source when you detect that the user has scrolled near the end of the list, but note that with a large number of items your app will significantly slow down quite soon and eventually you'll run out of memory with all the UIElements in memory and the WrapPanel calculating their layout.

A better choice, though requiring quite a bit more work would be fore you to calculate how much horizontal space each item uses, how much is available and put your items in a virtualized list of lists bounds to a ListView where each row is a list of items of its own - possibly built with an ItemsControl.

The best solution would be to just handle all the virtualization yourself.

A realistic solution for a small app is a GridView with the default ItemsWrapGrid panel.
Coordinator
Feb 6, 2015 at 5:24 PM
Oh, one more option - use a WrapPanel to show a few items that fit on screen and have a button to view more items either in a ListView or a GridView.
Feb 6, 2015 at 5:33 PM
I think I'll try the ScrollViewer event(s) as you've suggested - it seems to best coincide with what I'm tryin to do (two column list of images that scrolls down vertically with about 1000ish items)

You mentioned 'The best solution would be to just handle all the virtualization yourself. ' where would I start learning to do such a thing?
Feb 6, 2015 at 5:42 PM
Actually I was able to do what I was thinking with a ListView (which does virtualize automatically - thanks for the idea!) and a WrapGrid. I guess the solution no longer applies to this toolkit but it works none the less.
     <ListView 
          IncrementalLoadingTrigger="Edge"
          ScrollViewer.VerticalScrollBarVisibility="Hidden"
          ScrollViewer.VerticalScrollMode="Enabled">
          <ListView.ItemsPanel>
               <ItemsPanelTemplate>
                    <WrapGrid Orientation="Horizontal" Width="300" />
               </ItemsPanelTemplate>
          </ListView.ItemsPanel>

          <ListView.ItemTemplate>
               <DataTemplate>
                    <controls:Thing/>
               </DataTemplate>
          </ListView.ItemTemplate>
     </ListView>
Also if anyone wants the code for my 'IncrementalObservableCollection', have at it.
public class IncrementalObservableCollection<T> : ObservableCollection<T>, ISupportIncrementalLoading
{
    private IEnumerable<T> itemsSource;

    private int itemsPerPage;

    private bool hasMoreItems;

    private int currentPage;
        
    /// <summary>
    /// 
    /// </summary>
    public bool HasMoreItems
    {
        get
        {
            return hasMoreItems;
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="itemsPerPage"></param>
    public IncrementalObservableCollection(IEnumerable<T> items, int itemsPerPage = 20)
    {
        this.itemsSource = items;
        this.itemsPerPage = itemsPerPage;
        this.hasMoreItems = true;
    }
        
    /// <summary>
    /// 
    /// </summary>
    /// <param name="count"></param>
    /// <returns></returns>
    public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
    {
        var dispatcher = Window.Current.Dispatcher;

        return Task.Run<LoadMoreItemsResult>(
            async () =>
            {
                uint resultCount = 0;

                var r = await Task.Run<IEnumerable<T>>(() =>
                {
                    var result = (from i in itemsSource
                                    select i).Skip(currentPage++ * itemsPerPage).Take(itemsPerPage);

                    return result;
                });

                if (r == null || r.Count() == 0)
                {
                    hasMoreItems = false;
                }
                else
                {
                    resultCount = (uint)r.Count();

                    await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                        {
                            foreach (T item in r)
                                this.Add(item);
                        }
                    );
                }

                return new LoadMoreItemsResult() { Count = resultCount };

            }).AsAsyncOperation<LoadMoreItemsResult>();
    }
}
Coordinator
Feb 6, 2015 at 5:46 PM
1000 items is way too many to load at the same time.

I'd search the web on various things about UI virtualization.

Essentially you have a list of items you need to show and a view port that shows only a portion of these items.
  1. Put a Canvas in a vertically scrollable ScrollViewer
  2. Calculate the positioning of all the items up to at least the view port position
  3. Materialize the containers to display the items in the view port and close to the view port (let's say 1-2 view port heights up and down)
  4. Handle ViewChanged event
    • don't move the containers that are still in or close to the view port
    • remove containers that are too far away from the view port - put them in a cache
    • add containers for items that are now in or near the view port - use containers from the cache first if available, otherwise create new ones
  5. Handle keyboard input
  6. Calculate layout of items in a background thread
  7. Handle view port size changes - you might need to recalculate the layout of the entire collection or otherwise simply scale everything