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
- 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
- //delegate which populate the next items
- Func<CancellationToken, uint, Task<ObservableCollection<T>>> _func;
Code Snippet
- public IncrementalLoadingCollection(Func<CancellationToken, uint, Task<ObservableCollection<T>>> func, uint maxItems)
- {
- _func = func;
- if (maxItems == 0) //Infinite
- {
- _isInfinite = true;
- }
- else
- {
- _maxItems = maxItems;
- _isInfinite = false;
- }
- }
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
- public bool HasMoreItems
- {
- get
- {
- if (_cts.IsCancellationRequested)
- return false;
- if (_isInfinite)
- {
- return true;
- }
- return this.Count < _maxItems;
- }
- }
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.
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.
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
- public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
- {
- return AsyncInfo.Run((cts) => InternalLoadMoreItemsAsync(cts, count));
- }
- async Task<LoadMoreItemsResult> InternalLoadMoreItemsAsync(CancellationToken cts, uint count)
- {
- ObservableCollection<T> intermediate = null;
- _cts = cts;
- var baseIndex = this.Count;
- uint numberOfitemsTogenerate = 0;
- if (!_isInfinite)
- {
- if (baseIndex + count < _maxItems)
- {
- numberOfitemsTogenerate = count;
- }
- else
- {
- //take the last items
- numberOfitemsTogenerate = _maxItems - (uint)(baseIndex);
- }
- }
- else
- {
- numberOfitemsTogenerate = count;
- }
- intermediate = await _func(cts, numberOfitemsTogenerate);
- if (intermediate.Count == 0) //no more items stop the incremental loading
- {
- _maxItems = (uint)this.Count;
- _isInfinite = false;
- }
- else
- {
- intermediate.AddTo<T>(this);
- }
- return new LoadMoreItemsResult { Count = (uint)intermediate.Count };
- }
Using IncrementalCollection<T>
In order to use this collection, you just has to create your own data model.
Example
Code Snippet
- public class DataModel : BindableBase
- {
- private Uri _uri;
- public Uri UriPath
- {
- get { return _uri; }
- set { this.SetProperty(ref _uri, value); }
- }
- private String _title;
- public String Title
- {
- get { return _title; }
- set { this.SetProperty(ref _title, value); }
- }
- //Use to show the download progress
- private int _progress;
- public int Progress
- {
- get { return _progress; }
- set { this.SetProperty(ref _progress, value); }
- }
- //Flag to know if the picture come from a remote source
- private Boolean _isRemote;
- public Boolean IsRemote
- {
- get { return _isRemote; }
- set { this.SetProperty(ref _isRemote, value); }
- }
- private String _toolTip;
- public String ToolTip
- {
- get { return _toolTip; }
- set { this.SetProperty(ref _toolTip, value); }
- }
- }
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
- private void GetAzureBlobImagesAsync()
- {
- uint pageNumber = 1;
- _data = new IncrementalLoadingCollection<DataModel>((CancellationToken cts, uint count) =>
- {
- return Task.Run<ObservableCollection<DataModel>>(() =>
- {
- //***************************************
- //Your code start here
- ObservableCollection<DataModel> intermediateList = new ObservableCollection<DataModel>();
- for (uint i = pageNumber; i < pageNumber + count; i++)
- {
- String FileName = String.Format("UrzaGatherer ({0}).jpg", i.ToString());
- String RemotePath = String.Format(@"https://devosaure.blob.core.windows.net/images/{0}", FileName);
- DataModel item = new DataModel();
- item.IsRemote = true;
- item.Title = FileName;
- item.UriPath = new Uri(RemotePath);
- intermediateList.Add(item);
- }
- pageNumber += count;
- return intermediateList;
- //*************and finish here**************************
- });
- }, 9651);
- _data.CollectionChanged += data_CollectionChanged;
- itemsGridViewIncremental.DataContext = _data;
- }
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é
Comments
- Anonymous
September 15, 2015
Love the name. I support incremental loading too :)