31 December 2011

A Windows Phone 7 behavior to show an image background for a search string

Note: an update to this article has been written here

On New Year’s Eve I can’t help but writing this last bit of 2011: yesterday on a #wp7nl developer’s meet up, which was basically a free-for-all fun hacking event organized by Matthijs Hoekstra, I wrote a little thingy for Windows Phone 7 that accepts a string, tries to find an image for it using Bing Image search and displays it as a background. Of course it’s a behavior – I write behaviors a dozen, because the concept of reusable dynamic behavior is something that fits very well with the way I think. Call me the behaviornator if you like ;-)

It’s very simple, it’s quite fun, demonstrates a little Rx usage, makes quite unusual use of the Bing Image Search api - and it will play a supporting act in my newest Windows Phone 7 app. The basic structure of the behavior is set up utilizing the #wp7nl library SafeBehavior that I described earlier:

using System;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Xml;
using System.Xml.Linq;
using Microsoft.Phone.Reactive;
 
namespace Wp7nl.Behaviors
{
  /// <summary>
  /// A behavior that puts an image on the background of the Attched object
  /// using Bing Image Search
  /// </summary>
  public class DynamicBackgroundBehavior : SafeBehavior<Panel>
  {
    private ImageBrush backgroundBrush;
    
    public DynamicBackgroundBehavior()
    {
      Opacity = 1.0;
    }

    #region SearchString
    public const string SearchStringPropertyName = "SearchString";

    /// <summary>
    /// The search string to be used on Bing Maps
    /// </summary>
    public string SearchString
    {
      get { return (string)GetValue(SearchStringProperty); }
      set { SetValue(SearchStringProperty, value); }
    }

    public static readonly DependencyProperty SearchStringProperty = 
        DependencyProperty.Register(
        SearchStringPropertyName,
        typeof(string),
        typeof(DynamicBackgroundBehavior),
        new PropertyMetadata(String.Empty, SearchStringChanged));

    public static void SearchStringChanged(DependencyObject d, 
                                           DependencyPropertyChangedEventArgs e)
    {
      var behavior = d as DynamicBackgroundBehavior;
      if (behavior != null)
      {
        behavior.StartGetFirstImage((string)e.NewValue);
      }
    }
    #endregion

    /// <summary>
    /// Bing search key
    /// </summary>
    public string BingSearchKey { get; set; }

    /// <summary>
    /// Stretch used for the image
    /// </summary>
    public Stretch Stretch { get; set; }

    /// <summary>
    /// Opacity used for the image
    /// </summary>
    public double Opacity { get; set; }
  }
}

In short, this behavior has four properties:

  • Opacity
  • Stretch
  • BingSearchKey
  • SearchString

BingSearchApiOpacity and Stretch are just simple properties for the images that is going to be displayed. SearchString is the string that’s going to be used to find an image for. This is a dependency property – so it can be used in data binding. Note the SearchStringChanged method – this is called when the SearchString property changes. That in turn is firing the method StartGetFirstImage, that will start the actual search.

BingSearchKey is a 40-character long string identifying your application. You have to create a key for your application on the Bing Developer portal. Click the left button (“Sign in – Bing Search API”) and fill in the form depicted to the right(click for larger image).

Note: the behavior references Microsoft.Phone.Reactive – so the project holding this should Microsoft.Phone.Reactive.dll and System.Observable.dll

Moving on to the setup of the behavior, which is very simple now since we are building upon the SafeBehavior:

/// <summary>
/// Setup the behavior
/// </summary>
protected override void OnSetup()
{
  backgroundBrush = new ImageBrush
  {
    Stretch = Stretch,
    Opacity = Opacity
  };

  // Set the image brush to the background of the Panel 
  AssociatedObject.Background = backgroundBrush;
}

Simply put: create an Image brush using the property settings, and put it as background on the GUI element to which the behavior is attached.

The method that’s called from SearchStringChanged (which is fired as the SearchString dependency property changes) is implemented as showed below:

/// <summary>
/// Start the image request using Bing Serach
/// </summary>
/// <param name="searchString"></param>
protected void StartGetFirstImage(string searchString)
{
  var queryUri = 
    string.Format(
      "http://api.bing.net/xml.aspx?Appid={0}&query={1}&sources=image",
      BingSearchKey, searchString);
  var request = WebRequest.Create(queryUri) as HttpWebRequest;
  var response = 
    Observable.FromAsyncPattern<WebResponse>(
      request.BeginGetResponse, request.EndGetResponse)();
  response.Subscribe(WebClientOpenReadCompleted, WebClientOpenReadError);
}

An Uri is formed using the BingSearchKey and the actual search string – and the clause “sources=image”, telling Bing to return images. That Uri is fed to a standard WebRequest. And then the Rx framework comes into play to easily process the async read process. The Observable.FromAsyncPattern and Subscribe usage has the distinct advantage of not having to attach all kind of event handlers, trap errors with try-catch blocks, and not forgetting to detach the event handlers when the reading is done. The Rx framework handles this all, so I don’t have to worry about that.

