Colin Eberhardt's Technology Adventures

Windows Phone 7 – Browsing your Photos via Bing Maps

January 16th, 2012

The Windows Phone 7 camera gives you the option to record the location where a picture was taken (under Settings => applications => pictures+camera). With this feature turned on, each application has their latitude, longitude and altitude stored as part of the standard EXIF data. I thought it would be fun to combine the previous blog post I wrote on pushpin clustering with the photos on my camera, to allow me to explore them via a Bing Maps control. With not much more than 100 lines of code I came up with an application which I think is a lot of fun to use.

Here are all the photos on my phone, note the way the pushpins are clustered.

Here are a few pictures I took in New York, of the One World Trade Centre and the Stock Exchange.

Here are some pictures around Europe, including one of Gergely Orosz waiting for his turn in the Edinburgh Marathon Relay.

And finally, some pictures I took whilst running around Kielder Water during Kielder marathon.

Accessing the EXIF data

You can access the photos on a WP7 device via the XNA MediaLibrary class. The interface that this class provides gives you access to Picture instances which have properties that allow you to access the width / height and a few other basic attributes. They also have methods that return streams which can be used to read the thumbnail and image data, however, they do not expose the picture location. This is ‘hidden’ within the EXIF data.

Fortunately there is a C# implementation of an EXIF decoder available on codeproject, which, with a few tweaks by Tim Heuer works just fine within Silverlight for Windows Phone 7.

With this library, accessing the EXIF data is a one-liner:

JpegInfo info = ExifReader.ReadJpeg(picture.GetImage(), picture.Name);

The JpegInfo class exposes the raw EXIF geolocation data, which is detailed in the EXIF specification as being expressed as separate components of degrees, minutes and seconds together with a reference direction (North / South, East / West). We can convert from the sexagesimal numeric system used in EXIF, to the decimal system as follows:

private static double DecodeLatitude(JpegInfo info)
{
  double degrees = ToDegrees(info.GpsLatitude);
  return info.GpsLatitudeRef == ExifGpsLatitudeRef.North ? degrees : -degrees;
}
 
private static double DecodeLongitude(JpegInfo info)
{
  double degrees = ToDegrees(info.GpsLongitude);
  return info.GpsLongitudeRef == ExifGpsLongitudeRef.East ? degrees : -degrees;
}
 
public static double ToDegrees(double[] data)
{
  return data[0] + data[1] / 60.0 + data[2] / (60.0 * 60.0);
}

Analysing the images

When the application starts a BackgroundWorker is used to read the EXIF data for all of the pictures in the phone’s media library, with those that have geolocation data available being stored in a separate list:

BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
 
// analyse the pictures that reside in the Media Library in a background thread
bw.DoWork += (s, e) =>
{
  var ml = new MediaLibrary();
 
  using (var pics = ml.Pictures)
  {
    int total = pics.Count;
    int index = 0;
    foreach (Picture picture in pics)
    {
      // read the EXIF data for this image
      JpegInfo info = ExifReader.ReadJpeg(picture.GetImage(), picture.Name);
 
      // check if we have co-ordinates
      if (info.GpsLatitude.First() != 0.0)
      {
        _images.Add(new LocatedImage()
        {
          Picture = picture,
          Lat = DecodeLatitude(info),
          Long = DecodeLongitude(info)
        });
      }
 
      // report progress back to the UI thread
      string progress = string.Format("{0} / {1}", index, total);
      bw.ReportProgress((index * 100 / total), progress);
 
      index++;
    }
  }
};
 
// update progress on the UI thread
bw.ProgressChanged += (s, e) =>
  {
    string title = (string)e.UserState;
    ApplicationTitle.Text = title;
  };
 
bw.RunWorkerAsync();
 
// when analysis is complete, add the pushpins
bw.RunWorkerCompleted += (s, e) =>
  {
    ApplicationTitle.Text = "";
    AddPushpins();
  };

When the pictures have all been analysed, a pushpin is created for each image which is then added to the clusterer described in my previous blog post.

