Showing posts with label Tombstoning. Show all posts
Showing posts with label Tombstoning. Show all posts

08 April 2011

Bing Maps control with MVVMLight on Windows Phone 7

This article appeared originally in the March 2011 issue of .Net Magazine – in Dutch. By popular request I wrote this English translation. Regular readers of this blog may recognize parts of it ;-)

A POWERFUL CONTROL WITH A ROBUST ARCHITECTURE

Not everyone is aware of it yet, but MVVM is becoming the de facto standard for all XAML driven environments – WPF, Silverlight, Microsoft Surface and Windows Phone 7. This article shows how this pattern can be applied and how it can make complex tasks like tombstoning easier. A quite unusual approach is used – driving the Bing Maps control

MVVM – theory

MVVM stands for Model-View-Viewmodel. Model (business classes) and View (XAML) are connected by a ViewModel – which was dubbed ‘basically a value convertor on steroids’ by Josh Smith. The ViewModel is a class that provides properties from the Model in such a way that the contents can be displayed using data binding.

Since data binding can work two-way, a change in the ViewModel is automatically displayed in the View, and a change in the View (on user input) is automatically populated to the model. The View can also bind to commands: these are methods in the ViewModel called as a result of an event in the View (for example, a button is pressed), and that in some way manipulate the underlying Model. In theory the companion ‘code behind’ files to the XAML are completely empty. This approach has two distinct advantages:

  1. The ViewModel can be unit-tested, something that’s next to impossible when the logic is put in the code behind in the traditional way.
  2. The View itself – the XAML – is totally devoid of code. Therefore a designer, who is not interested in code, can happily style the and adapt the View without breaking functionality.

MVVM – in practice, on Windows Phone 7

Windows Phone 7 – based on Silverlight 3 – poses some challenges. Property binding is a breeze, but command binding is not – for the simple reason that the classes supporting command binding are missing. Another challenge is the fact that Windows Phone 7 applications can be interrupted at any moment – because the user hits the ‘Start’ button, for instance. When this occurs, the application state must be preserved – ‘tombstoned’.

CodePlex host various frameworks that enable MVVM support in Silverlight and Windows Phone 7. Two of the most well-known are (in no particular order) MVVMLight by Laurent Bugnion and Caliburn Micro by Rob Eisenberg. Both have their distinct advantages and disadvantages, both have their ardent advocates. In this article MVVMLight is used.

In the next sample all classes are put together in one project – in practice certain classes will get their own assemblies. But the purpose of this article is to show the idea behind using MVVM.

Base application

designedappThe way Bing Maps and MVVMLight can act together will be demonstrated using a simple “MapBindingDemo” app. This consists of a Bing Maps control, two buttons to select map types, and a TextBlock to display the name of the selected map. Map manipulation by using pinch zoom, pan and double tap are all included for free, courtesy of the Bing Maps control. The easiest way to get started is to create an empty “Windows Phone Application” in Visual Studio 2010. Then open this application in Blend, enter “Map” in the Assets box and drag the map on the design pane. Add controls until the result looks like displayed on the the right.

 

 

In XAML, the result should look like this:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"/>
<Grid Grid.Row="1">
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="0.167*"/>
    <ColumnDefinition Width="0.667*"/>
    <ColumnDefinition Width="0.167*"/>
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition Height="0.13*"/>
    <RowDefinition Height="0.87*"/>
  </Grid.RowDefinitions>
  <Microsoft_Phone_Controls_Maps:Map Grid.Row="1" Grid.ColumnSpan="3"/>
  <Button Content="<"/>
  <Button Content=">" Grid.Column="2"/>
  <TextBlock Grid.Column="1" TextWrapping="Wrap" Text="TextBlock" 
    Margin="0,23,0,0" HorizontalAlignment="Center"/>
</Grid>

Those who are stubborn enough to venture here without using Blend: the project should have a reference to Windows.Controls.Phone.Maps and the namespace declarations of the PhoneApplicationPage should include the following declarations:

xmlns:Microsoft_Phone_Controls_Maps=
   "clr-namespace:Microsoft.Phone.Controls.Maps;assembly=Microsoft.Phone.Controls.Maps"

Another thing to take into consideration is the fact that Bing Maps Control usages requires credentials. These can be created using this website.The credentials should be included in the Map Control’s CredentialsProvider property. The Uri the site asks for does not matter – that’s only important when the Bing Maps control is used in a web site context, which is obviously not the case in a Windows Phone 7 app.

Bing Maps Control and TileSource

The Bing Maps Control is pretty flexible and powerful, and can show both vector and raster maps. This example concentrates on the latter. It’s important to know the control has three major settings as far as raster maps are concerned: “Road”,  “Aerial” and the least known – “Mercator”. When Mercator is set, the control won’t download any map data by itself – the developer should supply the logic in a class implementing Microsoft.Phone.Controls.Maps.TileSource. Only the GetUri method needs to be overridden. The Bing Maps controls supplies mapclassesthree parameters to this method: x, y and zoomlevel. At level 1, the world is a square of 2x2=4 tiles of 256x256 pixels each. On level 2, there are 16 tiles, on level 3 there are 64, etc. The method GetUri should convert these three numbers into an Uri leading to a suitable image. For geo nuts like me a piece of cake, for the rest of the world possible a load of gobbledygook. This is by no means a problem when trying to understand MVVM - the TileSource code is all included in this article. The application sports a folder "Maps" which will include this code.

