26 November 2011

Safe event detachment base class for Windows Phone 7 behaviors

Some time ago I blogged about the Safe Event Detachment ‘pattern’ for behaviors and even included a snippet that made implementing this pattern easier. When I started to use this pattern more often myself I observed quite some code duplication appearing and I don’t like that. What’s more – although the pattern works well under Silverlight and WPF, there are some unique situations in Windows Phone 7 that need some extra attention – particularly the situation in which the user is navigating back to a page containing such a behavior. For when the user is navigation from the page, the AssociatedObject’s Unloaded event fires and the behavior is de-activated. If the user then moves back to the page – the AssociatedObject’s OnAttached event is not fired and the behavior is not re-initialized.

So I set out to create a base class taking care of most of this initalization hooplah without bothering the developer too much. This turned out to be not so simple as I thought. But anyway – thinks worked out. The initial setup of the base class is like this:
using System;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;

namespace Wp7nl.Behaviors
{
  /// <summary>
  /// A base class implementing the safe event detachment pattern for behaviors.
  /// Optional re-init after page back navigation.
  /// </summary>
  /// <typeparam name="T">The framework element type this behavior attaches to</typeparam>
  public abstract class SafeBehavior<T> : Behavior<T> where T : FrameworkElement
  {
    protected SafeBehavior()
    {
      IsCleanedUp = true;
    }

    /// <summary>
    ///Setting this value to true in the constructor makes the behavior
    ///re-init after a page back event.
    /// </summary>
    protected bool ListenToPageBackEvent { get; set; }

    /// <summary>
    /// The page this behavior is on
    /// </summary>
    protected PhoneApplicationFrame ParentPage;

    /// <summary>
    /// The uri of the page this behavior is on
    /// </summary>
    private Uri pageSource;

    protected override void OnAttached()
    {
      base.OnAttached();
      InitBehavior();
    }

    /// <summary>
    /// Does the initial wiring of events
    /// </summary>
    protected void InitBehavior()
    {
      if (IsCleanedUp)
      {
        IsCleanedUp = false;
        AssociatedObject.Loaded += AssociatedObjectLoaded;
        AssociatedObject.Unloaded += AssociatedObjectUnloaded;
      }
    }
  }
}

The comments already give away which direction this is going to take: the behavior keeps track of the page it’s on and that page’s uri to track if the user is navigating back to this page. If you don’t want this behavior, do nothing. If you need to track the user navigating back to the page (and believe me, in Windows Phone 7 you want that in most of the cases), set ListenToPageBackEvent to true in the behavior’s constructor. The setting up of this tracking is done in the next method:

/// <summary>
/// Does further event wiring and initialization after load
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
{
  // Find the page this control is on and listen to its orientation changed events
  if (ParentPage == null && ListenToPageBackEvent)
  {
    ParentPage = Application.Current.RootVisual as PhoneApplicationFrame;
    pageSource = ParentPage.CurrentSource;
    ParentPage.Navigated += ParentPageNavigated;
  }
  OnSetup();
}

/// <summary>
/// Fired whe page navigation happens
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ParentPageNavigated(object sender, NavigationEventArgs e)
{
  // Re-setup when this page is navigated BACK to
  if (IsNavigatingBackToBehaviorPage(e))
  {
    if (IsCleanedUp)
    {
      InitBehavior();
    }
  }
  OnParentPageNavigated(sender, e);
}

/// <summary>
/// Checks if the back navigation navigates back to the page
/// on which this behavior is on
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
protected bool IsNavigatingBackToBehaviorPage(NavigationEventArgs e)
{
  return (e.NavigationMode == NavigationMode.Back && e.Uri.Equals(pageSource));
}

Now if you have set ListenToPageBackEvent to true, it keeps the root visual (i.e. the page on which the behavior is plonked) in ParentPage, it’s uri in pageSouce and attaches an listener to the ParentPageNavigated event of this page. Now if a navigation event happens, the IsNavigatingBackToBehaviorPage checks by comparing uri’s if the user is actually navigating back to this page.

All very interesting, but the most important is: there are two methods OnSetup and OnParentPageNavigated in this class which are called. They are basically emtpy and form your hook points into this:

/// <summary>
/// Override this to add your re-init
/// </summary>    
protected virtual void OnParentPageNavigated(object sender, NavigationEventArgs e)
{     
}

/// <summary>
/// Override this to add your own setup
/// </summary>
protected virtual void OnSetup()
{
}

So far for the setup stuff: the cleanup stuff is a lot simpler:

protected bool IsCleanedUp { get; private set; }

/// <summary>
/// Executes at OnDetaching or OnUnloaded (usually the last)
/// </summary>
private void Cleanup()
{
  if (!IsCleanedUp)
  {
    AssociatedObject.Loaded -= AssociatedObjectLoaded;
    AssociatedObject.Unloaded -= AssociatedObjectUnloaded;
    OnCleanup();
    IsCleanedUp = true;
  }
}

protected override void OnDetaching()
{
  Cleanup();
  base.OnDetaching();
}

