Ahmar
Ahmar

Reputation: 750

Implementing ISupportIncrementalLoading interface - UWP - C#

I am learning C# and currently working on ISupportIncrementalLoading in UWP app but cant seem to get a hang of it. It gives me errors even before compiling it. I got the idea from here. But I am unable to understand as how I can implement it in my application.

I have a IIncrementalLoading interface and a separate class which dynamically adds content to an ObservableCollection<T>. If I don't implement Incremental Loading my ObservableCollection dynamically provides content to my SearchPage.xaml in GridView but if I load more items they replace previously generated items and only the new items are shown instead.

Code in SearchPage.xaml:

<Page
x:Class="WatchfreeWebsite.SearchPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WatchfreeWebsite"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:data="using:WatchfreeWebsite"
mc:Ignorable="d">

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>

    <StackPanel>
        <TextBlock Text="Search Results"
                   FontSize="20"
                   TextAlignment="Center"/>
        <TextBlock Text="Search for Movies and TV Shows"
                   TextAlignment="Center"
                   TextWrapping="Wrap"/>
        <HyperlinkButton x:Name="IndexPageLink"
                         Content="Go to Index"
                         HorizontalAlignment="Center"
                         Click="IndexPageLink_Click"/>
    </StackPanel>
    <StackPanel Grid.Row="1">
        <TextBox x:Name="SearchInputBox" 
                 TextAlignment="Left"
                 Width="280"
                 PlaceholderText="search"
                 TextChanged="SearchInputBox_TextChanged"></TextBox>
        <TextBlock x:Name="ErrorTextBox"
                   TextWrapping="Wrap"
                   TextAlignment="Center"/>
    </StackPanel>
    <GridView Grid.Row="2"
              x:Name="SearchGrid"
              HorizontalAlignment="Center"
              ItemsSource="{x:Bind SearchList, Mode=OneWay}"
              IsItemClickEnabled="True"
              ItemClick="SearchGrid_ItemClick">
        <GridView.Header>
            <StackPanel BorderBrush="Red"
                        BorderThickness="0,0,0,1"
                        Margin="5"
                        HorizontalAlignment="Center">
                <TextBlock Text=""
                           x:Name="SearchGridHeader"
                           TextAlignment="Center"
                           Margin="0"/>
            </StackPanel>
        </GridView.Header>
        <GridView.ItemTemplate>
            <DataTemplate x:DataType="data:SearchItems">
                <StackPanel BorderThickness="0,1,0,0"
                            BorderBrush="Red"
                            Margin="5">
                    <TextBlock Text="{x:Bind SearchTitle, Mode=OneWay}"
                               TextAlignment="Center"
                               TextWrapping="Wrap"
                               Width="140"
                               Height="40"/>
                    <StackPanel BorderBrush="Red" BorderThickness="0,0,0,1"
                                Margin="0">
                        <Image x:Name="CoverImage"
                               Source="{x:Bind SearchImageLink, Mode=OneWay}"
                               Width="130"
                               Height="200"
                               Margin="0"/>
                    </StackPanel>
                </StackPanel>
            </DataTemplate>
        </GridView.ItemTemplate>
        <GridView.Footer>
            <HyperlinkButton x:Name="MoreItemsLink"
                             Content="Load more items"
                             Click="MoreItemsLink_Click"
                             Visibility="Collapsed"/>
        </GridView.Footer>
    </GridView>
</Grid>

Code in SearchPage.xaml.cs:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using HtmlAgilityPack;
using static WatchfreeWebsite.HtmlDocs;



namespace WatchfreeWebsite
{
public sealed partial class SearchPage : Page
{
    HtmlDocument SearchDoc;
    public static string[] SearchLinks = new string[500];
    public static string[] SearchTitles = new string[500];
    public static string[] SearchImageLinks = new string[500];
    public static string[] SearchNextPagesLinks = new string[50];

    public static int numberOfLinks = 0;
    int numberOfTitles = 0;
    int numberOfImages = 0;
    int nextPage = 2;
    int numberOfPages = 0;
    public static int lastListItems = 0;


    //IIncrementalSource<SearchItems> SearchList = new IncrementalLoadingCollection<GetTVShows(),SearchItems>();

    int listItems = 0;
    DispatcherTimer sTimer = new DispatcherTimer();

    public SearchPage()
    {
        this.InitializeComponent();
        NavigationCacheMode = NavigationCacheMode.Enabled;
    }

    private void DeleteList(int count)
    {
        for (int x = 0; x < count; x++)
        {
            SearchList.RemoveAt(0);
        }
    }