In this article, the code for the various TileSources is showed in a single code file to give an easier overview. In real-world development, only code generators put multiple classes in one file, eh?

using System;
using Microsoft.Phone.Controls.Maps;

namespace MapBindingDemo.Maps
{
  public abstract class BaseTileSource : TileSource, 
    IEquatable<BaseTileSource>
  {
    public string Name { get; set; }


    public bool Equals(BaseTileSource other)
    {
      return other != null && other.Name.Equals(Name);
    }

    public override bool Equals(object obj)
    {
      return Equals(obj as BaseTileSource);
    }
  }

//------------------------

  public abstract class BaseBingSource : BaseTileSource
  {
    private static string TileXYToQuadKey(int tileX, int tileY, int levelOfDetail)
    {
      var quadKey = new StringBuilder();
      for (var i = levelOfDetail; i > 0; i--)
      {
        char digit = '0';
        int mask = 1 << (i - 1);
        if ((tileX & mask) != 0) digit++;
        if ((tileY & mask) != 0)
        {
          digit++;
          digit++;
        }
        quadKey.Append(digit);
      }
      return quadKey.ToString();
    }

    public override Uri GetUri(int x, int y, int zoomLevel)
    {
      if (zoomLevel > 0)
      {
        string quadKey = TileXYToQuadKey(x, y, zoomLevel);
        string veLink = string.Format(UriFormat,
           new object[] { quadKey[quadKey.Length - 1], quadKey });
        return new Uri(veLink);
      }
      return null;
    }
  }
  
//------------------------  
  
  public class BingRoad : BaseBingSource
  {
    public BingRoad()
    {
      UriFormat = "http://r{0}.ortho.tiles.virtualearth.net/tiles/r{1}.png?g=203";
    }
  }
  
//------------------------  

  public class BingAerial : BaseBingSource
  {
    public BingAerial()
    {
      UriFormat = "http://h{0}.ortho.tiles.virtualearth.net/tiles/h{1}.jpeg?g=203";
    }
  }

//------------------------

  public class OsmaRender : BaseTileSource
  {
    public OsmaRender()
    {
      UriFormat = "http://{0}.tah.openstreetmap.org/Tiles/tile/{1}/{2}/{3}.png";
    }

    private readonly static string[] TilePathPrefixes = 
      new[] { "a", "b", "c", "d", "e", "f" };

    public override Uri GetUri(int x, int y, int zoomLevel)
    {
      if (zoomLevel > 0)
      {       
        var url = string.Format(UriFormat, 
          TilePathPrefixes[(y%3) + (3*(x%2))], zoomLevel, x, y);
        return new Uri(url);
      }
      return null;
    }
  }
  
//------------------------  

  public class Mapnik : BaseTileSource
  {
    public Mapnik()
    {
      UriFormat = "http://{0}.tile.openstreetmap.org/{1}/{2}/{3}.png";
    }

    private readonly static string[] TilePathPrefixes = new[] { "a", "b", "c" };

    public override Uri GetUri(int x, int y, int zoomLevel)
    {
      if (zoomLevel > 0)
      {
        var url = string.Format(UriFormat, TilePathPrefixes[y%3], zoomLevel, x, y);
        return new Uri(url);
      }
      return null;
    }
  }
  
//------------------------  

  public enum GoogleType
  {
    Street     ='m',
    Hybrid     ='y',
    Satellite    ='s',
    Physical     ='t',
    PhysicalHybrid ='p',
    StreetOverlay  ='h',
    WaterOverlay   ='r'
  }

//------------------------  
  
  public class Google : BaseTileSource
  {
    public Google()
    {
      MapType = GoogleType.PhysicalHybrid;
      UriFormat = @"http://mt{0}.google.com/vt/lyrs={1}&z={2}&x={3}&y={4}";
    }

    public GoogleType MapType { get; set; }

    public override Uri GetUri(int x, int y, int zoomLevel)
    {
      return new Uri( 
        string.Format(UriFormat, (x % 2) + (2 * (y % 2)),
        (char)MapType, zoomLevel, x, y) );
    }
  } 
}

The starting point is a base class “BaseTileSource” which only adds a Name property and an Equals method that compares by name – a quite naïve implementation, but sufficient for this purpose. A subclass “BaseBingSource” contains most of the heavy lifting for the Bing Maps calculations, so two pretty simple subclasses “BingAerial” and “BingRoad” are all that’s needed to implement Bing Aerial and Bing Road. So far the net result is a quite complicated way to get exactly the same result as the standard Bing Maps Control, but the fun starts when two well-known open source OpenStreetMap mapservers are added – OsmaRender and Mapnik. And of course my personal favorite ‘nag the competition’ option: Google Maps.

As stated before, the Bing Maps Control should operate in Mercator mode. The way to do this is described below:

<Microsoft_Phone_Controls_Maps:Map                 
    CredentialsProvider="your_credentials_here">               
    <Microsoft_Phone_Controls_Maps:Map.Mode>
        <MSPCMCore:MercatorMode/>
    </Microsoft_Phone_Controls_Maps:Map.Mode>
</Microsoft_Phone_Controls_Maps:Map>

The namespace “MSPCMCore” is declared in the PhoneApplicationPage tag like this:

"clr-namespace:Microsoft.Phone.Controls.Maps.Core;assembly=Microsoft.Phone.Controls.Maps"

Setup MVVMLight

Solution1The framework itself contains of two assemblies: GalaSoft.MvvmLight.WP7.dll andGalaSoft.MvvmLight.Extras.WP7.dll. These can be downloaded from CodePlex. Apart from those, the assembly System.Windows.Interactivity.dll is necessary as well – this can be found in the Expression Blend SDK and is locaties either in
<drive>:\Program Files\Microsoft SDKs\Expression\Blend\Windows Phone\v7.0\Libraries
or in
<drive>:\Program Files (x86)\Microsoft SDKs\Expression\Blend\Windows Phone\v7.0\Libraries
when you have a 64 bit OS, which is becoming ever more commonplace these days. It’s good practice to put these assemblies in a solution folder to make sure they remain associated with the solution – it makes working with multiple developers on a single Windows Phone 7 project utilizing a source control mechanism a lot easier. In this sample a folder “Binaries” is used to that effect. The project needs to have a reference to the three assemblies mentioned above, and the solution now should look like displayed to the right.

Map app ViewModel

An MVVMLight ViewModel is a child class of the most originally named “ViewModelBase” class. A number of properties are defined within this application’s ViewModel: ZoomLevel, MapCenter, AvailableMaps and CurrentMap. In addition, it has a number of commands: one for moving a map definition ahead, and one for moving back. The ViewModel looks like this:

using System.Collections.Generic;
using System.Device.Location;
using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using MapBindingDemo.Maps;
using MapBindingDemo.Serialization;

namespace MapBindingDemo.ViewModel
{
  public class MvvmMap : ViewModelBase
  {
    public MvvmMap()
    {
      _availableMapSources = new List<BaseTileSource> 
      {
        new BingAerial{ Name = "Bing Aerial"},
        new BingRoad {Name = "Bing Road"},
        new Mapnik {Name = "OSM Mapnik"},
        new OsmaRender {Name = "OsmaRender"},
        new Google {Name = "Google Hybrid", MapType = GoogleType.Hybrid},
        new Google {Name = "Google Street", MapType = GoogleType.Street},
      };
    }

    private GeoCoordinate _mapCenter;
    public GeoCoordinate MapCenter
    {
      get { return _mapCenter; }
      set
      {
        if (_mapCenter == value) return;
        _mapCenter = value;
        RaisePropertyChanged("MapCenter");
      }
    }

    private double _zoomLevel;
    public double ZoomLevel
    {
      get
      {
        return _zoomLevel;
      }
      set
      {
        if (value == _zoomLevel) return;
        if (value >= 1)
        {
          _zoomLevel = value;
        }
        RaisePropertyChanged("ZoomLevel");
      }
    }

    private BaseTileSource _currentMap;
    public  BaseTileSource CurrentMap
    {
      get
      {
        if (_currentMap == null && 
          _availableMapSources != null && 
          _availableMapSources.Count > 0)
        {
          _currentMap = _availableMapSources[0];
        }
        return _currentMap;
      }
      set
      {
        if (value.Equals(CurrentMap)) return;
        {
          _currentMap = value;
        }
        RaisePropertyChanged("CurrentMap");
      }
    }

    private List<BaseTileSource> _availableMapSources;
    [DoNotSerialize]
    public List<BaseTileSource> AvailableMapSources
    {
      get
      {
        return _availableMapSources;
      }
      set
      {
        _availableMapSources = value;
        RaisePropertyChanged("AvailableMapSources");
      }
    }

    public ICommand NextMap
    {
      get
      {
        return new RelayCommand(() =>
        {
          var newIdx = AvailableMapSources.IndexOf(CurrentMap) + 1 ;
          CurrentMap = 
            AvailableMapSources[newIdx > AvailableMapSources.Count - 1? 0 : newIdx];
        });
      }
    }

    public ICommand PreviousMap
    {
      get
      {
        return new RelayCommand(() =>
        {
          var newIdx = AvailableMapSources.IndexOf(CurrentMap) -1;
          CurrentMap = 
            AvailableMapSources[newIdx < 0 ? 
             AvailableMapSources.Count - 1 : newIdx];
        });
      }
    }
     
    private static MvvmMap _instance;
    public static MvvmMap Instance
    {
      get { return _instance; }
      set { _instance = value; }
    }

    public static void CreateNew()
    {
      _instance = new MvvmMap();
    }
  }
}

Hooking up ViewModel and user interface using Blend

dataUsing databinding, ZoomLevel and MapCenter can be hooked up without any special measures to a Bing Maps control, while the TextBlock “TextBlock” should display the map name. The easiest way to accomplish this is using Expression Blend. A free version for Windows Phone is included with the Windows Phone 7 tools. When Blend has finished loading your projects, notice the three tabs to the upper right: "Properties", "Resources" and "Data". Click the encircled symbol and a menu appears, which contains the option “Create Object Data Source. When this is selected, a popup appears with all the objects in the solution. Click MapBindingDemo, then MapBindingDemo.ViewModel, and finally MvvmMap. To the right, the properties of MvvmMap are displayed. Drag the “Instance” property to the Grid “LayoutRoot” in the “Objects and Timeline” panel at the left side. Blend shows the text “Data bind LayoutRoot.DataContext to Instance”. Release the mouse button and – done. Data binding.

At the right bottom, a panel “Data context” has appeared. Drag property “Name” from “CurrentMap” on top of the TextBlock. The contents of the TextBlock immediately changes to “Bing Aerial”. And that’s correct, since the ViewModel code shows that when no map is selected, the first map in “AvailableMaps” should be selected, which is indeed Bing Aerial.