private void AssociatedObjectUnloaded(object sender, RoutedEventArgs e)
{
  Cleanup();
}

/// <summary>
/// Override this to add your own cleanup
/// </summary>
protected virtual void OnCleanup()
{
}

And once again you see a simple virtual Cleanup you can override.

I realize this is all very theoretical and technical, and the question you probably have now is – so what is this for and how do you use it? The usage is simple:

  • You create a MyBehavior<T> that descends from SafeBehavior<T>
  • If you want your behavior to re-init when the user navigates back set ListenToPageBackEvent  to true in the MyBehavior constructor. But beware. By its very nature the Navigated event is not detached. So basically you are leaking memory. Therefore, if you make a lot of behaviors, like in a game, don’t ever set ListenToPageBackEvent to true.Use with care and moderation.
  • You do setting up events in an override of OnSetup
  • You do cleaning up of events in an override of OnCleanup
  • And if you want to do something extra when the user is navigating back to the page do that in an override of OnParentPageNavigated.

This makes implementing the Safe Event Detachment ‘pattern‘ way more easy. This new behavior base is now included in my wp7nl library on codeplex and the source can be found here. A sample usage of this base class can be found here.

22 November 2011

Using MVVMLight, ItemsControl, Blend and behaviors to make a ‘heads up compass’

When I talk of the MVVM pattern, people usually think of business objects that get wrapped by ViewModels which get data bound to a user interface. Usually this is something like a list of people, news, items that can be purchased, whatever – and usually this data is displayed in a list box, with a bit of templating if it isn’t too much trouble. That’s fine in itself and good way to use my favorite pattern but there are more things possible using MVVM data binding than most people imagine. The most fun way I have been able to discover is to combine ItemsControl and behaviors. This is what drives my game Catch’em Birds. And this article shows how to use this technique make a kind of heads up compass. I’ll sprinkle some ‘how to do things in Blend’ (like adding and configuring behaviors) throughout the article as well.

For the hasty readers: “Setting the stage”, “Building the models” and “Building the ViewModel” is the ground work. The real stuff starts at “Initial user interface”.

Setting the stage

  • Create a new Windows Phone 7 application. Let’s call it “HeadsUpCompass”. Select Windows Phone 7.1 - duh ;).
  • Install my wp7nl library from codeplex via NuGet. This will get you some of my stuff and MVVMLight and some more stuff as well in one go.
  • Add references to Microsoft.Device.Sensors and Microsoft.Xna.Framework.

Building the models

The application has two models: CompassDirectionModel – holding stuff that wants to be displayed, and CompassModel, that checks the compass direction using the motion API. The CompassModel is implemented below. I’ve explained using the Motion API to check where the camera is looking in an earlier post so I’ll skip the details here. It’s basically the same functionality, wrapped in a model, with an event firing at the end:

using System;
using System.Windows;
using Microsoft.Devices.Sensors;
using Microsoft.Xna.Framework;

namespace HeadsUpCompass.Models
{
  public class CompassModel
  {
    Motion motion;

    /// <summary>
    /// Inits this instance.
    /// </summary>
    public void Init()
    {
      // Check to see if the Motion API is supported on the device.
      if (!Motion.IsSupported)
      {
        MessageBox.Show("the Motion API is not supported on this device.");
        return;
      }

      // If the Motion object is null, initialize it and add a CurrentValueChanged
      // event handler.
      if (motion == null)
      {
        motion = new Motion {TimeBetweenUpdates = TimeSpan.FromMilliseconds(250)};
        motion.CurrentValueChanged += MotionCurrentValueChanged;
      }

      // Try to start the Motion API.
      try
      {
        motion.Start();
      }
      catch (Exception)
      {
        MessageBox.Show("unable to start the Motion API.");
      }
    }

        /// <summary>
    /// Stops this instance.
    /// </summary>
    public void Stop()
    {
      motion.Stop();
      motion.CurrentValueChanged -= MotionCurrentValueChanged;
    }

    /// <summary>
    /// Fired when a direction change is detected
    /// </summary>
    void MotionCurrentValueChanged(object sender, 
                                   SensorReadingEventArgs<MotionReading> e)
    {
      var yaw = MathHelper.ToDegrees(e.SensorReading.Attitude.Yaw);
      var roll = MathHelper.ToDegrees(e.SensorReading.Attitude.Roll);
      var pitch = MathHelper.ToDegrees(e.SensorReading.Attitude.Pitch);

      if (roll < -20 && roll > -160)
      {
        SetNewCompassDirection(360 - yaw + 90);
      }
      else if (roll > 20 && roll < 160)
      {
        SetNewCompassDirection(360 - yaw - 90);
      }
      else if (pitch > 20 && pitch < 160)
      {
        SetNewCompassDirection(-yaw );
      }
      else if (pitch < -20 && pitch > -160)
      {
        SetNewCompassDirection(360 - yaw + 180);
      }
    }