    private async void LoadHtmlDocument(string url)
    {
        try
        {
            if (NetworkInformatiom())
            {
                SearchDoc = await new HtmlWeb().LoadFromWebAsync(url);
            }
            else
            {
                SearchDoc = null;
            }
            if (SearchDoc != null)
            {
                Links(SearchDoc);
            }
        }
        catch (Exception ex)
        {
            ErrorDialog("SearchDoc_Download_Failed\n" + ex.Message);
        }
    }

    private void Links(HtmlDocument doc)
    {
        listItems = 0;
        try
        {
            foreach (var link in doc.DocumentNode.Descendants("a").Where(pl => pl.Attributes.Contains("href")))
            {
                string dataValue = link.GetAttributeValue("href", "");
                //dataValue = ReplaceLinkStrings(dataValue);
                if (dataValue.StartsWith("/watch") && dataValue.EndsWith(".html"))
                {
                    SearchLinks[numberOfLinks] = MoviesPage.MoviesBaseLink + dataValue.Trim();
                    listItems++;
                    numberOfLinks++;
                }
            }
            ImageLinks(doc);
        }
        catch (Exception ex)
        {
            ErrorDialog("There was a problem while acquiring search links.\n" + ex.Message);
        }
    }

    private void ImageLinks(HtmlDocument doc)
    {
        try
        {
            //int LinkID = 0;
            foreach (var link in doc.DocumentNode.Descendants("img").Where(d => d.Attributes.Contains("src")))
            {
                if (link != null)
                {
                    if (link.Attributes["src"].Value == "/images/noposter.jpg")
                    {
                        SearchImageLinks[numberOfImages] = @"ms-appx:///Assets/noposter.jpg";
                    }
                    else
                    {
                        SearchImageLinks[numberOfImages] = string.Format("http:{0}", link.Attributes["src"].Value);
                    }
                    numberOfImages++;
                }
                if (numberOfImages == numberOfLinks)
                {
                    break;
                }
            }
            Titles(doc);
        }
        catch (Exception ex)
        {
            ErrorDialog("There was a problem in Image links.\n" + ex.Message);
        }
    }

    public void Titles(HtmlDocument doc)
    {
        try
        {
            //int a = 0;
            foreach (var link in doc.DocumentNode.Descendants("a").Where(t => t.Attributes.Contains("title")))
            {
                if (link != null && link.InnerText != "WatchFree.to" && link.InnerText != "Movies" && link.InnerText != "TV Shows")
                {
                    string x = link.GetAttributeValue("title", null);
                    x = x.Replace("Watch Putlocker", "");
                    x = x.Trim();
                    SearchTitles[numberOfTitles] = x;
                    numberOfTitles++;
                    if (numberOfTitles == numberOfLinks)
                    {
                        break;
                    }
                }
            }
            SearchList = SearchManager.GetTVShows(lastListItems, numberOfLinks);
            // += or =       we will find out
            this.Bindings.Update();
            lastListItems = numberOfLinks;
            CheckNextPageLinks(doc);
        }
        catch (Exception ex)
        {
            ErrorDialog("There was a problem while acquiring movie titles.\n" + ex.Message);
        }
    }

    private void SearchGrid_ItemClick(object sender, ItemClickEventArgs e)
    {
        var clickedItem = (SearchItems)e.ClickedItem;
        if (SearchImageLinks[clickedItem.SearchID - 1] != null && clickedItem.SearchLink != null)
        {
            if (clickedItem.SearchLink.Contains("tv-show"))
            {
                string[] itemDetails = { clickedItem.SearchTitle, SearchImageLinks[(clickedItem.SearchID - 1)], clickedItem.SearchLink };
                Frame.Navigate(typeof(TVShowDetailsPage), itemDetails);
            }
            else if (clickedItem.SearchLink.Contains("movie"))
            {
                string[] itemDetails = { clickedItem.SearchTitle, SearchImageLinks[(clickedItem.SearchID - 1)], clickedItem.SearchLink };
                Frame.Navigate(typeof(MovieDetails), itemDetails);
            }
        }
        else
        {
            ;
        }
    }

    private void SearchInputBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        string input = SearchInputBox.Text.Trim();
        string output = "";
        if (input.Length < 3)
        {
            SearchGridHeader.Text = "";
            if (SearchNextPagesLinks[0] != null || SearchLinks[0] != null || SearchImageLinks[0] != null || SearchTitles[0] != null)
            {
                DefaultVariablesValues();
            }
            if (SearchList != null)
            {
                if (SearchList.Count > 0)
                { 
                    DeleteList((SearchList.Count));
                }
            }
        }
        if (!(input.Length < 3))
        {
            //sTimer = new DispatcherTimer();
            if (!sTimer.IsEnabled)
            {
                sTimer.Interval = TimeSpan.FromSeconds(1);
                //sTimer.Start();
            }

            ErrorTextBox.Text = "";
            SearchGridHeader.Text = "Search Results";
            if (input.Contains(" "))
            {
                output = input.Replace(" ", "+");
                output = MoviesPage.MoviesBaseLink + @"/?keyword=" + output + @"&search_section=1";
                ErrorTextBox.Text = output;
                ErrorTextBox.IsTextSelectionEnabled = true;
            }
            else
            {
                output = MoviesPage.MoviesBaseLink + @"/?keyword=" + input + @"&search_section=1";
                //ErrorTextBox.Text = output;
            }
            SearchList = new ObservableCollection<SearchItems>();
            LoadHtmlDocument(output);

        }
        else
        {
            ErrorTextBox.Text = "the length of the input should not be less than 3";
            if (input.Length == 0)
            {
                ErrorTextBox.Text = "";
            }
            if (sTimer.IsEnabled)
                sTimer.Stop();
        }
    }