private void AddPushpins()
{
  List<Pushpin> pushPins = new List<Pushpin>();
 
  // create a pushpin for each picture
  foreach (var image in _images)
  {
    Location location = new Location()
    {
      Latitude = image.Lat,
      Longitude = image.Long
    };
 
    Pushpin myPushpin = new Pushpin()
    {
      Location = location,
      DataContext = image,
      Content = image,
      ContentTemplate = this.Resources["MarkerTemplate"] as DataTemplate
    };
 
    pushPins.Add(myPushpin);
  }
 
  // add them to the map via a clusterer
  var clusterer = new PushpinClusterer(map, pushPins, this.Resources["ClusterTemplate"] as DataTemplate);
}

The template used for the pushpins simply renders the image thumbnail:

<DataTemplate x:Key="MarkerTemplate">
  <Border BorderBrush="White" BorderThickness="1">
    <Image Source="{Binding Picture, Converter={StaticResource PictureThumbnailConverter}}"
            Width="80" Height="80"/>
  </Border>
</DataTemplate>

This makes use of a simple value converter which takes a Picture instance and converts it into a BitmapImage which is used as the Source for the image:

public class PictureThumbnailConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    Picture picture = value as Picture;
    BitmapImage src = new BitmapImage();
    src.SetSource(picture.GetThumbnail());
    return src;
  }
 
  public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    return null;
  }
}

The puhspin clusterer allows you to specify a separate template for clustered pushpins. The DataContext for this template is a list of the DataContexts of the clustered pins that it represents. For this application I created a template which renders what looks like a ‘stack’ of images. The number of pictures in the cluster is rendered as a TextBlock and the last image in the cluster rendered.

<DataTemplate x:Key="ClusterTemplate">
  <Grid Width="75" Height="75">
    <Canvas>
      <Border Style="{StaticResource FakePhoto}"
              Canvas.Left="0" Canvas.Top="0"/>
      <Border Style="{StaticResource FakePhoto}"
              Canvas.Left="5" Canvas.Top="5"/>
      <Border BorderBrush="White" BorderThickness="1"
              Canvas.Left="10" Canvas.Top="10"
              DataContext="{Binding Path=., Converter={StaticResource LastConverter}}">
        <Image Source="{Binding Picture, Converter={StaticResource PictureThumbnailConverter}}"
                Width="60" Height="60"/>
      </Border>
      <TextBlock Text="{Binding Count}"
                  Opacity="0.5"
                  Canvas.Left="25" Canvas.Top="15"
                  FontSize="35"/>
    </Canvas>
  </Grid>      
</DataTemplate>
 
<Style TargetType="Border" x:Key="FakePhoto">
  <Setter Property="Width" Value="60"/>
  <Setter Property="Height" Value="60"/>
  <Setter Property="BorderBrush" Value="White"/>
  <Setter Property="Background" Value="Black"/>
  <Setter Property="BorderThickness" Value="1"/>
</Style>

The code that renders the last image is a bit cunning, it uses a value converter that performs a Linq style ‘last’ operations, extracting the last items from a collection of objects:

public class LastConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    IList enumerable = value as IList;
    return enumerable.Cast<object>().Last();
  }
 
  public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    return null;
  }
}

This feels quite neat to me :-)

The clustered pins look like the following, which is a cluster of 5 images around Paris, with the stunning La Grande Arche de la Défense as the image at the top of the cluster:

Despite its simplicity, I have had a lot of fun playing with this application. It has certainly encouraged me to take as many photos as possible whenever I go travelling.

You can download the full sourcecode here: PhotoBrowser.zip

Regards, Colin E.

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.

Metro In Motion Part #6 – Rolling List Location Indicator

June 6th, 2011

This blog post describes the development of a rolling list location indicator. This indicator mirrors the behaviour seen in the native Windows Phone 7 calendar which rolls from one date to the next as the user scrolls.

For those of you who have not been following my Metro-In-Motion series, I’ll briefly recap. My aim is to provide an implementation of the ‘fluid’ UI transitions and effects seen in native Windows Phone 7 applications but absent from the Silverlight APIs. So far I have covered fluid list animations, ‘peel’ animations, flying titles , a ’tilt’ effect and finally SandwichFlow which brought all these effects together and the series to a close. However, a recent StackOverflow questions inspired me to implement another fluid UI effect found in the native calendar application. When scrolling your list of appointments, the small day indicator at the top of the page displays the current date, with graceful roll transitions as you move from day-to-day:

It’s a subtle but the effect is very pleasing!

You can see my implementation of this effect in the video below:

