Showing posts with label Rx. Show all posts
Showing posts with label Rx. Show all posts

30 September 2012

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

On the last day of 2011 ago I made DynamicBackgroundBehavior - a quite bizarre behavior which plays a supporting role in my latest Windows Phone application “What’s cooking”. It basically shows a picture from a search string as a background, using Bing Image search. Quite neat. Until Microsoft decided to change the Bing Search API a bit. Up until then, it had been a free unlimited ride – since about August you have to buy a package of searches (anything below 5000 searches per month is free). And some more stuff has been changed.

Microsoft provides a nice little C# client library that should solve all of this using an OData protocol. The sad thing is that for the life of it I could not get it to compile on Windows Phone. So I ended up analyzing the PHP (*shudder*) sample in this document – a Word document no less – to see what I needed to do (see page 27 and further).

From a coding perspective, the following details have been changed:

Okay. My cheese moved a little. First things first. I need base64 encoded text. Samples of this are on the internet a dime a dozen, I took this one:

/// <summary>
/// Converts string to base64
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
private string Base64Encode(string data)
{
  try
  {
    var encData_byte = new byte[data.Length];
    encData_byte = System.Text.Encoding.UTF8.GetBytes(data);
    return Convert.ToBase64String(encData_byte);
  }
  catch (Exception e)
  {
    throw new Exception("Error in Base64Encode" + e.Message);
  }
}

Now the method that does the actual calling of the Bing Search API has changed a little, but not very much:

/// <summary>
/// Start the image request using Bing Serach
/// </summary>
/// <param name="searchString"></param>
protected void StartGetFirstImage(string searchString)
{
  if (!string.IsNullOrWhiteSpace(searchString))
  {
    var queryUri =
      string.Format(
        "https://api.datamarket.azure.com/Data.ashx/Bing/Search/v1/Image?Query=%27{0}%27&$top=1&$format=Atom",
         HttpUtility.UrlEncode(searchString));
    var request = WebRequest.Create(queryUri) as HttpWebRequest;
    request.Headers["Authorization"] = "Basic " + Base64Encode(string.Format("{0}:{0}", BingSearchKey));
    
    var response =
      Observable.FromAsyncPattern<WebResponse>(
        request.BeginGetResponse, request.EndGetResponse)();
    response.Subscribe(WebClientOpenReadCompleted, WebClientOpenReadError);
  }
}

Note the new url. It goes to a different location, the search term must be enclosed by %27 (i.e. the double quote “) and I have to specify a format (Atom). Really interesting is the thing I marked red, for a variety of reasons:

  • Windows Phone’s Response.Headers collection does not sport the Add method. That stopped me a little, until I realized you could poke directly a new key in the collection by using [“Autorization”] =.
  • Now the authentication is Basic, followed by a space, your account key, colon, your key again and that key:key sequence needs to base 64 encoded. Why this is implemented this way – no idea, but it gets your key across – and validated.

The final thing is that in the callback WebClientOpenReadCompleted I need to use “http://schemas.microsoft.com/ado/2007/08/dataservices” for a namespace,as I already stated.

And then my little Windows Phone behavior works again. The fixed version can be downloaded here. I will fix my #wp7nl library on codeplex ASAP.

20 June 2012

Reflection in WinRT: use Rx Extensions for dynamically adding event handlers

Suppose you want to add an event handler, but you don’t know up front which event. Admittedly, this is a bit of an edge case, but I have used it in a behavior that starts a storyboard upon an event (I will publish that later). The designer can specify the event using a string value in XAML, so I cannot simply use object.MyEvent += (handler). We need reflection here.

So suppose you have found the event in the object using the GetRuntimeXXX methods like I described earlier today, and want to dynamically add a handler to the event “MyEvent”:

var evt = MyObject.GetType().GetRuntimeEvent("MyEvent");
evt.AddEventHandler(MyObject, 
  new EventHandler((p, q) => {// your code here }));

This will compile, and even run - unless the object is, for instance, a Grid, and the event “Loaded”. That’s apparently a “WinRT event”, whatever that may be, and this will result in a runtime exception:

"Adding or removing event handlers dynamically is not supported on WinRT events."

Googling Binging this message leads to all kind of complicated solutions using the WindowsRuntimeMarshall class (without any samples, alas), but the solution turns out to be very simple: use Rx Extensions Beta for Windows RT. For the really lazy reader: click tools/Library Package Manager/Package Manager Console and enter “Install-Package Rx-Main –Pre” in the Package Manager Console.

Now add

using System.Reactive.Linq;

to the top of the code file and then simply use the following code:

Observable.FromEventPattern<RoutedEventArgs>(MyObject, "MyEvent")
  .Subscribe(se => { // your code here });

and you are done. The difference between ordinary events and “WinRT events” is apparently swallowed up in the bowels of Rx. This library is great to begin with, but if it saves you the trouble of digging in yet another still not very thoroughly documented API, it’s even better. As a bonus, by very nature of Rx the handler is automatically disposed of when your calling object goes out of scope, which is not necessarily the case in my first code. But that did not work anyway ;-)

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.