ObjectsAndTimeLineGo back to the “Objects and Timeline” panel, and select the object “Map”. To get there, you will first need to expand “Layoutroot” and “Grid”. Data bind the “MapCenter” property of the ViewModel to the “Center” property of the map by dragging MapCenter on top of the Bing Maps controls – a popup appears, in which you can select the “Center” property.

For some strange reason I could not get Blend to databind “ZoomLevel” this way – it only wants to bind to “Content”. To work around this: select advancedoptionstab “Properties”. Then expand “Miscellaneous”. All the way down you will see the “ZoomLevel” property. Right next to it is a little square: click this, and this will yield and menu which contains the option “Data Binding”. Select this, and then select property “ZoomLevel” of the model in the popup that follows.

Now switch to the XAML view, and notice that in the Bing Maps Control Center and Zoomlevel properties are bound: Center="{Binding MapCenter}" ZoomLevel="{Binding ZoomLevel}". To make the binding function properly, change this to Center="{Binding MapCenter, Mode=TwoWay}" ZoomLevel="{Binding ZoomLevel, Mode=TwoWay}”. This can be done from Blend as well, but this way is quicker.

Databinding command utilizes the MVVMLight EventToCommand behavior. Type “Event” in the “Search” box top left and the EventToCommand behavior appears. Drag this on both the “<” and the “>” button. Then drag “PreviousCommand” from the ViewModel on top of the EventToCommand below the “<” button (inthe Objects and Timeline panel). “NextCommand” is databound in the same way to the “>” button. Hit F5 in Blend, the emulator appears and when “>” and “<” the text in the TextBlock should change. But the map does not play along – even more, there is absolutely no map visible. That’s because there’s no TileSource associated with it.

Attached Dependency Properties – plumbing holes in data binding

Binding a TileSource to a Bing Maps control is not possible, because there’s no fitting property. Fortunately XAML includes attached dependency properties – these can be regarded as the property equivalent of extension methods. Back to Visual Studio, where an attached dependency property will be created in the folder “ViewModel”

using System.Windows;
using MapBindingDemo.Maps;
using Microsoft.Phone.Controls.Maps;

namespace MapBindingDemo.ViewModel
{
  public static class BindingHelpers
  {
    //Used for binding a single TileSource object to a Bing Maps control
    #region TileSourceProperty

    public static readonly DependencyProperty TileSourceProperty =
      DependencyProperty.RegisterAttached("TileSource", typeof(TileSource),
      typeof(BindingHelpers), new PropertyMetadata(SetTileSourceCallback));

    // Called when TileSource is retrieved
    public static TileSource GetTileSource(DependencyObject obj)
    {
      return obj.GetValue(TileSourceProperty) as TileSource;
    }

    // Called when TileSource is set
    public static void SetTileSource(DependencyObject obj, TileSource value)
    {
      obj.SetValue(TileSourceProperty, value);
    }

    //Called when TileSource is set
    private static void SetTileSourceCallback(object sender, 
      DependencyPropertyChangedEventArgs args)
    {
      var map = sender as Map;
      var newSource = args.NewValue as TileSource;
      if (newSource == null || map == null) return;

      // Remove existing layer(s)
      for (var i = map.Children.Count - 1; i >= 0; i--)
      {
        var tileLayer = map.Children[i] as MapTileLayer;
        if (tileLayer != null)
        {
          map.Children.RemoveAt(i);
        }
      }
      
      var newLayer = new MapTileLayer();
      newLayer.TileSources.Add(newSource);
      map.Children.Add(newLayer);
    }
    #endregion
  }
}

This quite adequately shows why an attached dependency property is necessary: a Bing Maps controls has Children that can contain (among others) MapTileLayer objects – and only that contains a TileSource. So when the displayed map is changed, first all current MapTileLayer objects need to be deleted, before a new MapTileLayer containing the new Tilesource is created, that can be added to the Children of the map. With this in place, the whole ViewModel can be databound to the Bing Maps Control. Unfortunately not with Blend, so this will have to go manually, in XAML. Add the namespace in which the BindingHelper class is situated to the namespace declarations at the top of the PhoneApplicationPage:

xmlns:MapBindingDemo_ViewModel="clr-namespace:MapBindingDemo.ViewModel"

Now the actual binding to the map needs to take place. On the same spot where ZoomLevel and MapCenter were bound the attached dependency property is bound:

<Microsoft_Phone_Controls_Maps:Map x:Name="Map" Grid.Row="1" Grid.ColumnSpan="3"             
  CredentialsProvider="your_credentials_here" 
  Center="{Binding MapCenter, Mode=TwoWay}" 
  ZoomLevel="{Binding ZoomLevel, Mode=TwoWay}" 
  MapBindingDemo_ViewModel:BindingHelpers.TileSource ="{Binding CurrentMap}">

Now the application is fully functional, even in design mode it shows the default map (Bing Aerial). The buttons left and right of the map name change the map that’s displayed – with not a single line of code in the code behind. Granted, the user experience and look and feel could benefit from some more attention, but nevertheless – mission accomplished. Well, almost. Zoom in a little, select a map that not the default, hit the start button and then hit back. Alas – the default map again, with full world view. The application’s last state is not preserved,  or to put things differently – it does not support tombstoning.