    private void IndexPageLink_Click(object sender, RoutedEventArgs e)
    {
        Frame.Navigate(typeof(IndexPage));
    }

    private void MoreItemsLink_Click(object sender, RoutedEventArgs e)
    {
        LoadHtmlDocument(MoviesPage.MoviesBaseLink + SearchNextPagesLinks[numberOfPages - 1]);
    }

    private void DefaultVariablesValues()
    {
        numberOfLinks = 0;
        numberOfTitles = 0;
        numberOfImages = 0;
        nextPage = 2;
        numberOfPages = 0;
        lastListItems = 0;
        int a = 0;
        foreach (var item in SearchLinks)
        {
            if (item != null)
            {
                SearchLinks[a] = null;
            }
            a++;
        }
        a = 0;
        foreach (var item in SearchTitles)
        {
            if (item != null)
            {
                SearchTitles[a] = null;
            }
            a++;
        }
        a = 0;
        foreach (var item in SearchImageLinks)
        {
            if (item != null)
            {
                SearchImageLinks[a] = null;
            }
            a++;
        }
        a = 0;
        foreach (var item in SearchNextPagesLinks)
        {
            if (item != null)
            {
                SearchNextPagesLinks[a] = null;
            }
            a++;
        }
    }

    private void CheckNextPageLinks(HtmlDocument doc)
    {
        string lastLink = "";
        foreach (var link in doc.DocumentNode.Descendants("a").Where(t => t.Attributes.Contains("href")))
        {
            string value = link.GetAttributeValue("href", "");
            if (value.StartsWith("/?keyword") && value.Contains("page=" + nextPage))
            {
                SearchNextPagesLinks[numberOfPages] = value;
                lastLink = value;
                numberOfPages++;
                break;
            }
        }
        //--------------------------------------
        // checking for the availability of next page
        if (lastLink.StartsWith(@"/?keyword") && lastLink.Contains("page=" + nextPage))
        {
            MoreItemsLink.Visibility = Visibility.Visible;
        }
        else
        {
            MoreItemsLink.Visibility = Visibility.Collapsed;
        }
        //--------------------------------------
        foreach (var item in SearchNextPagesLinks)
        {
            if (item == lastLink)
            {
                nextPage++;
            }
        }

    }
}
}

Code in IncrementalLoadingInterface:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;

namespace WatchfreeWebsite
{
public interface IIncrementalSource<T>
{
    Task<IEnumerable<T>> GetPagedItems(int pageIndex, int pageSize);
}

public class IncrementalLoadingCollection<T, I> : ObservableCollection<I>,
 ISupportIncrementalLoading
 where T : IIncrementalSource<I>, new()
{
    private T source;
    private int itemsPerPage;
    private bool hasMoreItems;
    private int currentPage;

    public IncrementalLoadingCollection(int itemsPerPage = 1)
    {
        this.source = new T();
        this.itemsPerPage = itemsPerPage;
        this.hasMoreItems = true;
    }

    public bool HasMoreItems
    {
        get { return hasMoreItems; }
    }

    public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
    {
        var dispatcher = Window.Current.Dispatcher;

        return Task.Run(
            async () =>
            {
                uint resultCount = 0;
                var result = await source.GetPagedItems(currentPage++, itemsPerPage);

                if (result == null || result.Count() == 0)
                {
                    hasMoreItems = false;
                }
                else
                {
                    resultCount = (uint)result.Count();

                    await dispatcher.RunAsync(
                        CoreDispatcherPriority.Normal,
                        () =>
                        {
                            foreach (I item in result)
                                this.Add(item);
                        });
                }

                return new LoadMoreItemsResult() { Count = resultCount };

            }).AsAsyncOperation();
    }
}
}

