,

Fetching Data When the User Scrolls to the End of a List

Most phone users are concerned about network usage. Network traffic comes at a premium, and a user’s perception of the quality of your app depends a lot on its responsiveness. When it comes to fetching data from a network service, it should be done in the most efficient way possible. Making the user wait while your app downloads a lot of data is a bad idea. Instead, data should be retrieved in bite-sized chunks.

I have created a ScrollViewerMonitor class that uses an attached property to monitor a ListBox and fetch data as the user needs it. You use it by adding an attached property to a control that contains a ScrollViewer, such as a ListBox, as shown in the following example:

<ListBox ItemsSource="{Binding Items}"
    u:ScrollViewerMonitor.AtEndCommand="{Binding FetchMoreDataCommand}" />

The AtEndCommand property specifies a command that is executed when the user scrolls to the end of the list.

The ScrollViewerMonitor works by retrieving the first child ScrollViewer control from its target (usually a ListBox). It then listens to its VerticalOffset property for changes. When a change occurs, and the ScrollableHeight of the scrollViewer is the same as the VerticalOffset, the AtEndCommand is executed (see Listing 27.9).

The VerticalOffset property is a dependency property, and to monitor it for changes I borrowed some of Pete Blois’s code (http://blois.us/), which allows you to detect changes to any dependency property. This class is called BindingListener and is located in the downloadable sample code.

LISTING 27.9. ScrollViewerMonitor Class


public class ScrollViewerMonitor
{
    public static DependencyProperty AtEndCommandProperty
        = DependencyProperty.RegisterAttached(
            "AtEndCommand", typeof(ICommand),
            typeof(ScrollViewerMonitor),
            new PropertyMetadata(OnAtEndCommandChanged));

    public static ICommand GetAtEndCommand(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(AtEndCommandProperty);
    }

    public static void SetAtEndCommand(DependencyObject obj, ICommand value)
    {
        obj.SetValue(AtEndCommandProperty, value);
    }

    public static void OnAtEndCommandChanged(
        DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement element = (FrameworkElement)d;
        if (element != null)
        {
            element.Loaded -= element_Loaded;
            element.Loaded += element_Loaded;
        }
    }
    static void element_Loaded(object sender, RoutedEventArgs e)
    {
        FrameworkElement element = (FrameworkElement)sender;
        element.Loaded -= element_Loaded;
        ScrollViewer scrollViewer = FindChildOfType<ScrollViewer>(element);
        if (scrollViewer == null)
        {
            throw new InvalidOperationException("ScrollViewer not found.");
        }

        var listener = new DependencyPropertyListener();
        listener.Changed
            += delegate
                {
                    bool atBottom = scrollViewer.ScrollableHeight > 0
                                        && scrollViewer.VerticalOffset
                                                >= scrollViewer.ScrollableHeight;

                    if (atBottom)
                    {
                        ICommand atEnd = GetAtEndCommand(element);
                        if (atEnd != null)
                        {
                            atEnd.Execute(null);
                        }
                    }
            };
        Binding binding = new Binding("VerticalOffset") {
                                           Source = scrollViewer };
        listener.Attach(scrollViewer, binding);
    }

    static T FindChildOfType<T>(DependencyObject root) where T : class
    {
        var queue = new Queue<DependencyObject>();
        queue.Enqueue(root);

        while (queue.Count > 0)
        {
            DependencyObject current = queue.Dequeue();
            int start = VisualTreeHelper.GetChildrenCount(current) - 1;
            for (int i = start; 0 <= i; i--)
            {
                var child = VisualTreeHelper.GetChild(current, i);
                var typedChild = child as T;
                if (typedChild != null)
                {
                    return typedChild;
                }
                queue.Enqueue(child);
            }
        }
        return null;
    }
}


The EbaySearchViewModel contains a FetchMoreDataCommand. When the user scrolls to the bottom of the list, the command is executed, which then sets a Busy flag and calls the network service asynchronously.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.2.223