Tombstoning an MVVMLight application

Mike Talbot has written a brilliant post in which he describes a helper object called “SilverlightSerializer”. This object can serialize virtually every Silverlight and Windows Phone 7 object as a binary. The fun part is that it even serializes non-serializable objects – like MVVMLight’s ViewModelBase. The blog post is called “Silverlight Binary Serialization” and includes the source code.

SilverlightSerializer is added to a folder “Serialization” in the project. In the same folder (and namespace) a file “ApplicationExtensions.cs” is added that implements two extension methods on the Application class: one for writing the entire ViewModel to Isolated Storage, and one for reading it back from IS.

using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows;
using GalaSoft.MvvmLight;

namespace MapBindingDemo.Serialization
{
  /// <summary>
  /// Some extensions method that allow serializing and deserializing
  /// a model to isolated storage
  /// </summary>
  public static class ApplicationExtensions
  {
    private static string GetDataFileName( Type t)
    {
      return string.Concat(t.Name, ".dat");
    }

    public static T RetrieveFromIsolatedStorage<T>(this Application app) 
      where T : class
    {
      using (var appStorage = IsolatedStorageFile.GetUserStoreForApplication())
      {
        var dataFileName = GetDataFileName(typeof(T));
        if (appStorage.FileExists(dataFileName))
        {
          using (var iss = appStorage.OpenFile(dataFileName, FileMode.Open))
          {
            try
            {
              return SilverlightSerializer.Deserialize(iss) as T;
            }
            catch (Exception e)
            {
              System.Diagnostics.Debug.WriteLine(e);
            }
          }
        }
      }
      return null;
    }

    public static void SaveToIsolatedStorage(this Application app, 
      ViewModelBase model)
    {
      var dataFileName = GetDataFileName((model.GetType()));
      using (var appStorage = IsolatedStorageFile.GetUserStoreForApplication())
      {
        if (appStorage.FileExists(dataFileName))
        {
          appStorage.DeleteFile(dataFileName);
        }
        using (var iss = appStorage.CreateFile(dataFileName))
        {
           SilverlightSerializer.Serialize(model,iss);          
        }
      }
    }
  }
}

These methods are used from App.Xaml’s code behind. This may look like a deviation from the MVVM paradigm, but it’s not as bad as it looks. This file has some default code anyway (the Application_* methods), for this is the place that sports the methods that are called when the application (re)starts or stops. This last step just adds a little code. First, add two usings:

using MapBindingDemo.Serialization;
using MapBindingDemo.ViewModel;

Then, adapt the existing code till it looks like this:

// Code to execute when the application is launching (eg, from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{  
  LoadModelFromIsolatedStorage();
}

// Code to execute when the application is activated (brought to foreground)
// This code will not execute when the application is first launched
private void Application_Activated(object sender, ActivatedEventArgs e)
{
  LoadModelFromIsolatedStorage();
}

/// 
/// Loads the model from isolated storage
/// 
private void LoadModelFromIsolatedStorage()
{
  MvvmMap.Instance = this.RetrieveFromIsolatedStorage();
  if (MvvmMap.Instance == null) MvvmMap.CreateNew();
}

// Code to execute when the application is deactivated (sent to background)
// This code will not execute when the application is closing
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
  this.SaveToIsolatedStorage(MvvmMap.Instance);
}

// Code to execute when the application is closing (eg, user hit Back)
// This code will not execute when the application is deactivated
private void Application_Closing(object sender, ClosingEventArgs e)
{
   this.SaveToIsolatedStorage(MvvmMap.Instance);
}

Notice both deactivation and closing call the same code: the data is always written to Isolated Storage. Similarly, launching and deactivation always call RetrieveFromIsolatedStorage. It’s perfectly possible to store the ViewModel in the PhoneApplicationService but then the stored model won’t be available when the application is started again from the start menu (in stead of using the “back” button). This is “works as designed” behavior as far as Windows Phone 7 is concerned but if it’s used in as described above, both the back button and a restart give the same result – the last application state is preserved.

A drawback of saving the entire ViewModel as a binary is that deserializing fails when major changes are implemented in the ViewModel. This is why it always is surrounded by try/catch statements. Attentive readers also may have noticed that property “AvailableMapSources” is serialized in vain, since it’s always initialized from the constructor. SilverlightSerializer’s default behavior is to serialize everything, but if something needs to be skipped it can be marked with the [DoNotSerialize] attribute defined by SilverlightSerializer itself.

Concluding remarks

For all but the most trivial Windows Phone 7 applications MVVM simply is the way to go, if only because SilverlightSerializer makes a quite complex thing like tombstoning a breeze. For complex application with multiple models things get a lit more challenging, but it’s clear MVVMLight and SilverlightSerializer are a winning team.

Thanks to Jarno Peshier and Dennis Vroegop for their suggestions on the original article.

Code can be downloaded here.

19 February 2011

Extended Windows Phone 7 page for handling rotation, focused element updates and back key press

In this article I present ‘my’ base class for Windows Phone 7 Application Pages – essentially nothing more than a collection of utilities to handle common scenarios. Most of the code I did not write myself, but I think it’s a nice aggregation that handles the following three common problems:

  1. When someone is typing in a TextBox and receives a phone call, the model bound to that TextBox does not receive an NotifyPropertyChanged and thus is not updated – and if you tombstone the ViewModel, like I do, the last keystrokes are lost when the application is activated again.
  2. When a page changes from portrait to landscape, you want that transition to be animated
  3. When the users presses the back key, the ViewModel needs to be able to interact with that