    private void SetNewCompassDirection(double compassDirection)
    {
      if (compassDirection > 360)
      {
        compassDirection -= 360;
      }
      if (compassDirection < 0)
      {
        compassDirection += 360;
      }

      if (CompassDirectionChanged != null)
      {
        CompassDirectionChanged(Convert.ToInt32(Math.Round(compassDirection)));
      }
    }

    // Event communicating compass direction change to outside world
    public event CompassDirectionChangedHandler CompassDirectionChanged;
    
    public delegate void CompassDirectionChangedHandler(int newDirection);
  }
}

The stuff that gets displayed has it’s own model, and is very simple:

using System.Collections.Generic;

namespace HeadsUpCompass.Models
{
  public class CompassDirectionModel
  {
    public int Direction { get; set; }

    public string Text { get; set; }

    public static IEnumerable<CompassDirectionModel> GetCompassDirections()
    {
      return new List<CompassDirectionModel>
      {
        new CompassDirectionModel {Direction = 0, Text = "N"},
        new CompassDirectionModel {Direction = 45, Text = "NE"},
        new CompassDirectionModel {Direction = 90, Text = "E"},
        new CompassDirectionModel {Direction = 135, Text = "SE"},
        new CompassDirectionModel {Direction = 180, Text = "S"},
        new CompassDirectionModel {Direction = 225, Text = "SW"},
        new CompassDirectionModel {Direction = 270, Text = "W"},
        new CompassDirectionModel {Direction = 315, Text = "NW"}
      };
    }
  }
}

And most of is a static factory method too that I, being a lazy programmer, just plonked into a class. This model accepts a text and a compass direction where it wants to be displayed. You can limit or add whatever you like.

Building the ViewModel

So far it has not been quite rocket science, and neither is the only ViewModel that is employed in this solution:

using System.Collections.ObjectModel;
using System.Windows;
using GalaSoft.MvvmLight;
using HeadsUpCompass.Models;

namespace HeadsUpCompass.ViewModels
{
  public class CompassViewModel : ViewModelBase
  {
    private readonly CompassModel model;

    public CompassViewModel()
    {
      model = new CompassModel();
      model.CompassDirectionChanged += ModelCompassDirectionChanged;
      CompassDirections = 
        new ObservableCollection<CompassDirectionModel>(
          CompassDirectionModel.GetCompassDirections());
      if( !IsInDesignMode) model.Init();
    }

    void ModelCompassDirectionChanged(int newDirection)
    {
      Deployment.Current.Dispatcher.BeginInvoke(
        () => { CompassDirection = newDirection; });
    }

    private int compassDirection;
    public int CompassDirection
    {
      get { return compassDirection; }
      set
      {
        if (compassDirection != value)
        {
          compassDirection = value;
          RaisePropertyChanged(() => CompassDirection);
        }
      }
    }

    private ObservableCollection<CompassDirectionModel> compassDirections;
    public ObservableCollection<CompassDirectionModel> CompassDirections
    {
      get { return compassDirections; }
      set
      {
        if (compassDirections != value)
        {
          compassDirections = value;
          RaisePropertyChanged(() => CompassDirections);
        }
      }
    }
  }
}

The ViewModel creates a CompassDirectionModel and subscribes to its events, and fills an observable collection with CompassDirectionModels – so basically a list of texts and the direction in which they want to be displayed.

Initial user interface

First of all, open MainPage.xaml, set shell:SystemTray.IsVisible="false", SupportedOrientations="PortraitOrLandscape" and then delete the grid “LayoutRoot” and everything inside it (and get rid of the App Bar sample code that commented out as well, that clears the stage). Replace it by this:

<Grid x:Name="LayoutRoot" >
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="*"/>
  </Grid.RowDefinitions >

  <!--TitlePanel contains the name of the application and page title-->
  <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
    <TextBlock x:Name="ApplicationTitle" Text="HeadsUp Compass" 
      Style="{StaticResource PhoneTextNormalStyle}"/>
  </StackPanel>

  <!--ContentPanel - place additional content here-->
  <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="0.7*"/>
        <RowDefinition Height="0.3*"/>
      </Grid.RowDefinitions >
    <ItemsControl x:Name="CompassItems" ItemsSource="{Binding CompassDirections}"
           Grid.Row="0">
      <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
          <Canvas Background="Transparent" />
        </ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
      <ItemsControl.ItemTemplate>
        <DataTemplate>
          <Grid>
            <TextBlock Text="{Binding Text}" FontSize="48" Foreground="Red"/>
          </Grid>
        </DataTemplate>
      </ItemsControl.ItemTemplate>
    </ItemsControl>

    <TextBlock TextWrapping="Wrap" Text="{Binding CompassDirection}" 
       VerticalAlignment="Top" Margin="0,10,0,0" FontSize="48" Foreground="Red" 
       HorizontalAlignment="Center" Grid.Row="1"/>
    </Grid>
  </Grid>
</Grid>

The interesting part I’ve marked red. This is an ItemsControl - nothing more than a simple repeater. The declaration used in this article uses two templates: an ItemsPanelTemplate, which describes what all the bound items are rendered upon – in this case, a transparent canvas – and an ItemTemplate, which describes how each individual item in the CompassDirections property bound to the control itself is displayed – in this case, a grid with a simple text bound to a Text property. But on the ItemTemplate you can put literally everything you can dream. Including behaviors.