Code in SearchItems (separate class):

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace WatchfreeWebsite
{
public class SearchItems
{
    public int SearchID
    {
        get; set;
    }

    public string SearchTitle
    {
        get; set;
    }

    public string SearchImageLink
    {
        get; set;
    }

    public string SearchLink { get; set; }
}

public class SearchManager : IIncrementalSource<SearchItems>
{
    private ObservableCollection<SearchItems> items;

    public async Task<IEnumerable<SearchItems>> GetPagedItems(int pageIndex, int pageSize)
    {
        return await Task.Run<IEnumerable<SearchItems>>(() =>
        {
            if (SearchPage.lastListItems == 0 && SearchPage.numberOfLinks == 0)
            {
                var shows = new ObservableCollection<SearchItems>();
                shows.Add(new SearchItems { SearchID = 1, SearchTitle = "No Search Results", SearchImageLink = @"ms-appx:///Assets/noposter.jpg" });
                return shows;
            }
            else
            {
                var result = GetTVShows(SearchPage.lastListItems, SearchPage.numberOfLinks); //(from p in persons select p).Skip(pageIndex * pageSize).Take(pageSize);
                return result;
            }

        });
    }

    public static ObservableCollection<SearchItems> GetTVShows(int start, int finish)
    {
        var movies = new ObservableCollection<SearchItems>();
        for (int x = start; x < finish; x++)
        {
            movies.Add(new SearchItems { SearchID = (x + 1), SearchTitle = SearchPage.SearchTitles[x], SearchImageLink = SearchPage.SearchImageLinks[x], SearchLink = SearchPage.SearchLinks[x] });
        }

        //if (start == 0 && finish == 0)
        {
            //movies.Add(new SearchItems { SearchID = 1, SearchTitle = "No Search Results", SearchImageLink = @"ms-appx:///Assets/noposter.jpg" });
        }
        return movies;
    }
}

}

Explanation: First before implementing incremental loading I call GetTVShows() with start and finish parameters to get the required amount of items. If the search doesn't get anything, both of them are zero so GetTVShows() only produces one item with No Results.

But in incremental loading I have to specify at compile time how many items I want, as it has to search for items I cannot specify how many items will be there but maximum of the items I get is 24 per request.

I even tried to use += to my SearchResults but VS produces the error that this is not available with ObservableCollection so I searched internet and found the incremental loading. But if it's possible I want to add more results with a click of the link rather than scrolling through the edges. I cannot seem to understand how Incremental loading is working and how I need to call my GetTVShows() function to produce the desired results as it does now.

I also recall this from Twitter for Windows 10 as it uses incremental loading to get more DMs with a hyperlink click. But those DMs are already stored in a database so they are not dynamically added (at least that is what I think and I could be wrong.).

If someone can point out what I can do to make it work, that will be really appreciated. Any information to help me out or any links to point me in the right direction so I can understand the effects of how ISupportIncrementalLoading actually works, would be appreciated a lot. Thanks.

Upvotes: 1

Views: 1029

Answers (1)

Ahmar
Ahmar

Reputation: 750

I figured it out on my own...

public static ObservableCollection<SearchItems> GetTVShows(int start, int finish)
{
    var movies = new ObservableCollection<SearchItems>();
    for (int x = start; x < finish; x++)
    {
        movies.Add(new SearchItems { SearchID = (x + 1), SearchTitle = SearchPage.SearchTitles[x], SearchImageLink = SearchPage.SearchImageLinks[x], SearchLink = SearchPage.SearchLinks[x] });
    }

    if (start == 0 && finish == 0)
    {
        movies.Add(new SearchItems { SearchID = 1, SearchTitle = "No Search Results", SearchImageLink = @"ms-appx:///Assets/noposter.jpg" });
    }
    return movies;
}

I was using the above code to add items to my GridView and as it turns out var movies was inside the local scope so it was empty every time the function was called. That's why it only loaded the items it was asked for. Now I declared it in the file scope as private static because the GetTVShows() is also a static function and it adds more items to the view as I click my hyperlink button.

So in my case all the time spent on incremental loading was just wasted. Because I didn't need the edge incremental loading anyway.

Now my code looks like:

private static ObservableCollection<SearchItems> movies = new ObservableCollection<SearchItems>();
public static ObservableCollection<SearchItems> GetTVShows(int start, int finish)
{
//var movies = new ObservableCollection<SearchItems>();
for (int x = start; x < finish; x++)
{
    movies.Add(new SearchItems { SearchID = (x + 1), SearchTitle = SearchPage.SearchTitles[x], SearchImageLink = SearchPage.SearchImageLinks[x], SearchLink = SearchPage.SearchLinks[x] });
}

if (start == 0 && finish == 0)
{
    movies.Add(new SearchItems { SearchID = 1, SearchTitle = "No Search Results", SearchImageLink = @"ms-appx:///Assets/noposter.jpg" });
}
return movies;
}

Hopefully it helps someone else along the way...

Upvotes: 1

Related Questions