Marek_86
Marek_86

Reputation: 11

Load more items with ListView (Windows 8.1)

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; 
                }
            }
    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

Answers (1)

Nate Diamond
Nate Diamond

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

Related Questions