Colin Eberhardt's Technology Adventures

Tombstoning with PhoneGap for Windows Phone 7 (and KnockoutJS)

October 24th, 2011

A few weeks back I wrote a blog post about how the recent announcement of PhoneGap support for Windows Phone 7 (WP7) which makes it possible to develop HTML5-based applications. In my previous blog post I showed the development of a simple HTML5 / JavaScript application which PhoneGap wraps up within a Silverlight application ‘shell’ allowing it to be deployed to your phone and potentially submitted to the Marketplace.

However, in order to pass the various Marketplace requirements and gain certification, your application must correctly handle the application lifecycle. With the recent Mango release, the lifecycle has become a little more complicated (although better! in that it adds multi-tasking / fast-app switching). I have also covered the lifecycle in a previous blog post and demonstrated how you can handle the various lifecycle events within an MVVM application.

The most tricky part of the application lifecycle that as a developer you need to handle is the tombstoned state, where your application is terminated (i.e. stopped and removed from memory). It is your responsibility to save enough state in order that when your tombstoned application is restarted, it looks to the user as if your application never stopped running, i.e. you restore your application UI to its original state.

The Mango application lifecycle is illustrated below:

The PhoneGap events API includes pause and resume events, which can be used to detect when the application transitions to and from the dormant state, however, for WP7 these events do not give us enough information. When resuming, we need to know whether it has resumed from a dormant or a tombstoned state. Considering that the tombstoned state is peculiar to WP7 (Android, and iPhone simply have a suspend / resume model), I don’t think it makes sense for the PhoneGap APIs to change in order to accommodate this. In this blog post I will show how the WP7 PhoneGap application host can be modified in order to support tombstoning.

But before we get there, I want to digress a while and look at using the MVVM pattern with JavaScript …

Using the MVVM pattern in JavaScript

Handling tombstoning is much easier if you have a good separation between your view and your logic, with the MVVM pattern being a sensible choice for achieving this. When your application is tombstoned (and your application terminated), then re-activated, it is your responsibility to recreate the original state. Your view-model, is a model-of-a-view, so technically should provide all the information required to fulfil this requirement. See my previous blog post for details.

There are numerous JavaScript UI frameworks available (MVC, MVP, MVVM), however, because I feel tombstoning lends itself particularly well to the MVVM pattern, I decided to give KnockoutJS a try. When reading about this framework you will find references to WPF and Silverlight, it is clear that it has been heavily inspired by the Microsoft XAML frameworks.

The application I have built to demonstrate tombstoning is a very simple, single page twitter search application.

The Knockout view model is a JavaScript object where the properties are defined as ‘observables’. These are JavaScript functions which provide change notification, much like CLR properties with INotifyPropergtyChanged within Silverlight / WPF.

The View-Model

The view model for my twitter search application is shown below:

/// <summary>
/// A view model for searching twitter for a given term
/// </summary>
function TwitterSearchViewModel() {
 
  var that = this;
 
  // --- properties
 
  this.isSearching = ko.observable(false);
 
  this.searchTerm = ko.observable("#wp7dev");
 
  this.tweets = ko.observableArray();
 
  // --- functions
 
  // search twitter for the given string
  this.search = function () {
    if (that.searchTerm() != "") {
 
      that.isSearching(true);
      var url = "http://search.twitter.com/search.json?q=" +
            encodeURIComponent(that.searchTerm());
 
      $.ajax({
        dataType: "jsonp",
        url: url,
        success: function (response) {
          // clear the results
          that.tweets.removeAll();
          // add the new items
          $.each(response.results, function () {
            var tweet = new TweetViewModel(this);
            that.tweets.push(tweet);
          });
 
          that.isSearching(false);
        }
      });
    }
  }
}

It comprises a few simple properties and a search function. Note, the items property is an observableArray, this is analogous to the WPF / Silverlight ObservableCollection, which raises events when its contents are modified, allowing the UI to update automatically. The search function queries the twitter APIs to find matching tweets, updating the observable items array with the results.

The TwitterSearchViewModel items collection is populated with TweetViewModel instances:

/// <summary>
/// A view model that represents a single tweet
/// </summary>
function TweetViewModel(tweet) {
 
    // --- properties
 
    this.author = tweet.from_user,
    this.text = tweet.text,
    this.time = parseDate(tweet.created_at);
    this.thumbnail = tweet.profile_image_url;
 
    // --- functions
 
    // parses the tweet date to give a more readable format
    function parseDate(date) {
      var diff = (new Date() - new Date(date)) / 1000;
 
      if (diff < 60) {
        return diff.toFixed(0) + " seconds ago";
      }
 
      diff = diff / 60;
      if (diff < 60) {
        return diff.toFixed(0) + " minutes ago";
      }
 
      diff = diff / 60;
      if (diff < 10) {
        return diff.toFixed(0) + " hours ago";
      }
 
      diff = diff / 24;
      return diff.toFixed(0) + " days ago";
    }
};

Note, here the properties are not observables, again much like WPF / Silverlight you can bind to a property that does not notify of changes if this is not required.

Also, the Knockout documentation typically defines view-models as literal objects. I prefer to use constructor functions, allowing the creation of multiple instances of the same view model.

The View

With Knockout the view is defined in HTML, you can create it directly, or via a template. I have created the following templates:

<script type=text/x-jquery-tmpl" charset="utf-8" id="twitterSearchView">
  <form data-bind="submit: search">
      <input data-bind="value: searchTerm, valueUpdate: 'afterkeydown'" />
      <button type="submit" data-bind="enable: searchTerm().length > 0 &amp;&amp; isSearching() == false">Go</button>  
  </form>    
  <ul data-bind="template: {name: 'tweetView', foreach: tweets}"> </ul>
</script>
<script type="text/x-jquery-tmpl" charset="utf-8" id="tweetView">
  <li class="tweet">
    <div class="thumbnailColumn">
      <img data-bind="attr: { src: thumbnail }" class="thumbnail"/>
    </div>
    <div class="detailsColumn">
      <div class="author" data-bind="text: author"/> 
      <div class="text" data-bind="text: text"/> 
      <div class="time" data-bind="text: time"/> 
    </div>
  </li>
</script>

The data-bind attribute is used by Knockout to set up the various bindings, connecting your view model properties and observables to the UI.It also defines functions to invoke when DOM events are raised, in much the same way as commands do in WPF / Silverlight.

The application code

Instantiating the view-model and the view is as simple as the following:

$(document).ready(function () {
  document.addEventListener("deviceready", onDeviceReady, false);
});
 
// phonegap is initialised
function onDeviceReady() {
 
  // create the view model
  twitterSearchViewModel = new TwitterSearchViewModel();
 
  // create an instance of the view
  $("#twitterSearchView").tmpl("").appendTo("#app");
 
  // wire-up
  ko.applyBindings(twitterSearchViewModel)
}

Originally I wanted to have the HTML for each view within a separate file, loading them via jQuery as described in this blog post. However, I just couldn’t get this to work within the embedded WP7 browser.

The tweetView tempate is used via the template / foreach Knockout binding, mimicking the Silverlight / WPF ItemsControl.ItemTemplate concept.

The application, after a bit of styling, looks like this:

Tombstoning

Now that the application has a decent structure, we can tackle the application-lifecycle. We need a way to store the view model state when the application transitions into a dormant state. Fortunately Knockout makes this very easy by supplying a toJSON function, which can create a JSON representation of your view-model graph (minus the observables). I have added a getState function to the TwitterSearchViewModel as follows:

// gets the view model state as a JSON string
this.getState = function () {
  return ko.toJSON(this);
}

Now we need a way to invoke this function when the application pauses. PhoneGap provides a pause lifecycle event, however, we need to store the output of this function in the WP7 PhoneApplicationService.Current.State dictionary. Because this is a very much WP7 specific feature, I decided to do this outside of the PhoneGap lifecycle events.

My handler for the Deactivated event simply invokes the above method, storing the state in the application state dictionary:

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
  var viewModelState = PhoneGapView.Browser.InvokeScript("getState") as string;
  PhoneApplicationService.Current.State[ModelKey] = viewModelState;
}

Note, to do this I have had to modify the current PhoneGap WP7 library to provide access to the underlying WebBrowser control, I have raised an issue requesting this change to the PhoenGap library.

The application now stores it state when it becomes dormant, the next step is to use this state when an application is activated from a tombstoned state. Within the Activated handler we can read this state as follows:

/// <summary>
/// Gets the state that has been retrieved from isolated storage
/// in order to re-activated after tombstoning.
/// </summary>
public string TombstoneState { get; private set; }
 
private void Application_Activated(object sender, ActivatedEventArgs e)
{
  if (!e.IsApplicationInstancePreserved)
  {
    if (PhoneApplicationService.Current.State.ContainsKey(ModelKey))
    {
      var viewModelState = PhoneApplicationService.Current.State[ModelKey] as string;
      TombstoneState = viewModelState;
    }
  }
}

Note, we check IsApplicationInstancePreserved, if this is true, we do not need to use the state that was saved during deactivation, this allows for fast-application switching.

Unfortunately as this point our UI has not been created and our JavaScript application code is not running, which is why the tombstoned state is simply stored in a public property of our application. To pick this state up, we add a new step to our JavaScript view model creation code:

// phonegap is initialised
function onDeviceReady() {
 
  // create the view model
  twitterSearchViewModel = new TwitterSearchViewModel();
 
  // ---> check for tombstoned state
  window.external.Notify("getState");
 
  // instantiate the view an bind
  $("#twitterSearchView").tmpl("").appendTo("#app");
  ko.applyBindings(twitterSearchViewModel)
}

When the PhoneGap view is created, we add a handler to the ScriptNotify event, allowing us to handle this getState notification:

// Constructor
public MainPage()
{
  InitializeComponent();
 
  phoneGapView.Browser.ScriptNotify += Browser_ScriptNotify;
}
 
 
private void Browser_ScriptNotify(object sender, NotifyEventArgs e)
{
  string commandStr = e.Value;
 
  if (commandStr == "getState" && App.Current.TombstoneState != null)
  {
    phoneGapView.Browser.InvokeScript("setState", new string[] { App.Current.TombstoneState });
  }
}

This checks for the presence of tombstone state, and if found, invokes setState back on our JavaScript view model:

// sets the view model state based on the given JSON string.
this.setState = function (stateString) {
  var state = $.parseJSON(stateString),
      that = this;
 
  this.isSearching(state.isSearching);
  this.searchTerm(state.searchTerm);
 
  this.tweets.removeAll();
  $.each(state.tweets, function () {
    that.tweets.push(this);
  });
}

Note, re-creating our view-model form JSON data is a little more involved than the opposite. I have also cheated a little here, rather than re-creating each TweetViewModel I am using the JSON representation, because this view model has no public functions (i.e. commands).

Conclusions

With the above code the PhoneGap application now successfully handles all of the WP7 lifecycle states and transitions. There are a couple of things to note if you try to run this code yourself:

  1. You can force tombstoning via the Debug properties, “Tombstone upon deactiviation while debugging”.
  2. I have fund that the WebBrowser on the emulator does not tombstone correctly, when your application resumes the WebBrowser control fails to execute any JavaScript! Fortunately on a real device it works just fine.

Now that I can tombstone a PhoneGap application, I feel that it is one step closer to be a viable solution for application development. The final thing that I still haven’t quite worked out yet is navigation and back-button support. Fortunately Knockout has a lot to offer in this area as well, but more on that later …

You can download the full sourcecode (including PhoneGap library mods) here: PhoneGapExample.zip

Regards, Colin E.

Codeproject September Competition – Scored a Double!

October 22nd, 2011

I have just received an email from Codeproject – my article on XAMLFinance, a cross platform Silverlight, WPF and WP7 application, has won Best C# and Best Overall article competitions for September.

Having narrowly missed out in the monthly competitions a few times, I am pleased to have scored a double this time round! Thanks to all who voted.

Regards, Colin E.

Adding Error Bars to Visiblox Silverlight Charts

October 21st, 2011

Having spent a number of years studying Physics at university, I have had the importance of error bars well and truly drummed into me! Within physics, or any experimental science, there are always going to be errors in the measurements you make. The more repeat measurements you make, the more confident you can be in the mean value, however you cannot remove the errors altogether. Error bars provide a way to represent the spread of experimental observations graphically, without them, it is hard to have any confidence in the conclusions drawn from the observations!

In this blog post I will show how to implement a custom Visiblox chart series to render error bars:

(The data in the above chart is from a page which details how to calculate the standard error from experimental results).

Creating a Custom Series

As described in my previous blog post on creating a spline series, to create a new series type, you sub-class one of the Visiblox base-classes, in this case MultiValueSeriesBase is a suitable starting point:

public class ErrorBarSeries : MultiValueSeriesBase
{ 
  protected override FrameworkElement CreatePoint(IDataPoint dataPoint)
  {
    throw new NotImplementedException();
  }
 
  protected override void RenderDataLabels()
  {
    throw new NotImplementedException();
  }
}