I have put this class in “LocalJoost.Controls”. First, class definition and the part that handles rotation:

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Navigation;
using LocalJoost.BaseModels;
using Microsoft.Phone.Controls;

namespace LocalJoost.Controls
{
  public class ExtendedPhonePage : PhoneApplicationPage
  {
    public ExtendedPhonePage()
    {
      Loaded += ExtendedPhonePageLoaded;
    }

    void ExtendedPhonePageLoaded(object sender, RoutedEventArgs e)
    {
      _lastOrientation = Orientation;
    }
    
    PageOrientation _lastOrientation;
    protected override void OnOrientationChanged(OrientationChangedEventArgs e)
    {
      var newOrientation = e.Orientation;
      var transitionElement = new RotateTransition();

      switch (newOrientation)
      {
        case PageOrientation.Landscape:
        case PageOrientation.LandscapeRight:
          // Come here from PortraitUp (i.e. clockwise) or LandscapeLeft?
          if (_lastOrientation == PageOrientation.PortraitUp)
            transitionElement.Mode = RotateTransitionMode.In90Counterclockwise;
          else
            transitionElement.Mode = RotateTransitionMode.In180Clockwise;
          break;
        case PageOrientation.LandscapeLeft:
          // Come here from LandscapeRight or PortraitUp?
          if (_lastOrientation == PageOrientation.LandscapeRight)
            transitionElement.Mode = RotateTransitionMode.In180Counterclockwise;
          else
            transitionElement.Mode = RotateTransitionMode.In90Clockwise;
          break;
        case PageOrientation.Portrait:
        case PageOrientation.PortraitUp:
          // Come here from LandscapeLeft or LandscapeRight?
          if (_lastOrientation == PageOrientation.LandscapeLeft)
            transitionElement.Mode = RotateTransitionMode.In90Counterclockwise;
          else
            transitionElement.Mode = RotateTransitionMode.In90Clockwise;
          break;
        default:
          break;
      }

      // Execute the transition
       var phoneApplicationPage = 
           (((PhoneApplicationFrame)Application.Current.RootVisual)).Content
              as PhoneApplicationPage;
      transition.Completed += (sender, args) => transition.Stop();
      transition.Begin();

      _lastOrientation = newOrientation;
      base.OnOrientationChanged(e);
    }
  }
}
This is basically a slightly (very slightly) modified version of what is presented by Andy Wigley, only stuck in a base class.

The second part, which I nicked from The Code Project simply overrides OnNavigatedFrom – an event that is called when an App tombstones, unlike TextBox.LostFocus – and then forces the bound element to be updated. As a bonus, it tries to reset the focused element as well.

private const string FocusedElement = "FOCUSED_ELEMENT";

protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
  if (State.ContainsKey(FocusedElement))
  {
    State.Remove(FocusedElement);
  }

  var focusedElement = FocusManager.GetFocusedElement() as Control;
  if (focusedElement == null) return;
  if (!String.IsNullOrEmpty(focusedElement.Name))
  {
    State.Add(FocusedElement, focusedElement.Name);
  }

  BindingExpression be = null;

  //TODO - Developers, add additional controls here like a 
  //date picker, combobox, etc.
  if (focusedElement is TextBox)
  {
    be = focusedElement.GetBindingExpression(TextBox.TextProperty);
  }
  if (be != null)
  {
    be.UpdateSource();
  }
  base.OnNavigatingFrom(e);
}

And the third part, which I actually created myself, handles the OnBackKeyPress scenario. Sometimes you want to cancel navigation out of the page, for instance because you have created a popup. If the user then presses the back key you want to close the popup and cancel the navigation (this is actually part of the Certification Requirements), and preferably do so my letting the view model handle that. Unfortunately the only place to intercept the OnBackKeyPress event is by overriding in a method in the Page. So I simply decided to handle this by overriding the OnBackKeyPress like this:

protected override void OnBackKeyPress( CancelEventArgs e)
{
  var dc = DataContext as IBackKeyPressHandler;
  if (dc != null) dc.OnBackKeyPress(e);
}
All ViewModels that should be able to handle dc.OnBackKeyPress should then implement the very complicated interface IBackKeyPressHandler:
using System.ComponentModel;

namespace LocalJoost.BaseModels
{
  public interface IBackKeyPressHandler
  {
    void OnBackKeyPress(CancelEventArgs e);
  }
}
and you are done - the model now gets notified of the OnBackKeyPress and can handle this – or not. Of course you can do this with Messaging and whatnot – this is easy, clean and simple to understand. With on prerequisite of course: the ViewModel should be bound directly to the Page.

In stead of “phone:PhoneApplicationPage” on the top and bottom of my application pages I now simply put “LocalJoost:ExtendedPhonePage” and I am done and get the three functions described above for free. Well, that is, you need of course to define the namespace LocalJoost as something like
“xmlns:LocalJoost="clr-namespace:LocalJoost.Controls;assembly=LocalJoost" – or leave it to ReSharper or some tool like that to do that for you. 

12 January 2011

Tombstoning MVVMLight ViewModels with SilverlightSerializer on Windows Phone 7