In order to create this indicator, we need to determine the item that is currently at the top of the list while it is being scrolled, and the direction of scrolling. Once we have this data at our disposal, the rest is all just visualisation!

Finding the head of the list

The Silverlight ListBox and ItemsControl do not expose a property which indicates the first visible item, so we need to add this functionality. Adding the properties themselves is simply a matter of defining a FirstVisibleItem and a IsScrollingUpwards attached properties. The logic that computes these properties is a little more complicated!

I have created a boolean ExposeFirstVisibleItem attached property (which acts as an attached behaviour). When this property is set to true, we navigate the visual tree to locate the vertical scrollbar that is located within the ScrollViewer which is part of the ListBox template.

The changed event handler for this attached property is shown below:

private static void OnExposeFirstVisibleItemPropertyChanged(DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
  if (e.NewValue.Equals(true))
  {
    ItemsControl itemsControl = d as ItemsControl;
 
    // wire up the scrollbar, handling ValueChanged events
    if (!WireUpScrollbar(itemsControl))
    {
      // if wire-up fails, try again on LayoutUpdated
      EventHandler tryFindScrollBar = null;
      tryFindScrollBar = (s2, e2) =>
      {
        if (WireUpScrollbar(itemsControl))
        {
          itemsControl.LayoutUpdated -= tryFindScrollBar;
        }
      };
 
      itemsControl.LayoutUpdated += tryFindScrollBar;
    }
  }
}

Note, the method WireUpScrollbar returns true if it has located the scrollbar. However, if the template for our ListBox has not yet been instantiated this will fail. In this case, we handle the LayoutUpdated event and retry until this method returns a success. Note, the EventHandler that removes its own subscription to the LayoutUpdated event, a neat pattern that I will certainly use again!

The WireUpScrollbar method uses Linq-to-VisualTree to locate the ScrollBar.

private static bool WireUpScrollbar(ItemsControl itemsControl)
{
  var sb = itemsControl.Descendants<ScrollBar>()
                      .Cast<ScrollBar>()
                      .Where(s => s.Orientation == Orientation.Vertical)
                      .SingleOrDefault() as ScrollBar;
 
  if (sb == null)
    return false;
 
  // set to the initial value
  SetFirstVisibleItem(itemsControl);
  sb.Tag = sb.Value;
 
  // update value on scroll ...
  sb.ValueChanged += (s, e2) =>
  {
    SetFirstVisibleItem(itemsControl);
    SetIsScrollingUpwards(itemsControl, sb.Value < (double)sb.Tag);
 
    // store the previous scroll position in the Tag
    sb.Tag = sb.Value;
  };
 
  return true;
}

The ScrollBar.Tag is used to store the previous scroll location so that we can determine the scroll direction, with SetIsScrollingUpwards setting the attached property on our ListBox (or ItemsControl).

Finally SetFirstVisibleItem locates the first visible item within the list and updated the FirstVisibleItem attached property:

private static void SetFirstVisibleItem(ItemsControl itemsControl)
{
  itemsControl.Dispatcher.BeginInvoke(() =>
  {
    var item = itemsControl.GetItemsInView().First();
    ListUtils.SetFirstVisibleItem(itemsControl, item.DataContext);
  });
}

Note the GetItemsInView extension method which enumerates the visible items. This was implemented in an earlier Metro-In-Motion blog post, however I have extended this implementation to support both virtualizing and non-virtualizing panels, you can see the latest version on codeplex within the Windows Phone 7 Contrib (WP7Contrib) project. The FirstVisibleItem exposes the DataContext of the first visible item, which will be the first visible model object.

Visualising the top item

With the above code, we simply set the following attached property to true on a ListBox or ItemsControl in order for it to expose teh first visible item and scroll direction:

<ListBox mim:ListUtils.ExposeFirstVisibleItem="true" />

The rollover effect seen in the native control is very easy to reproduce with the Silverlight Toolkit TransitioningContentControl, which has proven popular with Silverlight developers wishing to create page transitions. The only problem is that this control has not made it into the WP7 version of the toolkit. So I simply grabbed the source of and placed it directly into my project.

If we look at the calendar example that is shown in the video at the start of this blog post, it has the following XAML:

