Search code examples
c#listviewuwpwindows-10-universaluwp-xaml

UWP listview load more data when listview scrolling reaches to last item / infinite scrolling listview


I have a listview in my UWP (Windows 10) application. Ideally it will load 100 items when application starts.

When list is scrolled to bottom i.e. to the last item in listview, API call will go & will load another 100 items & so on..

Here is my code :

<ListView x:Name="lstSuggestions" Height="200" Width="250" Foreground="#333eb4" HorizontalAlignment="Left" Margin="60,10" SelectionChanged="lstSuggestions_SelectionChanged"></ListView>

following call binds the listview (first 100 items on app start) :

public async void GetData(string Id, string limit)
    { 
        string mainUrlForSuggestions = ApiUrl +  "&id=" + d;
        string finalSelCharas = "";

        using (var httpClient = new HttpClient())
        {
            var dataUri = await httpClient.GetStringAsync(mainUrlForSuggestions);
            JsonObject jsonObject = JsonObject.Parse(dataUri.ToString());
            JsonArray jsonArray = jsonObject["payload"].GetArray();
            foreach (JsonValue groupValue in jsonArray)
            {
                JsonObject groupObject = groupValue.GetObject();
                lstSuggestionsAdd.Add(new SuggestedWords { Name = groupObject.GetNamedString("sug_name"), ID = groupObject.GetNamedString("id") });
            }
            lstSuggestions.ItemsSource = lstSuggestionsAdd;
        } 
    }

on app start limit is 100, once list reaches to an end, it must set limit to 200 or next 100 items and make an API call again.

I tried to achieve this with pointerEntered event. But, couldn't achieve the said functionality as it only matches the height assigned to listview with pointer height, so that wont work as scrollviewer height can vary. I even tried to get access to scrollviewer, but couldn't!

I have also referred following URL's : How do I allow a UWP ListView to scroll past the last item? && Detect when WPF listview scrollbar is at the bottom? && https://social.msdn.microsoft.com/Forums/windows/en-US/63b4b530-61d8-477f-af96-87e33260c919/uwa-how-to-detect-the-end-and-the-start-of-listview-and-load-more-data-items?forum=wpdevelop

But none of them actually worked in my case.

I tried to find an event to achieve this functionality, but didn't find any.

Can anyone give an idea about how to detect if listview scrolling reached to an end (last item in the listview)???

Note that i am working on windows 10 UWP application & not win 8


Solution

  • It's a bit different; it uses the ListView's incremental loading functionality to create a infinite scrolling list.

    This means you won't have as much control of loading the data as you assumed in your question, but still I think it will suit your needs:

    It uses MVVM bindings so no typical UI events are used. If you don't know about MVVM, try to duckduckgo it a bit.

    First some XAML, the default main page:

    <Page
        x:Class="App6.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:App6"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance local:ViewModel, IsDesignTimeCreatable=True}">
    
        <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <ListView ItemsSource="{Binding Items}" 
                      DataFetchSize="1" 
                      IncrementalLoadingTrigger="Edge" 
                      IncrementalLoadingThreshold="5">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <TextBlock Text="{Binding Text}"></TextBlock>
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>
    </Page>
    

    Note the ItemsSource="{Binding Items}" DataFetchSize="1" IncrementalLoadingTrigger="Edge" IncrementalLoadingThreshold="5"

    1. ItemSource will bind to the items collection, its used in the item template
    2. DataFetchSize, the amount to fetch when the end is reached: in PAGES. (confused me for a moment)
    3. IncrementalLoadingTrigger, see msdn
    4. IncrementalLoadingThreshold, see msdn

    Then..., the code:

    First a custom observable collection: Here is also your load routine:

    public class IncrementalLoadingCollection : ObservableCollection<Item>, ISupportIncrementalLoading
    {
        uint x = 0; //just for the example
        public bool HasMoreItems { get { return x < 10000; } } //maximum
    
        //the count is the number requested
        public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
        {
            return AsyncInfo.Run(async cancelToken =>
            {
                //here you need to do your loading
                for (var c = x; c < x + count; c++)
                {
                    //add your newly loaded item to the collection
                    Add(new Item()
                    {
                        Text = c.ToString()
                    });
                }
    
                x += count;
                //return the actual number of items loaded (here it's just maxed)
                return new LoadMoreItemsResult { Count = count };
            });
        }
    }
    

    We are using a new Item class, so lets define it:

    //the type which is being used to display the data
    //you could extend it to use images and stuff
    public class Item
    {
        public string Text { get; set; }
    }
    

    Lets create a viewmodel:

    public class ViewModel
    {
        public ViewModel()
        {
            Items = new IncrementalLoadingCollection();
        }
        //using the custom collection: these are your loaded items
        public IncrementalLoadingCollection Items { get; set; }
    }
    

    Wrap it up in the code behind: we use the data context:

    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            DataContext= new ViewModel(); //using a viewmodel as data context           
        }
    }
    

    More info can be found here