Setting up data binding using Expression Blend

First let’s get the data binding done. This is considerably easier using Expression Blend. Compile the application so far, and open it up in Expression Blend. Then use the following workflow:DataSource

  • On the top right hand side, select the “Data” tab
  • Click the Icon on the right that gives as tooltip “Create data source”
  • Select “Create Object DataSource”
  • Select “CompassViewModel” in the popup that appears.
  • Drag “CompassViewModel” under “CompassViewModelSource on top of the “LayoutRoot” grid in the Objects and Timeline Panel to the left bottom

Bound

If you have done things correctly, you should immediately see appear a red zero in the horizontal middle of your screen a little below the center, and a lot of texts stacked upon each other in the top left op your screen.

The fun thing is, this application already works more or less. If you deploy this on a device and fire it up, you will already see that the 0 starts to display the compass direction in degrees. But the compass direction texts are still stacked upon each other in the top left corner. Now it’s time for the coupe de grâce: a behavior that dynamically changes the location of the compass direction texts.

Location calculation

The behavior actually consist out of two parts: the LocationCalculator class and the actual CompassDirectionDisplayBehavior. I pulled the actual location calculation out of the behavior because I had to cobble it together by trial and error – and adding it to a test project by means of a link and testing it by unit tests made this a lot easier. Anyway, the code itself it pretty small: most of it is comments and properties:

using System;
using System.Windows;

namespace ARCompass.Behaviors
{
  /// <summary>
  /// Calculates screen positions based upon compass locations
  /// </summary>
  public class LocationCalcutator
  {
    /// <summary>
    /// Initializes a new instance of the LocationCalcutator class.
    /// Sets some reasonable defaults
    /// </summary>
    public LocationCalcutator()
    {
      Resolution = (6 * 800 / 360);
      CanvasHeight = 800;
      CanvasWidth = 480;
      ObjectWidth = 10;
    }

    /// <summary>
    /// Gets or sets the resolution (i.e. the pixels per degree
    /// </summary>
    public int Resolution { get; set; }

    /// <summary>
    /// The compass direction where the object to calculate for is located
    /// </summary>
    public int DisplayCompassDirection { get; set; }

    /// <summary>
    /// Gets or sets the width of the canvas.
    /// </summary>
    public double CanvasWidth { get; set; }

    /// <summary>
    /// Gets or sets the height of the canvas.
    /// </summary>
    public double CanvasHeight { get; set; }

    /// <summary>
    /// Gets or sets the width of the object (in pixels)
    /// </summary>
    public double ObjectWidth { get; set; }

    /// <summary>
    /// Sets the horizontal pixels.
    /// </summary>
    /// <param name="pixels">The pixels.</param>
    public void SetHorizontalPixels(double pixels)
    {
      Resolution = Convert.ToInt32(Math.Round(pixels/360));
    }

    /// <summary>
    /// Calculates the screen position.
    /// </summary>
    /// <param name="compassDirection">The compass direction the screen is 
    /// currently looking at.</param>
    /// <returns></returns>
    public Point CalculateScreenPosition(int compassDirection)
    {
      if (!(double.IsNaN(CanvasHeight) || double.IsNaN(CanvasWidth)))
      {
        var y = CanvasHeight / 2;
        var deltaDegrees1 = compassDirection - DisplayCompassDirection;
        var deltaDegrees2 = compassDirection - DisplayCompassDirection - 360;
        var deltaDegrees = 
           Math.Abs(deltaDegrees1) < Math.Abs(deltaDegrees2) ? 
             deltaDegrees1 : deltaDegrees2;

        var dx = deltaDegrees * Resolution;
        var x = Convert.ToInt32(CanvasWidth / 2) - dx;
        return new Point(x, y);
      }
      return new Point(-1000, -1000);
    }

    /// <summary>
    /// Determines whether the specified point is visible in the current canvas
    /// </summary>
    public bool IsVisible(Point point)
    {
      var overshoot = Convert.ToInt32(Math.Round(ObjectWidth/2 + 5));
      return (point.X > -overshoot && point.X < CanvasWidth + overshoot);
    }
  }
}

Resolution is a pretty weird property and is the base for all other calculations. It basically says – how many pixels is 1 degree? Default I set it to 6 * 800 / 360 = 13.333, which basically means if you move your camera 1 degree to the left whatever is displayed on your screen moves 13 pixels to the right.

The CalculateScreenPosition method basically is my way to calculate the screen position of an object in direction compassDirection, without using trigonometry – since I am notoriously bad at it. I’ve learned it about three times if I really have to use it, but for some reason as soon as I stop using it, it quite quickly drops from my mind again. I don’t doubt I’ll be getting reactions of math lovers who will point out this a stupid way to do it ;-). But this works, and that’s fine with me. Finally, the IsVisible property can be used to determine if the objects is on the screen at all.

The actual behavior