I don’t want data labels, so the only method I need to implement is CreatePoint, which takes the (multi-valued) point to be rendered as its only argument. The lifecycle of point creating and destruction is taken care of by the base-class.

The IDataPoint has a string indexer which is used to retrieve multiple Y values for multi-valued series. It is a good idea to define these in a single place, here we define the three y-values required for an error-bar series:

public static readonly string ErrorUp = "ErrorUp";
 
public static readonly string ErrorDown = "ErrorDown";
 
public static readonly string Value = "Value";

The CreatePoint implementation for this series creates a Path as follows:

protected override FrameworkElement CreatePoint(IDataPoint dataPoint)
{
  var lineGeometry = BuildGeometry(dataPoint);
 
  Path line = new Path();
  line.Stroke = new SolidColorBrush(Colors.Black);
  line.Fill = new SolidColorBrush(Colors.Gray);
  line.StrokeThickness = 1.0;
  line.StrokeLineJoin = PenLineJoin.Bevel;
  line.Data = lineGeometry;
  line.SetValue(ZoomCanvas.IsScaledPathProperty, true);
 
  return line;
}

The BuildGeometry method does most of the work, extracting the values from the IDataPoint, transforming them (via the axis) to the required coordinate system, then creating a suitable geometry:

/// <summary>
/// Creates the geometry for the given datapoint
/// </summary>
private PathGeometry BuildGeometry(IDataPoint dataPoint)
{
  var halfWidth = SuggestedPointWidth * WidthFactor;
 
  // obtain the data values
  var topDataValue = dataPoint[ErrorUp] as IComparable;
  var middleDataValue = dataPoint[Value] as IComparable;
  var bottomDataValue = dataPoint[ErrorDown] as IComparable;
 
  // convert to a the required render coordinates
  double topRenderPos = YAxis.GetDataValueAsRenderPositionWithoutZoom(topDataValue);
  double middleRenderPos = YAxis.GetDataValueAsRenderPositionWithoutZoom(middleDataValue);
  double bottomRenderPos = YAxis.GetDataValueAsRenderPositionWithoutZoom(bottomDataValue);
 
  double xMiddleRenderPos = XAxis.GetDataValueAsRenderPositionWithoutZoom(dataPoint.X);
  double xRightRenderPos = xMiddleRenderPos - halfWidth;
  double xLeftRenderPos = xMiddleRenderPos + halfWidth;
 
  // build a suitable gemoetry
  PathGeometry lineGeometry = new PathGeometry();
 
  PathFigure upperVerticalLine = CreateLineFigure(
    new Point(xMiddleRenderPos, middleRenderPos - halfWidth),
    new Point(xMiddleRenderPos, topRenderPos));
  lineGeometry.Figures.Add(upperVerticalLine);
 
  PathFigure lowerVerticalLine = CreateLineFigure(
    new Point(xMiddleRenderPos, bottomRenderPos),
    new Point(xMiddleRenderPos, middleRenderPos + halfWidth));
  lineGeometry.Figures.Add(lowerVerticalLine);
 
  PathFigure upperBar = CreateLineFigure(
    new Point(xLeftRenderPos, topRenderPos),
    new Point(xRightRenderPos, topRenderPos));
  lineGeometry.Figures.Add(upperBar);
 
  PathFigure lowerBar = CreateLineFigure(
    new Point(xLeftRenderPos, bottomRenderPos),
    new Point(xRightRenderPos, bottomRenderPos));
  lineGeometry.Figures.Add(lowerBar);
 
  PathFigure center = CreateLineFigure(
    new Point(xMiddleRenderPos - halfWidth, middleRenderPos),
    new Point(xMiddleRenderPos, middleRenderPos + halfWidth),
    new Point(xMiddleRenderPos + halfWidth, middleRenderPos),
    new Point(xMiddleRenderPos, middleRenderPos - halfWidth)
  );
  lineGeometry.Figures.Add(center);
 
  return lineGeometry;
}
 
/// <summary>
/// Create a line figure that connects the given points
/// </summary>
private PathFigure CreateLineFigure(params Point[] points)
{
  // add all the points (except the first)
  var pointCollection = new PointCollection();
  foreach (var point in points.Skip(1))
  {
    pointCollection.Add(point);
  }
 
  // create a figure, using the first point as the StartPoint.
  return new PathFigure()
  {
    IsClosed = true,
    StartPoint = points.First(),
    Segments = new PathSegmentCollection()
    {
      new PolyLineSegment
      {
        Points = pointCollection
      }
    }
  };
}