I’ve been down this road before but I thought it wise to revisit this subject, since I see a lot of people in the Windows Phone 7 developer community still struggling with the concept of tombstoming. In my previous attempt to make some universal way of tombstoning ViewModels on Windows Phone 7 I used DataContractSerializer, which is a nice idea but does not work very well with MVVMLight since the ViewModelBase is not serializable. And sometimes you have to go trough a lot of hooplah if stuff you use in your code does not turn out to be serializable after all.There is a better way, I think. So here it is: universal tombstoning for MVVMLight, take two.

Enter SilverlightSerializer by the as far as I am concerned immortal Mike Talbot. Download this thing and store it somewhere in your sources. To use it for tombstoning, I have created the following extension methods:

using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows;
using GalaSoft.MvvmLight;

namespace LocalJoost.Utilities
{
  public static class ApplicationExtensions
  {
    private static string GetIsFile( Type t)
    {
      return string.Concat(t.Name, ".dat");
    }

    public static T RetrieveFromIsolatedStorage<T>(this Application app) 
      where T : class
    {
      using (var userAppStore = 
         IsolatedStorageFile.GetUserStoreForApplication())
      {
        var dataFileName = GetIsFile(typeof(T));
        if (userAppStore.FileExists(dataFileName))
        {
          using (var iss = userAppStore.OpenFile(dataFileName, FileMode.Open))
          {
             return SilverlightSerializer.Deserialize(iss) as T;
          }
        }
      }
      return null;
    }

    public static void SaveToIsolatedStorage(this Application app, 
                                      ViewModelBase model)
    {
      var dataFileName = GetIsFile((model.GetType()));
      using (var userAppStore = 
               IsolatedStorageFile.GetUserStoreForApplication())
      {
        if (userAppStore.FileExists(dataFileName))
        {
          userAppStore.DeleteFile(dataFileName);
        }
        using (var iss = userAppStore.CreateFile(dataFileName))
        {
          SilverlightSerializer.Serialize(model,iss);          
        }
      }
    }
  }
}

Then I go to the App.xaml.cs and modify it as depicted next:

private void Application_Launching(object sender, LaunchingEventArgs e)
{
  LoadModel();
}

private void Application_Activated(object sender, ActivatedEventArgs e)
{
  LoadModel();
}

private void LoadModel()
{
  try
  {
    MyMainViewModel.Instance = this.RetrieveFromIsolatedStorage<MyMainViewModel>();
  }
  catch (Exception){ }
  if( MyMainViewModel.Instance == null) MyMainViewModel.CreateNew(); 
}

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
  this.SaveToIsolatedStorage(MyMainViewModel.Instance);
}

private void Application_Closing(object sender, ClosingEventArgs e)
{
  this.SaveToIsolatedStorage(MyMainViewModel.Instance);
}

In red you see my additions to the standard generated App.xaml.cs. You will also need to add at least one “using” statement (using LocalJoost.Utilities) and a second one with the namespace in which you have put MyMainViewModel. Notice the fact that there is an empty try-catch around the RetrieveFromIsolatedStorage call. This is intentional. Since binary deserialization tends to throw exceptions if you have changed the ViewModel’s code, you always want to make sure your application gets served a working ViewModel where ever it needs to come from.

The skeleton of a MainViewModel looks something like this in my household:

public class MyMainViewModel : ViewModelBase
{
  public MyMainViewModel( )
  {
  }  

  private static MyMainViewModel _instance;
  public static MyMainViewModel Instance
  {
    get {return _instance;}
    set { _instance = value; }
  }

  public static void CreateNew()
  {
    if (_instance == null)
    {
      _instance = new MyMainViewModel();
    }
  }
}

If your want certain properties of your ViewModel specifically not to be serialized (because they come from a – in this example omitted – application model) you can mark them with the [DoNotSerialize] attribute that comes with SilverlightSerializer.

You can now always bind to MyMainViewModel.Instance and you will either get a fresh new or a retrieved from isolated storage ViewModel. The fun thing is that although MVVMLight’s ViewModelBase is not serializable, the SilverlightSerializer does in fact serialize it which makes life a whole lot easier.

You can, by the way, also decide to store the ViewModel on the PhoneApplicationService on deactivation and retrieve it there on activation. IMHO always storing on isolated storage gives a more consistent feeling to an application. But that’s just me.

A few important points to conclude this post:

  • A ViewModel that is to be serialized on isolated storage should always have a default constructor.
  • Properties of the ViewModel that you want to have serialized need to have a getter and a setter (believe me, you can spend some time overlooking this).
  • Do not try something clever like this on the static instance property of your ViewModel:
public static MyMainViewModel Instance
{
  get {return _instance ?? (_instance = new MyMainViewModel());}
  set { _instance = value; }
}
If you do this, MyMainViewModel.Instance = this.RetrieveFromIsolatedStorage will first call the getter, thus creating a new instance of MyMainViewModel whatever happens next, then overwrite it with the retrieved version. But if you do something like registering a message in the constructor of MyMainViewModel, the initially created model will stay alive ‘somewhere’ - you will end up with two instances of your supposedly singleton instance running in you App, both listening to the same message and reacting to it - and spend, like me, a not so nice and probably extended period of  time scratching your head and wondering what the hell is going on.

I hope that with this post I once and for all made it easier for the Windows Phone 7 community to tackle something apparently tricky as tombstoning thus improving the quality of what we are all going to put in the App Hub.

29 September 2010

Extension methods for tomb stoning the Windows Phone 7 model