This behavior heavily leans on things I wrote about earlier, namely the extension methods for FrameWorkElement I described in my article  “Simple Windows Phone 7 / Silverlight drag/flick behavior” but which are now fortunately all in the Wp7nl library. It is also based upon the Safe event detachment ‘pattern’ for behaviors. Anyway – the base setup is like this:

using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Media;
using ARCompass.Behaviors;
using Phone7.Fx.Preview;
using Wp7nl.Utilities;

namespace HeadsUpCompass.Behaviors
{
  public class CompassDirectionDisplayBehavior : Behavior<FrameworkElement>
  {
    private FrameworkElement elementToAnimate;

    private FrameworkElement displayCanvas;

    private LocationCalcutator calculator;

    #region Setup
    protected override void OnAttached()
    {
      base.OnAttached();
      AssociatedObject.Loaded += AssociatedObjectLoaded;
      AssociatedObject.Unloaded += AssociatedObjectUnloaded;
    }

    void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
    {
      calculator = new LocationCalcutator 
        {DisplayCompassDirection = DisplayCompassDirection};
      elementToAnimate = AssociatedObject.GetElementToAnimate();
      if (!(elementToAnimate.RenderTransform is CompositeTransform))
      {
        elementToAnimate.RenderTransform = new CompositeTransform();
      }
      displayCanvas = elementToAnimate.GetVisualParent();
      if (displayCanvas != null)
      {
        displayCanvas.SizeChanged += DisplayCanvasSizeChanged;
        UpdateCalculator();
      }
    }
    #endregion

    #region Cleanup
    private bool isCleanedUp;

    private void Cleanup()
    {
      if (!isCleanedUp)
      {
        isCleanedUp = true;
        AssociatedObject.Loaded -= AssociatedObjectLoaded;
        AssociatedObject.Unloaded -= AssociatedObjectUnloaded;
      }
    }

    protected override void OnDetaching()
    {
      Cleanup();
      base.OnDetaching();
    }

    void AssociatedObjectUnloaded(object sender, RoutedEventArgs e)
    {
      Cleanup();
    }
    #endregion
  }
}

Now that most of the calculation logic is in the LocationCalculator, the calculation and positioning logic is reduced to these three little methods:

void DisplayCanvasSizeChanged(object sender, SizeChangedEventArgs e)
{
  UpdateCalculator();
}

private void UpdateCalculator()
{
  calculator.CanvasHeight = displayCanvas.ActualHeight;
  calculator.CanvasWidth = displayCanvas.ActualWidth;
  calculator.SetHorizontalPixels(6 * calculator.CanvasWidth);
  UpdateScreenLocation();
}

void UpdateScreenLocation()
{
  var translationPoint = 
     calculator.CalculateScreenPosition(CurrentCompassDirection);
  if (calculator.IsVisible(translationPoint))
  {
    elementToAnimate.SetTranslatePoint(
      calculator.CalculateScreenPosition(CurrentCompassDirection));
    elementToAnimate.Visibility = Visibility.Visible;
  }
  else
  {
    elementToAnimate.Visibility = Visibility.Collapsed;
  }
}

The fun thing is that the calculator’s resolution is set to 6 times the canvas width, so that every time you rotate the phone it recalculates the location where objects are displayed. The net result is that objects are spaced wider apart when you rotate the phone in landscape. Thus, the app makes optimal use of the screen space available.

And all there is left are two dependency properties: DisplayCompassDirection which holds the location in which the current object wants to be displayed, and CurrentCompassDirection  which should get the current direction the camera is looking at. By nature they are pretty verbose unfortunately:

#region DisplayCompassDirection
public const string DisplayCompassDirectionPropertyName = 
   "DisplayCompassDirection";

public int DisplayCompassDirection
{
  get { return (int)GetValue(DisplayCompassDirectionProperty); }
  set { SetValue(DisplayCompassDirectionProperty, value); }
}

public static readonly DependencyProperty DisplayCompassDirectionProperty = 
  DependencyProperty.Register(
    DisplayCompassDirectionPropertyName,
    typeof(int),
    typeof(CompassDirectionDisplayBehavior),
    new PropertyMetadata(0, null));
#endregion

#region CurrentCompassDirection
public const string CurrentCompassDirectionPropertyName = 
  "CurrentCompassDirection";

public int CurrentCompassDirection
{
  get { return (int)GetValue(CurrentCompassDirectionProperty); }
  set { SetValue(CurrentCompassDirectionProperty, value); }
}

public static readonly DependencyProperty CurrentCompassDirectionProperty =
  DependencyProperty.Register(
    CurrentCompassDirectionPropertyName,
    typeof(int),
    typeof(CompassDirectionDisplayBehavior),
    new PropertyMetadata(0, CurrentCompassDirectionChanged));

public static void CurrentCompassDirectionChanged(
  DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  var behavior = d as CompassDirectionDisplayBehavior;
  if (behavior != null)
  {
    behavior.UpdateScreenLocation();
  }
}
#endregion

Save and compile the app. Don’t run it yet.