We can now create an instance of this series in XAML:

<vis:Chart x:Name="chart">
  <vis:Chart.Series>
    <local:ErrorBarSeries/>
  </vis:Chart.Series>
</vis:Chart>

Supplying data to the chart via MultiValuedDataPoint as follows:

public MainPage()
{
  InitializeComponent();
 
  var data = new DataSeries<double, double>();
  data.Add(CreatePoint(-195, 1.4, 0.2));
  data.Add(CreatePoint(0, 62.2, 9.3));
  data.Add(CreatePoint(20, 70.4, 6.5));
  data.Add(CreatePoint(100, 77.4, 1.9));
 
  chart.Series[0].DataSeries = data;
}
 
private MultiValuedDataPoint<double, double> CreatePoint(double x, double y, double error)
{
  var point = new MultiValuedDataPoint<double, double>(x,
    new Dictionary<object, double>()
    {
      { ErrorBarSeries.ErrorUp, y + error },
      { ErrorBarSeries.ErrorDown, y - error },
      { ErrorBarSeries.Value, y }
    });
  return point;
}

This results in the following chart:

Binding to a Multi-valued Series

In the previous example we created instances of MultiValuedDataPoint, a Visiblox type for representing multi-valued points. As an alternative, we can create model objects to represent each point, rendering them in the chart via databinding.

We first modify the code to create a collection of Measurement instances (a simple model object that implements INotifyPropertyChanged):

public MainPage()
{
  InitializeComponent();
 
  var data = new ObservableCollection<Measurement>();
  data.Add(CreateMeasurement(-195, 1.4, 0.2));
  data.Add(CreateMeasurement(0, 62.2, 9.3));
  data.Add(CreateMeasurement(20, 70.4, 6.5));
  data.Add(CreateMeasurement(100, 77.4, 1.9));
  this.DataContext = data;    
}
 
private Measurement CreateMeasurement(double x, double y, double error)
{
  return new Measurement()
    {
      XValue = x,
      YValue = y,
      YValueErrorUp = y + error,
      YValueErrorDown = y - error
    };
}

The markup for the chart is modified to use a BindableDataSeries, with bindings specified for the various component of the error bar series. Also, the ItemsSource of the BindableDataSeries is bound to the inherited DataContext:

<local:ErrorBarSeries>
  <local:ErrorBarSeries.DataSeries>
    <vis:BindableDataSeries ItemsSource="{Binding}"
                            XValueBinding="{Binding XValue}">
      <vis:BindableDataSeries.YValueBindings>
        <vis:YValueBinding YValueKey="Value" Binding="{Binding YValue}"/>
        <vis:YValueBinding YValueKey="ErrorUp" Binding="{Binding YValueErrorUp}"/>
        <vis:YValueBinding YValueKey="ErrorDown"  Binding="{Binding YValueErrorDown}"/>
      </vis:BindableDataSeries.YValueBindings>              
    </vis:BindableDataSeries>
  </local:ErrorBarSeries.DataSeries>
</local:ErrorBarSeries>

We can also display our data in a DataGrid, allowing us to manipulate the values (not that I condone manipulation of scientific data!), with the changes being reflected in the chart:

<sdk:DataGrid Grid.Row="1"
              x:Name="grid"
              ItemsSource="{Binding}"/>

This gives us the following application:

You can edit the values, with the changes reflected immediately in the chart above.

You can download the sourcecode for the above example here: VisibloxErrorBarSeries.zip

Regards, Colin E.

Paging Data from the Server with Silverlight

October 19th, 2011

This blog post provides an implementation of IPagedCollectionView which allows paging of data from the server. An IPagedDataSource is introduced that allows any paged data source to be plugged in, with the standard controls such as DataPager making it easy to create paging applications.

With web-based applications, bandwidth constraints often mean that when querying large datasets, the results must be paged, i.e. split into discrete pages each containing a small number of results, with an API available for moving forwards / backwards or to a specific page. There are numerous APIs available on the web that expose this type of interface, for example:

With paging being a common application requirement, Silverlight has the functionality to deal with this scenario built-in. The System.Windows.Data assembly contains the IPagedCollectionView interface, which defines the following methods, properties and events, allowing navigation of paged data:

