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
Opacity 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.
Put 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.
No comments:
Post a Comment