DragBehaviorAdding/configuring the behavior using Expression Blend

Go back to Expression Blend, and use the following workflow:

  • In the Objects and Timeline Panel to the left, select ItemsControl “CompassItems”
  • Right-click on in, select “Edit Additional Templates”, then “Edit Generated Items (ItemsTemplate)”,  and finally “Edit Current”
  • Top left, select the “Assets” tab. In the left panel below it, select “Behaviors”. In the right panel you should see “CompassDirectionDisplayBehavior” appear.
  • Drag the behavior on top of the grid

As soon as you have done this, you will get a properties tab on the right of your screen that neatly shows the two dependency properties. Of course you can do this in code, but Blend takes care of creating namespaces and name space references – it makes life so much easier and that should appeal to a programmer’s natural laziness. Next task is data binding properties of the behavior to the ViewModel, and using Blend that is dead easy too.

Configuring behavior binding

behaviorpropertiesData binding to a behavior, yes sir (or ma’am)! Welcome to Mango: this is Silverlight 4, so no more complex hooplah with attached dependency properties if you want to have a behavior to play along with data binding. You can now directly bind to dependency properties in the behavior itself! After you have dragged the CompassDirectionDisplayBehavior on Grid, you get the little properties tab as displayed on the left, showing the behavior's two properties. To data bind these, use the following workflow:

  • Click the little square right of “DisplayCompassDirection” (indicated with the red circle), and select “Data Binding” from the popup menu
  • Choose tab “Data context” on the dialog that pops us (usually that’s default)
  • Select property “Direction : (Int32), that is directly under CompassDirections : (CompassDirectionModel)

You have now selected the DisplayCompassDirection property of the behavior to be bound directly to the “Direction” property to the CompassDirectionModel. One can argue if it’s technically corrDataBindingDisplayDirectionect to directly bind a Model in stead of having a ViewModel sitting in between. Since this Model only shows data and has no intelligence at all, I feel comfortable with it – if you do not, go ahead and define a ViewModel around it ;-)

The second and last part of binding goes like this:

  • Click the square behind “CurrentCompassDirection” and select “Data Binding” again in the popup menu
  • Select the “Data Field” tab
  • In the Data Field Tab, select “CompassViewModelDataSource” in the right pane
  • In the left pane, select “CompassDirection : (Int32)
  • Click the down-pointing arrow on the bottom of the dialog – more options appear
  • Select “TwoWay” for “Binding Direction”

You have now selected the CurrentCompassDirection property of the behavior to be bound to the CompassDirection of the CompassViewModel. And that means – you are done! Hit File/Save all and go back to Visual Studio to launch the application on your phone, and you will see compass directions moving through your screen if you move phone.

One for the road

Having a plain black screen to see the compass directions moving over is quite boring. So lets add the behavior to show the Windows Phone 7 camera as background I wrote some time ago as well. Add it to the project, drag it on top of the LayoutRoot grid, and bang – now you don’t only have a heads-up, but a see-trough compass as well!

Some final words

This is all pretty crude in terms of how things move, and there are quite some things eligible for improvement but nevertheless - what we have here is almost like any other old LOB MVVM based application. Business objects bound to the GUI. Only, that GUI happens to be an ItemsControl with a behavior on its template. Which turns the whole app into a dynamic experience. I hope to have tickled your imagination, and showed some useful Blend how-to as well.

MVVM is not limited to just LOB apps. Make something fun. To quote Nokia – make the amazing everyday ;-

Complete sample solution can be found here.

06 November 2011

Carpe Lumia!

Last updated November 10 2011

You know that band you used to love when you were in your teens? They really rocked your adolescent years. They rocked like hell. Their concerts where one great party. I would never end. ….except you grew up, the times changed, so did the music, but the band stuck to what made them great. Fame faded, and slowly they disappeared from your sight. Maybe even their CD’s did not make it into your MP3 collection. They lost their mojo. Sometimes they still performed, but you did not know and even less cared. Sic transit gloria mundi. We all have such a band. If you ask politely and promise to keep it a secret, I’ll tell you mine ;-) .

Now suppose, just for the sake of this analogy, after years and years this band of yours got back together. Not to do a ‘one more time for old times’ sake or a Live Aid gig, no: they kick out the drug-addicted drummer, recruit two new young guitar players, a few cool babes as extra singers, and team up with a new song writer. They reinvent themselves, make a new album – totally different music than what they did before, but still keeping what made them great – a beautiful performance. They bring out their new album and they TOTALLY rock the charts.

Now if you try to imagine the WTF feeling this would give you, you get an idea what I had for a few moments when I first booted up the Nokia Lumia 800 that was delivered on November 4. My first four mobile phones were all Nokia – and now this! Enough poetic mesmerizing, down to business. My thoughts on the Nokia Lumia 800. I’ll just follow the format I used for my HTC 7 Pro to make a fair comparison

The phone itself