The System.Windows.Controls assembly contains a DataPager control, that collaborates with this interface to provide a UI for paging the dataset. Simply set the Source property of this control to an instance of IPagedCollectionView and your user can page through the data:

NOTE: The Source property of DataPager is of type IEnumerable, which is a little odd, because it doesn’t really need to enumerate the collection of items being displayed. Internally it probes the object you supply as the Source to see if it implements IPagedCollectionView, and if it does, the control becomes ‘active’.

This control gives you quite a bit of functionality for free, the buttons become enabled / disabled based on whether navigation forwards / backwards is possible. It respects the CanChangePage property, computes the total number of pages etc …

So, how do you make use of this interface and control?

The System.Windows.Data assembly also contains a concrete implementation of the paging interface, PagedCollectionView. This class gives much more than just paging, it also implements grouping, sorting and filtering of a source collection (IEnumerable). To make use of this class simply construct an instance based on your collection of data and use that as your DataContext:

List<MyDataObject> sourceCollection = ParseData(serverResponse);
var collectionView = new PagedCollectionView(sourceCollection);
this.DataContext = collectionView;

The problem is PagedCollectionView allows you to sort, group and page a collection of data that is already held in memory. What I want to do, and what I think is a more common use-case, is page data that is supplied by the server. I don’t want to have to fetch all the data up-front then page it on the client, this would defeat the object of paging in the first place … to improve performance!

I discovered that there is an IPagedCollectionView implementation as part of RIA Services that provides this functionality, the DomainCollectionView. However, if I want to page data form a simple JSON formatted web service, adding a dependency to RIA Services seems like overkill. So I decided to create my own simple implementation of this interface.

In order to make a generic/ re-useable implementation of this paging interface, I first created an interface that would act as the source of the data:

/// <summary>
/// Defines a source of data that can be paged.
/// </summary>
public interface IPagedDataSource<TDataType>
{
  /// <summary>
  /// Asynchronously returns the data for the given page
  /// </summary>
  void FetchData(int pageNumber, Action<PagedDataResponse<TDataType>> responseCallback);
}
 
/// <summary>
/// The items returned as a result of a paged data request.
/// </summary>
public class PagedDataResponse<TDataType>
{
  /// <summary>
  /// The items contained within the requested page
  /// </summary>
  public List<TDataType> Items { get; set; }
 
  /// <summary>
  /// The total count of all available items
  /// </summary>
  public int TotalItemCount { get; set; }
}

This interface is very simple, having a single method that fetches data for the given page. The result is returned asynchronously, providing both the items within the given page and the total item count.

My paging collection view extends ObservableCollection and takes an instance of IPagedDataSource in its constructor:

public class ServerSidePagedCollectionView<T> : ObservableCollection<T>,
  IPagedCollectionView
{
  IPagedDataSource<T> _pagedDataSource;
 
  public ServerSidePagedCollectionView(IPagedDataSource<T> pagedDataSource)
  {
    _pagedDataSource = pagedDataSource;
  }
 
  ...
}

As an aside, aren’t generics great?

The various properties defined in IPagedCollectionView (CanChangePage, TotalPages, etc …) are implemented as standard field-backed properties that notify changes via INotifyPropertyChanged which is inherited via ObservableCollection, I am not going to show them here.

The various methods that permit navigation are all implemented using the same pattern:

public bool MoveToFirstPage()
{
  RefreshData(0);
  return true;
}
 
public bool MoveToLastPage()
{
  RefreshData(TotalPages - 1);
  return true;
}    
 
public bool MoveToNextPage()
{
  RefreshData(PageIndex + 1);
  return true;
}
 
public bool MoveToPage(int pageIndex)
{
  RefreshData(pageIndex);
  return true;
}
 
public bool MoveToPreviousPage()
{
  RefreshData(PageIndex - 1);
  return true;
}

With the RefreshData method doing all the work, it is the only method that uses the supplied IPagedDataSource interface and is shown below:

/// <summary>
/// Fetches the data for the given page
/// </summary>
private void RefreshData(int newPageIndex)
{
  // set the pre-fetch state
  CanChangePage = false;
  OnPageChanging(newPageIndex);
 
  _pagedDataSource.FetchData(newPageIndex, response =>
    {
      // process the received data
      DataReceived(response);
 
      // set the post-fetch state
      PageIndex = newPageIndex;
      OnPageChanged();
      CanChangePage = true;
    });
}

