Reputation: 11
I'm using ListView to develop in XAML-C# a Windows Store App.
I want to load more items on ListView every time the user arrived at the end of the list.
I just read this discussion Load more items on grid view scroll end and I just read this example http://code.msdn.microsoft.com/windowsapps/Data-Binding-7b1d67b5
I tried to implement something like mentioned example: - Some of mycode
uint nentries = 0;
// Follow method return a int: number of items (entries) between 0 and 20
nentries = await App.EntriesViewModel.LoadEntries(0);
if (nentries != 0)
{
//entries is a GeneratorIncrementalLoadingClass<Entry>
App.EntriesViewModel.entries = new GeneratorIncrementalLoadingClass<Entry>(nentries, count =>
{
//EntriesOc is an observable collection with INotifyPropertyChanged
return App.EntriesViewModel.EntriesOc.ElementAt(count);
});
//entriesCVS is a CollectionViewSource defined into xaml code
entriesCVS.Source = App.EntriesViewModel.entries;
}
}
this.DataContext = null;
this.DataContext = App.EntriesViewModel;
//until here it's works
if (nentries == 20)
{
uint n = 0;
while (nentries % 20 == 0)
{
n = await App.EntriesViewModel.LoadEntries(nentries);
if (n == 0) break; // no more data to load
nentries += n;
App.EntriesViewModel.entries = new GeneratorIncrementalLoadingClass<Entry>(nentries, (count) =>
{
return App.EntriesViewModel.EntriesOc.ElementAt(count);
});
// without the follow line of code the CollectionViewSource doesn't update
// however the list scroll to the top (I want to remove this behaviour)
entriesCVS.Source = App.EntriesViewModel.entries;
}
}
IncrementalLoadingBase.cs (same file of example)
namespace MySolution {
public abstract class IncrementalLoadingBase: IList, ISupportIncrementalLoading, INotifyCollectionChanged { public int Add(object value) { throw new NotImplementedException(); } public void Clear() { throw new NotImplementedException(); } public bool Contains(object value) { return _storage.Contains(value); } public int IndexOf(object value) { return _storage.IndexOf(value); } public void Insert(int index, object value) { throw new NotImplementedException(); } public bool IsFixedSize { get { return false; } } public bool IsReadOnly { get { return true; } } public void Remove(object value) { throw new NotImplementedException(); } public void RemoveAt(int index) { throw new NotImplementedException(); } public object this[int index] { get { return _storage[index]; } set { throw new NotImplementedException(); } } public void CopyTo(Array array, int index) { ((IList)_storage).CopyTo(array, index); } public int Count { get { return _storage.Count; } } public bool IsSynchronized { get { return false; } } public object SyncRoot { get { throw new NotImplementedException(); } } public IEnumerator GetEnumerator() { return _storage.GetEnumerator(); } public bool HasMoreItems { get { return HasMoreItemsOverride(); } } public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) { if (_busy) { throw new InvalidOperationException("Only one operation in flight at a time"); } _busy = true; return AsyncInfo.Run((c) => LoadMoreItemsAsync(c, count)); } public event NotifyCollectionChangedEventHandler CollectionChanged; async Task<LoadMoreItemsResult> LoadMoreItemsAsync(CancellationToken c, uint count) { try { var items = await LoadMoreItemsOverrideAsync(c, count); var baseIndex = _storage.Count; _storage.AddRange(items); // Now notify of the new items NotifyOfInsertedItems(baseIndex, items.Count); return new LoadMoreItemsResult { Count = (uint)items.Count }; } finally { _busy = false; } } void NotifyOfInsertedItems(int baseIndex, int count) { if (CollectionChanged == null) { return; } for (int i = 0; i < count; i++) { var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _storage[i + baseIndex], i + baseIndex); CollectionChanged(this, args); } } protected abstract Task<IList<object>>LoadMoreItemsOverrideAsync(CancellationToken c, uint count); protected abstract bool HasMoreItemsOverride(); List<object> _storage = new List<object>(); bool _busy = false; }
}
GeneratorIncrementalLoadingClass.cs (same file of example)
namespace MySolution{
public class GeneratorIncrementalLoadingClass: IncrementalLoadingBase
{
public GeneratorIncrementalLoadingClass(uint maxCount, Func<int, T> generator)
{
_generator = generator;
_maxCount = maxCount;
}
protected async override Task<IList<object>> LoadMoreItemsOverrideAsync(System.Threading.CancellationToken c, uint count)
{
uint toGenerate = System.Math.Min(count, _maxCount - _count);
// Wait for work
await Task.Delay(10);
// This code simply generates
var values = from j in Enumerable.Range((int)_count, (int)toGenerate)
select (object)_generator(j);
_count += toGenerate;
return values.ToArray();
}
protected override bool HasMoreItemsOverride()
{
return _count < _maxCount;
}
Func<int, T> _generator;
uint _count = 0;
uint _maxCount;
}
}
Upvotes: 1
Views: 2727
Reputation: 5575
What LoadMoreItemsOverrideAsync
is intended to do is to fill the current list with more items. What you are currently doing is resetting the list each time. This is why the page is scrolling up to the top each time. It is loading a completely new set of data.
Here's my attempt at fixing it.
Firstly, if you'll note in the example, they explain that MaxCount
is just an example and not necessary. What you really want is for your list to know when it has come to the end of the list. This means that it is the one who should be checking nentries
.
Your new IncrementalLoading implementation should look similar to this (if not exactly this).
public class GeneratorIncrementalLoadingClass: IncrementalLoadingBase
{
private int _numLeft;
private Func<int, Task<int>> _loadMore;
public GeneratorIncrementalLoadingClass(Func<int, Task<int>> loadMore, Func<int, T> generator)
{
_loadMore = loadMore;
_generator = generator;
}
protected async override Task<IList<object>> LoadMoreItemsOverrideAsync(System.Threading.CancellationToken c, uint count)
{
// If count is greater than the max size that we know, load the difference first
List<object> returnList = new List<object>();
if(count > 20)
{
var tempList = await LoadMoreItemsOverrideAsync(c, count);
returnList.AddRange(tempList);
}
// Find out if there are enough left that it's asking for
uint toGenerate = System.Math.Min(count, _numLeft);
// Wait for load
_numLeft = await _loadMore(toGenerate);
// This code simply generates
var values = from j in Enumerable.Range((int)_count, (int)toGenerate)
select (object)_generator(j);
_count += toGenerate;
return values.ToList().AddRange(tempList);
}
protected override bool HasMoreItemsOverride()
{
return _numLeft > 0;
}
Func<int, T> _generator;
uint _count = 0;
uint _maxCount;
}
You then use it like this.
// Move these outside of the loop
entriesCVS.Source = App.EntriesViewModel.entries;
App.EntriesViewModel.entries = new GeneratorIncrementalLoadingClass<Entry>(App.EntriesViewModel.LoadEntries, (index) =>
{
return App.EntriesViewModel.EntriesOc.ElementAt(index);
});
What should happen now is it sets the CollectionViewSource
at the outset, then loads the data into the base collection (your EntriesOc
) lazily. As the ListView gets scrolled to the bottom, it should automatically call LoadMoreItemsOverrideAsync
. What this will do is call your asynchronous load function and store the response (the number left that can be loaded). It then is able to notify the ListView
if it has any items left based on that response.
What you were doing before was loading all of the items lazily at the start as opposed to incrementally (in chunks, based on what the user is currently doing.
This solution still isn't perfect. It will have issues (that I've tried to account for) if the user scrolls down very far very quickly, and loads everything sequentially. The ideal situation would be if your background loading function could accept a range of items to load preferentially so that the last ones get loaded first instead of having to wait for the rest of the list to load.
Anyway, hope that helps and happy coding!
Upvotes: 1