It’s physical dimensions are 11.5 x 6 x 1 cm according to my own measurements, although I have to allow for a bit of uncertainty on the latter two because a cross-section on this phone would basically be an oval. The back side is convex, and so is the front side, i.e. the screen. This gives a very beautiful result. It weights 142 grams, which I don’t think is very much but it feels heavy – although I think solid would be a better way to describe it. The first thing that strikes – this phone is different from what I am used to in the Windows Phone arena. It’s beautiful. It has a smooth, streamlined look. And it feels that way, too. Its polycarbonate unibody design feels soft and it has nothing of this plasticy feel so common for these kind of devices. The hardware buttons are freakin’ made of metal – having used the Lumia for only a short time makes the camera button of the HTC 7 Pro feel like a wobbly piece of plastic on a cheap bubble contact. See below how this looks. Click for larger picture – as with all pictures in this post

Lumina2

The USB port is on the top, which I initially thought was very stupid for in-car use. When I attach a charger the plug would stick out of the top of the phone. Turned out I had not thought long enough about it. More about that later.

Screen

Lumia1Apparently Nokia have used some kind of AMOLED that is not the same as Samsung’s Super AMOLED. Nokia’s take on this is called “ClearBlack”. Black is very black, even seen from an extreme angle. Combined with the Nokia-only accent color “Nokia Blue” this gives a very beautiful, lively and crisp image on the start screen. The red is really like very burning fiery red. I don’t know if Omnia 7 users would be as impressed as I am, but coming from the considerably blander looking HTC screens this is nothing short of stunning. The tiles look like they are floating on the screen. Second thing that strikes is the device’s speed. There is a very notable performance increase compared to the HTC 7 Pro. Think Nodo-to-Mango faster. Things are happening at an uncanny speed. New messages in the People Hub are loaded like almost instantly. I suppose this is the faster processor doing its thing.

Another thing about the screen – it is very sensitive. So much actually that I had a little trouble getting the hang of it – I accidently kept selecting messages in the People hub when I just wanted to scroll trough them. But this extreme sensitivity has a rather unexpected bonus for me – combined the convex screen and the enhanced Mango keyboard I can actually touch type on it. I only managed this with the HTC Titan before – because that is simply big enough to accommodate my big carpenter’s son hands.

I am told the screen is made out of gorilla glass. This is supposed to be pretty scratch resistant. I did not try any destructive tests on it. I will report later on its durability.

Phone calls

Made one phone call with my wife. She said I sounded very crisp and and it was better than the HTC 7 Pro. Hello world, welcome to the smartphone that is actually able to make decent phone calls. Well phooey, is anyone impressed? Nokia were already making phones when I was just coming from college so if they had not figured out how to do this after all this time, they had best started making rubber boots again a long time ago.

The camera

WP_000006The first thing that struck me about the camera is that there’s a lot more options in the camera menu than I was used to, like Contrast, Saturation, Focus Mode, ISO, Exposure Mode and White Balance. I took this pretty autumn scene picture with it and I think that looks pretty good. Like I said in my review of the HTC 7 Pro, I am a photographer too and if I take pictures I care about I use a Canon EOS 400 DSLR, so in my book pictures taken by any mobile phone are all, well, let’s keep this polite ;-). Having said that: if I zoom in on pictures made by my HTC 7 Pro I can zoom in about twice before things start to become grainy. Lumia 800: seven times. Nokia wins this hands-down. But that’s from my old phone. I have no comparison with other new phones. I have heard quite mixed results from this.

I also took a few pictures of flowers close by but that did not work out at all. Only later I found out that this probably has to do with the default Focus mode – that’s Macro, and then apparently it does not want to focus on things close by.

Sound

Lumia3I think the best way to describe the sound that comes out of that tiny little speaker on the bottom is something among the lines of “HOLY C…!!!”. I played my little game of Catch’em Birds. When you slam a bird into a cage, it gives a kind of metallic clang, trying to convey the idea of a metal cage door closing behind the bird. On the Lumia 800 this actually sounds more like a vault slamming shut. This device makes a pretty decent stand alone music player. Which has a flip side, too – I played Hydro Thunder Go on it and, well, er, the excellent speaker mercilessly reveals the game sounds effects are actually pretty low quality. But I can hardly blame Nokia for that ;-)

Miscellaneous

