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

3 minute read

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.