<StackPanel x:Name="TitlePanel" Grid.Row="0">
 
  <layout:TransitioningContentControl
                Content="{Binding Path=(mim:ListUtils.FirstVisibleItem).Time,
                                   ElementName=list,
                                   Converter={StaticResource StringFormatConverter},
                                   ConverterParameter='dd MMM yy'}"
                Transition="{Binding Path=(mim:ListUtils.IsScrollingUpwards),
                                   ElementName=list,
                                   Converter={StaticResource BooleanToTransitionConverter}}">
    <layout:TransitioningContentControl.ContentTemplate>
      <DataTemplate>
        <TextBlock Text="{Binding}"
                        FontSize="60"/>
      </DataTemplate>
    </layout:TransitioningContentControl.ContentTemplate>
  </layout:TransitioningContentControl>
</StackPanel>
 
<Grid x:Name="ContentPanel" Grid.Row="1" >
  <ListBox ItemsSource="{Binding}"
            x:Name="list"
            mim:ListUtils.ExposeFirstVisibleItem="true">
    <!-- template goes here -->
  </ListBox>
</Grid>

Which gives the following layout:

The Content property of our TransitioningContentControl uses ElementName binding and a property path of (mim:ListUtils.FirstVisibleItem).Time, in order to bind to the Time property of the first visible model object. Note that the list contains multiple items that are on the same day but have different times. The Converter associated with this binding (which is a simple implementation of the Silverlight 4 StringFormat binding property), uses the string 'dd MMM yy', which will give the same value for all appointments on the same day. Therefore the content of the TransitioningContentControl control will only change as we navigate between day boundaries.

The TransitioningContentControl already has suitable transitions which give a nice roll-up and roll-down effect. These are selected by setting the Transition property to the named transition. In the example above, this is bound to the attached boolean IsScrollingUpwards property of our list, with a simple value converter that converts this into the required string transition identifier.

And we’re done!

I quite like the way that the implementation of this effect is split into two halves, the one which extends the functionality of the list, and the other which visualises the output. This should give great flexibility, as can be seen in the example which indicates the list location within a contacts list.

This code also works with the Jump List I created a few months ago, however, it did require a few changes to that code. My current plan is to move all of the WP7 controls and effects I have created into the WP7Contrib project, so watch this (or that) space!).

You can download the sourcecode for this blog here: MetroInMotionSix.zip

Regards,
Colin E.

Metro In Motion #5 – SandwichFlow

May 16th, 2011

For the past few months I have been writing a Metro-In-Motion blog series which describes how to recreate some of the fluid effects found in native Windows Phone 7 applications within your own applications. So far I have covered fluid list animations, ‘peel’ animations, flying titles and a ’tilt’ effect – and now … I am all out of ideas!

The previous blog posts used a pretty simple little application to demonstrate their functionality, so I thought it might be fun to put these effects together within a more meaningful application. Rather than simply re-hashing an old idea, like a Twitter app, I decided to create something original, something inspiring, something which explores a subject area close to my heart – and stomache. This is how I came up with the (entirely original) SandwichFlow … an application for the sandwich aficionado!!!

SandwichFlow – As good as sandwiches get on a mobile … Brought to you by Team Distraction and ColinDoesIt(AsWell):

Random Points Of Interest

The important bits of this application are covered in the previous Metro-In_motion blog posts, so the following is just a random bunch of things that your might find interesting about this application:

Recipe Thief

The recipes were scraped from the fantastic BBC Food web-pages, which are published in XHTML format, making it easy for me to grab the various component parts of each recipe. The scraped output was saved as an XML file which the application uses to build a view model using Linq to XML:

XDocument doc = XDocument.Parse(xml);
Sandwiches = doc.Descendants("sandwich")
                .Select(el => new Sandwich()
                {
                  Title = el.Attribute("title").Value,
                  Id = id++,
                  Keywords = el.Descendants("keyword")
                                .Select(l => Regex.Replace(l.Value, @"\b(\w)", m => m.Value.ToUpper()))                                      
                                .ToList(),
                  Ingredients = el.Descendants("ingredient")
                                  .Select(l => l.Value)
                                  .ToList(),
                  Instructions = el.Descendants("instruction")
                                  .Select(l => new InstructionStep()
                                  {
                                    Text = l.Value,
                                    Step = l.ElementsBeforeSelf().Count() + 1
                                  })
                                  .ToList()
 
                })
                .ToList();

Jump then slide