Lumia4The nice blue box it comes in contains the standard equipment, i.e. an USB charger with detachable cable (so it can be used to connect the phone to Zune as well), a pair of headphones, a box of manuals in various languages (still unread *cough*) and a silicon case. It actually fits like a glove, and is in the same color as the phone’s body. I suppose it’s for protecting the phone’s shell, but it eludes me from what exactly, since the phone itself is made from a material used in the helmets ice hockey players wear. My wife suggested it was for preventing the floor tiles to crack when you drop a Lumia 800 on it ;-). I also heard these cases will come in different colors, and if that is the case I am so much going to get a blue one ;-). [Note: the blueish reflection of the screen is a side effect of the flash, you don't see this in real life - I just wanted to emphasize the silicon case]

The headphones are kinda cheap and don’t to the phone’s music playing capabilities much justice. For those who want better sound quality, Nokia makes these amazing Monster headsets that are a much better match for the phone.

Software and extra’s

No review of a Lumia 800 would be complete without the on-board extra’s. First of all – we got the genuine iconic, nay, legendary Nokia ringtone and SMS alert. Played on a xylophone. Very nice. For now I selected all the Nokia sounds as a default, just for the heck of it.

Then we have Nokia Drive. Say hello to free turn-by-turn navigation with downloadable maps and voice directions. I don’t know if it’s actually completely world wide, but if I select Manage Maps/Install, I first have to select which continent I want maps to install for: Africa, America, Asia, Australia/Oceania and Europe. Drilling down reveals Russia is in there. China is in there. South America is in there too - including Argentina which does not even have a Marketplace, and the Cayman Islands for heavens’ sake. If works way better than Bing Maps directions, and it’s free! And what’s even better – it supports landscape mode, so you can turn your phone sideways and have wide angle navigation. And then the penny dropped – maybe this is why the USB port is on top, for if you use it for navigation you rotate it 90° and then the USB port is conveniently pointing sideways for the car charger.

The current version of Nokia Drive still needs an online connection for finding addresses and Points of Interest, but at the Tweakers event at November 9 in Amsterdam Nokia revealed an update that supports total offline navigation will be available very soon. So I guess it's time to kiss my old TomTom goodbye. The Germans have a very good saying about this, attributed to Mikhail Gorbachev: “Wer zu spät kommt, den bestraft das Leben” (life punishes those who are too late)

Nokia Maps – can’t say much about this; what I currently have on my phone is a very early beta that is obviously not ready yet, but it seems to be a non-driver’s map application for finding your way around. You know? Like Local Scout, but one that does work in Europe? As a GIS developer I very much wonder if this can be extended and used like the Bing maps control in custom applications. I am not sure if this will eventually work completely offline as well.

Nokia Music – reminds me of what I saw from Pandora before us Europeans got kicked out. Stream music from a multitude of pre-defined ‘stations’ and you can buy and download stuff from Nokia’s MP3 store if you like. Yesterday I just turned it on “80’s rock” and let it go for hours on end. You can skip a limited number of songs you don’t like. The price of listening: zilch. Nada. Nothing. No pass required, no subscription required. Booya Nokia! Does this compete with Zune? Like hell it does – if we could get Zune here. Zune is available in like, 8 countries? Nokia music in a little short of 40, if I am correct. In the mean time – this is great news for all people outside the Zune coverage area. But beware – this eats a lot of data. Don’t run this over your 3G connection unless your unlimited plan is really unlimited. The software specifically warns you for that on first startup.

Room for improvement

Nokia have gone out on a limb to make the ultimate Windows Phone 7 and I think they came pretty far – but there is still room for improvement. From a hardware standpoint, it lacks two features, which by now everyone has heard of:

  • No front facing camera
  • No gyro

Now the gyro is a feature that the proverbial Iowa soccer mom ain’t  gonna miss – the camera is going to hurt if indeed Skype comes to Windows Phone 7. Although I also wonder how much it will actually hurt – I’ve had a video call capable phone for 2.5 years and only used this feature it 3 times – to demo it. I have no idea how much iPhone users actually use FaceTime all the time.

I think Nokia could also look into bringing out a model with more storage aboard. It now ‘only’ has 16GB of storage, which is more than enough for my podcasts, photo’s and music (I may be just weird but I don’t go carrying around enormous music libraries) but apparently there are people need more these days.

Personally I would like also like to have seen a somewhat larger screen.

The verdict

I shelled out €562 to get a Windows Phone 7 with keyboard from the UK to the Netherlands only 8 months ago. I am a die hard physical keyboard phone user. After playing with the Nokia Lumia 800 for only 48 hours, I transferred all my stuff to it and shut down the HTC 7 Pro. Sorry old workhorse. You did well. I know I am going to miss the keyboard sometimes, but the screen colors, the relative sluggish response, the already battered plastic shell, the ablating battery case… it’s just no match for this design beastie.

If you want a phone that supports all the latest Windows Phone 7 hardware features, buy something else. There is plenty of choice. That’s the fun thing of Windows Phone 7 – there is a baseline quality, they’re basically all good, and you do have a choice. One size does not fit all – as for instance the HTC Titan shows quite literally. If you want a really well designed, durable and smart looking, and not overly large Windows Phone 7 device with some very nice free extra’s that’s available at a pretty aggressive price point, by all means, buy the Lumia 800. Especially the blue one. I predict that is going to be a phone you can put on the table and everyone will know what it is – pretty soon.

Concluding thoughts

If this is what Nokia can crank out in 8 months, I really wonder what will happen in 2012. But even more important than what they crank out, is how they do it. They really got their marketing act together, and it looks like we are finally going to see some serious marketing for the Windows Phone 7 environment. I see posters, dummy’s and demo models of actual Windows Phone 7 devices appearing in shops. Someone from the #wp7nl gang who has a Lumia too walked into a Vodafone shop to get a microsim and the staff all recognized the phone immediately.

The time for Windows Phone 7 being an ‘underground’ phone platform is over!