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.
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.
3.15.229.111