As I was (and still am) playing with my MVVM-driven Windows Phone 7 map viewer, I started thinking about tomb stoning and/or serializing stuff into the Windows Phone 7 application state and/or isolated storage. I came up with four extension methods than can be used to store/serialize the complete model in both. Maybe not ideal in all cases, but it was a simple solution for me.

Getting started

I created an assembly LocalJoost.Utilities, in which I first defined a generic interface for the model, which I called – with a flash of inspiration – IModel ;-). It’s implementation is not interesting for the extension methods.

Save to phone application state

using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Runtime.Serialization;
using System.Windows;
using Microsoft.Phone.Shell;

namespace LocalJoost.Utilities
{
  /// <summary>
  /// Some extensions method that allow serializing and deserializing
  /// a model to and from phone state and/or isolated storage
  /// </summary>
  public static class ApplicationExtensions
  {
    private const string ModelKey = "model";
 
    public static IModel RetrieveFromPhoneState(this Application app) 
    {
      if (PhoneApplicationService.Current.State.ContainsKey(ModelKey))
      {
        return PhoneApplicationService.Current.State[ModelKey] as IModel;
      }
      return null;
    }

    public static void SaveToPhoneState(this Application app, IModel model)
    {
      if (PhoneApplicationService.Current.State.ContainsKey(ModelKey))
      {
        PhoneApplicationService.Current.State.Remove(ModelKey);
      }
      PhoneApplicationService.Current.State.Add(ModelKey, model);
    }
  }
}

Creating a Locator

I wanted my model to be bindable by XAML and be usable from code as well, so I created the following ‘Locator’:

using LocalJoost.Utilities;

namespace LocalJoost.Models
{
  public class Locator
  {
    private static IModel _model;
    private static Locator _locator;

    public IModel Model
    {
      get { return _model; }
      set { _model = value; }
    }

    public static Locator Instance
    {
      get { return _locator ?? (_locator = new Locator()); }
    }
  }
}

Using Locator and Model from XAML

First, add the namespace to the XAML

xmlns:Utilities="clr-namespace:LocalJoost.Utilities;assembly=LocalJoost.Utilities"

Then bind it to your Layout root or any other element of choice

 <Grid x:Name="LayoutRoot"
  DataContext="{Binding Source={StaticResource Locator}, Path=Model}">

Instantiating your model

Interesting caveat – the Locator is instantiated, not your model. When the application runs for the very first time, there will be no model to retrieve. Thus, in the constructor of App.Xaml.cs you need to add:

Locator.Instance.Model = new YourModel();

in which you replace "YourModel" for your own model type.

Storing in / retrieving from phone application state

Using the extension methods is pretty simple then: open your App.Xaml.cs, find two methods called “Application_Activated” and “Application_Deactivated” and modify them as follows:

// Code to execute when the application is activated (brought to foreground)
// This code will not execute when the application is first launched
private void Application_Activated(object sender, ActivatedEventArgs e)
{
  Locator.Instance.Model = this.RetrieveFromPhoneState();
}

// Code to execute when the application is deactivated (sent to background)
// This code will not execute when the application is closing
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
  this.SaveToPhoneState(Locator.Instance.Model);
}

And that’s it. Your model now persists to the phone state and survives, for instance, a hit on the search button

Storing in Isolated Storage

This proves to be marginally more difficult. I expanded the class ApplicationExtensions with two more methods and a const:

private const string DataFile = "model.dat";

public static T RetrieveFromIsolatedStorage<T>(this Application app) 
  where T : class
{
  using (var appStorage = IsolatedStorageFile.GetUserStoreForApplication())
  {
    if (appStorage.FileExists(DataFile))
    {
      using (var iss = appStorage.OpenFile(DataFile, FileMode.Open))
      {
        try
        {
          var serializer = new DataContractSerializer(typeof(T));
          return serializer.ReadObject(iss) as T;
        }
        catch (Exception e)
        {
          System.Diagnostics.Debug.WriteLine(e);
        }
      }
    }
  }
  return null;
}

public static void SaveToIsolatedStorage(this Application app, IModel model)
{
  using (var appStorage = IsolatedStorageFile.GetUserStoreForApplication())
  {
    if (appStorage.FileExists(DataFile))
    {
      appStorage.DeleteFile(DataFile);
    }
    var serializer = new DataContractSerializer(model.GetType());
    using (var iss = appStorage.CreateFile(DataFile))
    {
      serializer.WriteObject(iss, model);
    }
  }
}

You now have to find Application_Launching and Application_Closing in App.Xaml.cs and modify them in a similar way:

// Code to execute when the application is launching (eg, from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
    Locator.Instance.Model = 
       this.RetrieveFromIsolatedStorage<YourModel>();
}

// Code to execute when the application is closing (eg, user hit Back)
// This code will not execute when the application is deactivated
private void Application_Closing(object sender, ClosingEventArgs e)
{
    this.SaveToIsolatedStorage(Locator.Instance.Model);
}

Some notes

  • In order to make this work, your need to mark your model class with the [DataContract] attribute, and every member you want to be serialized with [DataMember]
  • If you are using MVVMLight (which of course you are ;-) ) and if you are just making simple apps that don’t use separate models but just view models, be aware that your model cannot inherit from ViewModelBase, for the simple reason that this class is not serializable. Oops ;-)
  • If your model communicates with the view using attached dependency properties you may run into some timing difficulties. I will soon blog about a work-around for that.