ISupportIncrementalLoading : Loading a subsets of Data

Download the source  : https://aka.ms/q4mity

You could find in my last post https://aka.ms/islchg   how to populate an collection of data in background. But in this post I would to explain how to sequentialy load a subset of data, in order to allow a fast & fluid scrolling loading an huge set of data.

First the class has to implement the ISupportIncrementalLoading, IList and INofifyCollectionChanged. For a simplier sample, it’s possible to inherit from the ObservableCollection<T> class instead of the latter two interfaces

 

Code Snippet

  1. public class IncrementalLoadingCollection<T> : ObservableCollection<T>, ISupportIncrementalLoading

 

The CCTOR take a delegate as an argument, in order to retrieve a subset of data. This is what you will have to implement.

Code Snippet

  1. //delegate which populate the next items       
  2.         Func<CancellationToken, uint, Task<ObservableCollection<T>>> _func;

Code Snippet

  1. public IncrementalLoadingCollection(Func<CancellationToken, uint, Task<ObservableCollection<T>>> func, uint maxItems)
  2.         {
  3.             _func = func;
  4.             if (maxItems == 0) //Infinite
  5.             {
  6.                 _isInfinite = true;
  7.             }
  8.             else
  9.             {
  10.                 _maxItems = maxItems;
  11.                 _isInfinite = false;
  12.             }
  13.         }

 

ISupportIncrementalLoading

Two methods to implement

HasMoreItems, is invoked by the control (GridView or ListView), to know if more items has to be loaded.

 

Code Snippet

  1. public bool HasMoreItems
  2.         {
  3.             get
  4.             {
  5.                 if (_cts.IsCancellationRequested)
  6.                     return false;
  7.  
  8.                 if (_isInfinite)
  9.                 {
  10.                     return true;
  11.                 }
  12.                 return this.Count < _maxItems;
  13.             }
  14.         }

LoadMoreItemsAsync, is invoked with the variable count argument. This argument is calculated according to several factors :

First, the control need to know the number of objects to virtualize. So count is always equal 1 for the first call to the method.

Then, for an object with a size of 480*680, on a 15 inch screen, with a 1920*1080 resolution, XAML virtualize 24 objects (4 visible objects as you can see on the following picture) , count=24.

picture1_thumb[2]

By the way, using the DataFetchSize and IncrementalLoadingThreshold attributes, we are able to bypass this value for the same config.

With a DataFetchSize=1,   the count is equal 16

With DataFetchSize=5 count=40 and so on.

With  IncrementalLoadingThreshold=10, LoadMoreItemsAsync will be call 3 times with 120 virtualized objects (3*40)

It’s up to you to play with these attributes, in order to find the more accurate setting for your application.

 

With the code attach with this post you could play with sliders to see what’s happened when you use different values.

apbar_thumb[1]

 

Now go deeper in the  LoadMoreItemsAsync

The return parameter is an IAsyncOperation<LoadMoreItemsResult>. Interface. This interface, is create from the AsyncInfo.Run available in the System.Runtime.InteropServices.WindowsRuntime, with a lambda expression as a parameter. This lambda call the InternalLoadMoreItemsAsync()

The IntenalLoadMoreItemsAsync() goals are : 

  • Calculate the count of subset data (numberOfItemsTogenerate).
  • Invoke the CCTOR’s   _func delegate  argument (this is what you have to implement as we can see later), delegate wich populate an intermediate data list.
  • Agregate the intermediate list with the current list

 

Code Snippet

  1. public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
  2.         {                      
  3.             return AsyncInfo.Run((cts) => InternalLoadMoreItemsAsync(cts, count));                       
  4.         }     
  5.         async Task<LoadMoreItemsResult> InternalLoadMoreItemsAsync(CancellationToken cts, uint count)
  6.         {
  7.           
  8.             ObservableCollection<T> intermediate = null;         
  9.             _cts = cts;    
  10.                 var baseIndex = this.Count;
  11.                 uint numberOfitemsTogenerate = 0;
  12.  
  13.                 if (!_isInfinite)
  14.                 {
  15.                     if (baseIndex + count < _maxItems)
  16.                     {
  17.                         numberOfitemsTogenerate = count;
  18.  
  19.                     }
  20.                     else
  21.                     {
  22.                         //take the last items
  23.                         numberOfitemsTogenerate = _maxItems - (uint)(baseIndex);
  24.                     }
  25.  
  26.                 }
  27.                 else
  28.                 {
  29.                     numberOfitemsTogenerate = count;
  30.                 }                                
  31.                 intermediate = await _func(cts, numberOfitemsTogenerate);             
  32.                 if (intermediate.Count == 0) //no more items stop the incremental loading
  33.                 {
  34.                     _maxItems = (uint)this.Count;                   
  35.                     _isInfinite = false;
  36.                 }
  37.                 else
  38.                 {
  39.                     intermediate.AddTo<T>(this);
  40.                 }                  
  41.                 return new LoadMoreItemsResult { Count = (uint)intermediate.Count };                  
  42.         }            