The final piece of the behavior – the actual processing of the image:

/// <summary>
/// Called when image search returns
/// </summary>
/// <param name="result"></param>
private void WebClientOpenReadCompleted(WebResponse result)
{
  using (var stream = result.GetResponseStream())
  {
    using (var reader = XmlReader.Create(stream,
       new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }))
    {
      var doc = XDocument.Load(reader);

      // Get the first image from the result
      XNamespace ns = "http://schemas.microsoft.com/LiveSearch/2008/04/XML/multimedia";
      if (doc.Root != null)
      {
        var firstImage = doc.Root.Descendants(ns + "MediaUrl").FirstOrDefault();
        if (firstImage != null)
        {
          Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
              var bi = new BitmapImage
                {
                  UriSource = new Uri(firstImage.Value),
                  CreateOptions = BitmapCreateOptions.BackgroundCreation
                };
              backgroundBrush.ImageSource = bi;
            });
        }
      }
    }
  }
}

/// <summary>
/// Called upon a search error (not used)
/// </summary>
/// <param name="ex"></param>
private void WebClientOpenReadError(Exception ex)
{
}

Since the xml.aspx page is referenced, Bing returns the result as a xml document. If you are interested in the details of the Bing Search result, feel free to explore the xml document – this code basically just finds the first “MediaUrl” tag, makes a BitMapImage from it, and puts the result into the backgroundBrush. And we’re done.

carrotdemoPut this behavior on a descendant of Panel (a Grid, for instance), fill the BingSearchKey property with a valid key, databind the “SearchString” property to a string in a ViewModel and as soon as the value of SearchString changes, the behavior will show the first available image returned by Bing Image search as a background on that grid.

I’ve put together a small demo application containing and demonstrating the behavior. It deviates in two ways from my usual mode of operation. First, does not run out of the box – you will have to get your own Bing Search API key first. Second: it does not use MVVM – I’ve data bound the behavior’s SearchString property directly to a TextBox’s Text property, which makes the behavior start to search for background immediately as you start typing, as showed to the left. So if you type “carrot” in the textbox you get, well – an image showing carrots ;-)

 

Well, that’s all for 2011. A very special year from me with some ups and downs, with getting a Windows Phone Development MVP award definitely being the top event in the "ups" category. Now onwards to 2012, which I think will prove to be a very exciting year indeed. I hope you all will continue to enjoy this blog as I did - and you apparently did in 2011 as well.

03 December 2011

Re-imagining the behavior to show the Windows Phone 7 camera as background

This blog turns out not only to be a collection of how-to samples but also a record of my evolution as a Windows Phone 7 developer. At the end of my previous post, Safe event detachment base class for Windows Phone 7 behaviors, I promised a usage example and I decided to re-implement the behavior to show the Windows Phone 7 camera as background using the SafeBehavior as a base class. This makes the code considerable easier.

The base setup of the re-implemented behavior is pretty simple:

using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Navigation;
using Microsoft.Devices;
using Microsoft.Phone.Controls;

namespace Wp7nl.Behaviors
{
  /// <summary>
  /// A behavior that shows a camera view on the background of a panel
  /// </summary>
  public class CameraViewBackgroundBehavior : SafeBehavior<Panel>
  {

    private PhotoCamera camera;
    private VideoBrush backgroundBrush;

    public CameraViewBackgroundBehavior()
    {
      ListenToPageBackEvent = true;
    }
  }
}

Note this behavior needs to detect the user navigating back to the page – this in necessary because we need to do something with the camera.

In stead of all the song and dance for attaching and detaching events using the snippet I published earlier, it’s now a matter of overriding the OnSetup and OnCleanup methods to initialize the camera:

protected override void OnSetup()
{
  if (camera == null)
  {
    camera = new PhotoCamera();
    ParentPage.OrientationChanged += ParentPageOrientationChanged;
  }

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

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

protected override void OnCleanup()
{
  ParentPage.OrientationChanged -= ParentPageOrientationChanged;
  camera.Dispose();
  camera = null;
}

This behavior also needs to do some action when the user actually navigates back to the page, which you can do by override the OnParentPageNavigated method - in this case, re-initializing the whole behavior

/// <summary>
/// Fired whe page navigation happens
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected override void OnParentPageNavigated(object sender, NavigationEventArgs e)
{
  // Re-setup when this page is navigated BACK to
 if( IsNavigatingBackToBehaviorPage(e))
 {
   if (camera != null)
   {
     OnCleanup();
     OnSetup();
   }
  }
}

The actual implementation of showing the camera background has hardly changed, and is only mentioned here for the sake of completeness:

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;
  }
}

As this post demonstrated, using the SafeBehavior as a base class makes life a lot easier than implementing the whole pattern over and over again, even when using a snippet.

Code is part of the the #wp7nl library on codeplex and can be found here