With the simple implementation of IPagedCollectionView given above I can now page data from a range of web services by simply implementing IPagedDataSource. For example, to page data from NetFlix you can use the following implementation:

/// <summary>
/// A paged NetFlix movies search datasource.
/// </summary>
public class NetFlixDataSource : IPagedDataSource<SyndicationItem>
{
  private string _searchString;
 
  private int _pageSize = 10;
 
  public NetFlixDataSource(string searchString)
  {
    _searchString = searchString;
  }
 
  public void FetchData(int pageNumber, Action<PagedDataResponse<SyndicationItem>> responseCallback)
  {
    WebClient client = new WebClient();
 
    // create a url for fetching movies with a name that contains the search string
    string url = string.Format("http://odata.netflix.com/Catalog/Titles?"
      + "$top={0}&$orderby=Name&$select=Name&$inlinecount=allpages&$skip={1}&$filter=indexof(Name,'{2}') ne -1",
      _pageSize, pageNumber * _pageSize, _searchString);
 
    client.DownloadStringCompleted += (s, e) =>
    {
      // parse the response using SyndaicationFeed
      XmlReader reader = XmlReader.Create(new StringReader(e.Result));
      SyndicationFeed feed = SyndicationFeed.Load(reader);
 
      // locate the item count 
      var itemCount = feed.ElementExtensions[0].GetObject<XElement>().Value;
 
      // invoke the response callback
      responseCallback(new PagedDataResponse<SyndicationItem>()
      {
        Items = feed.Items.ToList(),
        TotalItemCount = int.Parse(itemCount)
      });
    };
    client.DownloadStringAsync(new Uri(url));
  }
}

As you can see, this simple implementation of our paging data source constructs the required URL based on the search string and request page number. It also uses the ‘syndication’ APIs which are able to parse the response from the NetFlix OData APIs.

To test out this implementation of the paged search interface I have created a very simple movie search application. The view model for this application has a SearchString property, exposes the results as SearchResults property and has a single command which initiates a new search. The important parts of this class are shown below:

/// <summary>
/// Gets the paged search results
/// </summary>
public ServerSidePagedCollectionView<SyndicationItem> SearchResults
{
  get
  {
    return _searchResults;
  }
  private set
  {
    SetField(ref _searchResults, value, "SearchResults");
  }
}
 
/// <summary>
/// Gets a command which executes the search
/// </summary>
public ICommand ExecuteSearchCommand
{
  get
  {
    return new DelegateCommand(() =>
    {
      SearchResults = new ServerSidePagedCollectionView<SyndicationItem>(
        new NetFlixDataSource(SearchText));
    });
  }
}

When ExecuteSearchCommand is invoked, we create a new ServerSidePagedCollectionView, providing it with the NetFlix data source. From here on, the ServerSidePagedCollectionView is responsible for the paging of data.

The view is as follows:

<Grid x:Name="LayoutRoot" Background="LightGray"
      Width="400" Height="360">
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="Auto"/>
  </Grid.RowDefinitions>
 
  <StackPanel Orientation="Horizontal"
              Margin="4">
    <TextBox Text="{Binding SearchText, Mode=TwoWay}"
                Width="150"/>
    <Button Command="{Binding ExecuteSearchCommand}"
            Content=" Search Movies ..."
            Margin="5,0,0,0"/>
  </StackPanel>
 
  <ItemsControl ItemsSource="{Binding SearchResults}"
                Grid.Row="1"
                Height="300"
                ItemTemplate="{StaticResource MovieTemplate}">
    <ItemsControl.ItemsPanel>
      <ItemsPanelTemplate>
        <StackPanel Orientation="Vertical"/>
      </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
  </ItemsControl>
  <Rectangle Fill="White" Grid.Row="1"
            Opacity="0.5"
            Visibility="{Binding Path=SearchResults.CanChangePage, Converter={StaticResource BoolToVisibilityConverter}}"/>
 
  <sdk:DataPager  PageSize="10" 
                  Source="{Binding SearchResults}"
                  Grid.Row="2"/>
 
</Grid>

The DataPager simply binds to the SearchResults as does the ItemsControl. I have also added a Rectangle element which grays-out the current search results when a new page is being fetched.

You can see the application in action below:

So in summary, by implementing IPagedCollectionView with a pluggable datasource, IPagedDataSource, it is possible to very quickly and easily create an application that pages data from the server.

You can download the full sourcecode here: PagedCollectionViewExample.zip

Regards, Colin E.