Using IncrementalCollection<T>

In order to use this collection, you just has to create your own data model.

Example

Code Snippet

  1. public class DataModel : BindableBase
  2.     {
  3.         private Uri _uri;
  4.         public Uri UriPath
  5.         {
  6.             get { return _uri; }
  7.             set { this.SetProperty(ref _uri, value); }
  8.  
  9.         }            
  10.         private String _title;
  11.         public String Title
  12.         {
  13.             get { return _title; }
  14.             set { this.SetProperty(ref _title, value); }
  15.  
  16.         }
  17.  
  18.         //Use to show the download progress
  19.         private int _progress;
  20.         public int Progress
  21.         {
  22.             get { return _progress; }
  23.             set { this.SetProperty(ref _progress, value); }
  24.  
  25.         }
  26.         //Flag to know if the picture come from a remote source       
  27.         private Boolean _isRemote;
  28.         public Boolean IsRemote
  29.         {
  30.             get { return _isRemote; }
  31.             set { this.SetProperty(ref _isRemote, value); }
  32.  
  33.         }
  34.         private String _toolTip;
  35.         public String ToolTip
  36.         {
  37.             get { return _toolTip; }
  38.             set { this.SetProperty(ref _toolTip, value); }
  39.  
  40.         }              
  41.     }

 

Then implement an algorithm wich populate the data.

In this sample, I pushed 9561 pictures on Azure ( https://devosaure.blob.core.windows.net/images/) and build a really simple algorithm based on the picture name .

 

Code Snippet

  1. private void GetAzureBlobImagesAsync()
  2. {
  3.  
  4.     uint pageNumber = 1;
  5.     _data = new IncrementalLoadingCollection<DataModel>((CancellationToken cts, uint count) =>
  6.     {
  7.  
  8.         return Task.Run<ObservableCollection<DataModel>>(() =>
  9.         {
  10.             //***************************************
  11.             //Your code start here               
  12.             ObservableCollection<DataModel> intermediateList = new ObservableCollection<DataModel>();
  13.  
  14.             for (uint i = pageNumber; i < pageNumber + count; i++)
  15.             {
  16.                 String FileName = String.Format("UrzaGatherer ({0}).jpg", i.ToString());
  17.                 String RemotePath = String.Format(@"https://devosaure.blob.core.windows.net/images/{0}", FileName);
  18.                 DataModel item = new DataModel();
  19.                 item.IsRemote = true;
  20.                 item.Title = FileName;
  21.                 item.UriPath = new Uri(RemotePath);
  22.                 intermediateList.Add(item);
  23.             }
  24.             pageNumber += count;
  25.             return intermediateList;
  26.             //*************and finish here**************************     
  27.         });
  28.  
  29.     }, 9651);
  30.  
  31.     _data.CollectionChanged += data_CollectionChanged;
  32.     itemsGridViewIncremental.DataContext = _data;         
  33. }

 

You could find in the source code (https://aka.ms/q4mity) :

  • A Bing sample  (You have to provide your own Bing APPID https://ssl.bing.com/webmaster/developers/createapp.aspx)
  • A File sample wich load local pictures (pictures you have to copy in the C:\Users\ [Your User Name] \AppData\Local\Packages\ericvIncremental_nwdczr7kjtdqt\LocalState

 

To play with this sample, click right to show the AppBar, and select the button you want.

You will notice it’s possible to disable the virtualisation, with the IncrementalLoadingTrigger = IncrementalLoadingTrigger.None. Inthis case the GridView doesn’t call the LoadMoreItemsAsync() method but it’s possible to invoke manually the LoadMoreItems depending on what you want for you application.

 

Eric Vernié