The front-page of the application uses the JumpList control I wrote a few months back, with DataTemplates supplied for rending recipes or keywords that have the MetroInMotion.AnimationLevel property set on various elements so that they slide gracefully as the pivot control is animated from one item to the next:

<DataTemplate x:Key="SandwichTemplate">
  <StackPanel Orientation="Vertical"
              Margin="0,5,0,5"
              mim:MetroInMotion.Tilt="3">
    <TextBlock  Text="{Binding Title}"
                mim:MetroInMotion.AnimationLevel="1"
                x:Name="Title"
                Foreground="Black"
                FontSize="{StaticResource PhoneFontSizeLarge}"/>
    <TextBlock Text="{Binding KeywordSummary}"
                mim:MetroInMotion.AnimationLevel="2"
                FontSize="{StaticResource PhoneFontSizeNormal}"
                Foreground="#888"/>
  </StackPanel>
</DataTemplate>

Bring it back

I originally added the background image to each page, then I realised a better place to put this is the application frame:

<Style x:Key="mainFrameStyle"
    TargetType="phone:PhoneApplicationFrame">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="phone:PhoneApplicationFrame">
        <Grid Background="White">
          <!-- background image -->
          <Image Source="background.jpg"
                HorizontalAlignment="Stretch"
                VerticalAlignment="Stretch"/>
          <Border x:Name="ClientArea"
                  BorderBrush="{TemplateBinding BorderBrush}"
                  BorderThickness="{TemplateBinding BorderThickness}"
                  Background="{TemplateBinding Background}"
                  HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
                  VerticalAlignment="{TemplateBinding VerticalAlignment}">
            <ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}"
                              Content="{TemplateBinding Content}"
                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                              Margin="{TemplateBinding Padding}"
                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
          </Border>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

The application frame remains throughout the application lifetime, it hosts the currently displayed page. I have a feeling that the application frame is a little under-used. Rather than adding your application title to each page, why not add it to the frame?

The same but different

The code to find similar sandwiches uses an interesting Linq method which I have not used before, Intersect. It is always good to learn something new!

// locate related sandwiches
foreach (var sandwich in Sandwiches)
{
  sandwich.Similar = Sandwiches.Where(s => s != sandwich)
                      .Select(s =>
                        new
                        {
                          Sandwich = s,
                          Score = KeywordCorrelation(s.Keywords, sandwich.Keywords)
                        })
                      .OrderBy(s => s.Score).Reverse()
                      .Take(5)
                      .Select(s => s.Sandwich)
                      .ToList();
}
 
private double KeywordCorrelation(List<string> keywordsOne, List<string> keywordsTwo)
{
  var commonKeywords = keywordsOne.Intersect(keywordsTwo);
  var allKeywords = keywordsOne.Union(keywordsTwo).Distinct();
  return (double)commonKeywords.Count() / (double)allKeywords.Count();
}

The KeywordCorrelation methods finds the number of keywords that both sandwiches have in common, then divides this by the distinct collection of keywords for both sandwiches. Yummy.

Slippery Ingredients

When you view a recipe, the ingredients slide in from the right. I didn’t really have time to find images for all of the ingredients on the web, so each recipe has the ingredients of my favourite – the cheese and pickle sandwich. Each ingredient slides in using a storyboard, with their BeginTime staggered:

<Image Source="tom.png"
        Margin="150,70,0,0"
        mim:MetroInMotion.AnimationLevel="2">
  <Image.RenderTransform>
    <TranslateTransform x:Name="tomTrans"
                          X="400"/>
  </Image.RenderTransform>
  <Image.Triggers>
    <EventTrigger RoutedEvent="Image.Loaded">
      <BeginStoryboard>
        <Storyboard BeginTime="00:00:0.2">
          <DoubleAnimation Duration="00:00:0.7"
                            Storyboard.TargetName="tomTrans" 
                            Storyboard.TargetProperty="X" 
                            From="400" To="0">
            <DoubleAnimation.EasingFunction>
              <SineEase EasingMode="EaseOut"/>
            </DoubleAnimation.EasingFunction>
          </DoubleAnimation>
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Image.Triggers>
</Image>

They also have the MetroInMotion.AnimationLevel attached property set so that they slide with the pivot control.

You can grab the code here: SandwichFlow.zip

Regards, Colin E.