17 October 2011

A behavior to show the Windows Phone 7 camera as background

This article was updated October 29, 2011, implementing correctly handling of “back” navigation

In a previous post I showed how to use the camera feed as background on a page of a Windows Phone 7 application. Today, I make it even easier for the really really lazy programmer: I have created a behavior to do the same. As a bonus, this behavior also takes the page orientation into account and whether the SystemTray is visible or not.

First order of business: make a basic behavior. Using the snippet from my previous safe event detachment pattern for behaviors the initial setup looks like this:

namespace Wp7nl.Behaviors
{
  /// <summary>
  /// A behavior that shows a camera view on the background of a panel
  /// </summary>
  public class CameraViewBackgroundBehavior : Behavior<Panel>
  {
    private PhotoCamera camera;
    private PhoneApplicationFrame parentPage;
    private Uri pageSource;
    private VideoBrush backgroundBrush;

    // Setup and cleanup according to http://bit.ly/dZb6D9

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

    private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
    {
      DoInit();
    }
    #endregion

    #region Cleanup
    private bool isCleanedUp;

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

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

    private void AssociatedObjectUnloaded(object sender, RoutedEventArgs e)
    {
      Cleanup();
    }
    #endregion
  }
}
With sincere apologies to region haters ;-). Nothing special here, just a skeleton behavior on a Panel. From the AssociatedObjectLoaded a method "DoInit" is called, which does actually all that’s needed to get this behavior going:
/// <summary>
/// Initializes the behavior
/// </summary>
private void DoInit()
{
  // Find the page this control is on and listen to its orientation changed events
  if( parentPage == null)
  {
    parentPage = Application.Current.RootVisual as PhoneApplicationFrame;
    pageSource = parentPage.CurrentSource;
    parentPage.Navigated += ParentPageNavigated;
  }  
  parentPage.OrientationChanged += ParentPageOrientationChanged;

  camera = new PhotoCamera();

  // Create a video brush with the right parameters
  backgroundBrush = new VideoBrush();
  backgroundBrush.Stretch = Stretch.UniformToFill;
  backgroundBrush.AlignmentX = AlignmentX.Left;
  backgroundBrush.AlignmentY = AlignmentY.Top;

  // Set the video brush to the background of the panel 
  // and do an initial display
  AssociatedObject.Background = backgroundBrush;
  backgroundBrush.SetSource(camera);
  SetVideoOrientation(parentPage.Orientation);
}

The comments in the source show pretty much what it does: it starts listening to page orientation change events, it sets up the video brush (but now from code in stead of from XAML) and sets the brush to the background of the panel.

Of course, it has its counterpart disabling all settings again:

/// <summary>
/// Safely detach all extra events
/// </summary>
private void DoCleanup()
{
  parentPage.OrientationChanged -= ParentPageOrientationChanged;
  camera.Dispose();
}

To make sure the behavior initializes correctly again when the user navigates back to the page with this behavior on it, the following code is needed (and this is the part I missed myself in the first version of this post)

/// <summary>
/// Re-init when user navigates back to this page
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ParentPageNavigated(object sender, NavigationEventArgs e)
{
  if (e.NavigationMode == NavigationMode.Back && e.Uri.Equals(pageSource))
  {
    isCleanedUp = false;
    AssociatedObject.Loaded += AssociatedObjectLoaded;
    AssociatedObject.Unloaded += AssociatedObjectUnloaded;
  }
}

I appears a behavior’s OnAttached is not called when a user navigates back to a page, resulting in the video not being displayed on the background. Anyway, to wrap up the code, you simply need this:

private void ParentPageOrientationChanged(object sender, 
                                          OrientationChangedEventArgs e)
{
  SetVideoOrientation(e.Orientation);
}

/// <summary>
/// Sets background video brush parameters based upon page orientation
/// </summary>
/// <param name="orientation"></param>
private void SetVideoOrientation(PageOrientation orientation)
{
  System.Diagnostics.Debug.WriteLine("Switching to {0}", orientation);
  switch (orientation)
  {
  case PageOrientation.PortraitUp:
    backgroundBrush.Transform = 
      new CompositeTransform { Rotation = 90, TranslateX = 480 };
    break;
  case PageOrientation.LandscapeLeft:
    backgroundBrush.Transform = null;
    break;
  case PageOrientation.LandscapeRight:
    if (Microsoft.Phone.Shell.SystemTray.IsVisible)
    {
    backgroundBrush.Transform = 
      new CompositeTransform { Rotation = 180, TranslateX = 728, TranslateY = 480 };
    }
    else
    {
    backgroundBrush.Transform = 
      new CompositeTransform { Rotation = 180, TranslateX = 800, TranslateY = 480 };
    }
    break;
  }
}

Based upon the way the page is oriented, a different translation and/or rotation is applied. The number are easy enough to recognize, 800 being the vertical resolution and 480 the horizontal resolution of a Windows Phone 7 screen. Apparently a system tray takes about 62 pixels – at least, that was the value I got by trial-and-error till the camera view filled the whole screen background. Bear in mind that although this will probably work on any panel, the numbers only make sense if you add this behavior to it’s top panel.

Now its simply a matter of dragging this behavior on top of your top panel using expression Blend, and boom – instant camera background. No coding necessary, and a clean and empty code behind file. You big coder hero makes friends with designer ;-)

Code file can be downloaded here. I will soon add this to the wp7nl library on codeplex

No comments: