Customizing WP7 Push Notification Tiles

Posted on August 16, 2010 by

This entry is part 5 of 8 in the series Windows Phone 7

Although I had originally hoped to be one of the first, there are LOTs of great articles out now about how to use Push Notifications in Windows Phone 7. Instead of re-hashing information that is already readily available on the internet, this article will instead focus on some of the things that have been left out of those articles.  If you need a refresher on how Push Notifications work, I will refer you to the following excellent articles that show you how to get it done:

My situation seemed really simple – I really liked the idea of being able to publish notifications to my WP7 applications, but didn’t AT ALL like the way they looked when they got there.  As you probably already know from your reading, that the notification system, by default, gives you the ability to customize 3 aspects of a tile – the background image, application title, and a “count”, as illustrated in the picture below:

Each tile is a 173×173 block that contains the three key elements.  My issue was with that little blue circle at the top.  It’s cool how the Outlook, Phone and Text tiles have very big, easy to read information, but I found it more difficult for my eyes to read the small number in the upper-right-hand corner of the tile. Instead, I’d rather have something that looked more awesome, like this:

Untitled

I spoke with some guys from the WP7 dev team, and they confirmed that there is no way to customize the tile layout beyond what it gives you. So that meant, any customization I needed to do would have to be done using dynamically-built graphics… My reasoning is this – the device can only get 3 pieces of information, and the graphics are the most customizable of the bunch. If I take all the information that would normally be sent down as 3 separate data points and combine it into one dynamically-built image, I can make the tile look like whatever I like.

Where to begin…

I started by downloading Yochay’s excellent Windows Phone 7 Training Kit and extracting out his lab on Push Notifications.

This lab is an EXCELLENT primer on Push Notifications in WP7, and I highly recommend that you walk through it before you try doing anything else with Push. His sample got me the boilerplate code I needed to begin my customizations. I was especially happy that he extracted all of the heavy lifting related to the actual Push into a separate assembly that I could start hacking into without fear of messing up the driver or the phone application itself. 

The architecture of the sample is really straightforward – you have a phone application that registers with the Live Notification Services and a WPF-based Driver program that pushes Tile, Toast and Raw updates down to the device.  It seemed like the most straightforward path was to modify the Driver program to somehow send out custom generated background images instead of referencing some existing static ones.  Unfortunately, that’s not how the Push Notification system works.  When you send a notification to the Phone, the phone doesn’t actually receive the image – it receives a URL pointing to the image:

<?xml version="1.0" encoding="utf-8"?>
<wp:Notification xmlns:wp="WPNotification">
  <wp:Tile?>
    <wp:BackgroundImage>/Images/Cloudy.png</wp:BackgroundImage>
    <wp:Count>28</wp:Count>
    <wp:Title>Moscow</wp:Title>
  </wp:Tile>
</wp:Notification>

To accommodate a pull scenario from the device, I was going to need a way for the device to request images based on some parameters.  For that, I’d need a new web site with a custom HTTP handler that built the images I needed based on those parameters – something on the order of this:

<?xml version="1.0" encoding="utf-8"?>
<wp:Notification xmlns:wp="WPNotification">
  <wp:Tile?>
    <wp:BackgroundImage>http://somehost.com/ImageBuilder.ashx?city=Moscow&temperature=28&conditions=Cloudy</wp:BackgroundImage>
    <wp:Count>28</wp:Count>
    <wp:Title>Moscow</wp:Title>
  </wp:Tile>
</wp:Notification>

Generating custom images

For this I thought of 2 approaches – one was to use the same technology that we’ve been talking about since .NET 1.1 days and use the Graphics class inside an HTTP Handler to generate images according to a set of parameters, or I could go out on a limb and utilize the power of WPF on the server-side to use XAML to layout our custom image and use Data Binding to wire up my custom data.  I chose the latter, because that’s how I roll Winking smile

I started with some code that Cori Drew pointed me at written by Laurent Bugnion (author of my most favorite MVVM Light framework) from his article Converting and customizing XAML to PNG with server-side WPF. His code sample included a library that makes short work of taking XAML from an external file and, through the setting of WPF Dependency Properties, render the XAML to a PNG based on the data values we pass in.

First, I added a web project to the sample’s solution and created a new HTTP Handler.  In here, we have to gather the information from the call to the handler to determine what we're supposed to do:

string xamlFileName = "Default";

var _parms = request.FilePath.Split(Char.Parse("/"));
var _filename = _parms[_parms.Length - 1];
var _parts = _filename.Split(Char.Parse("_"), Char.Parse("."));
var _city = _parts[0];
var _temperature = _parts[1];
var _conditions = _parts[2];

var _backgroundImage = String.Format(
    "http://{0}{1}/BackgroundImages/{2}.png",
    request.Url.Host,
    request.ApplicationPath,
    _conditions);

I tried using QueryString parameters to keep it a bit simpler, but that didn’t seem to work for me. I’m sure it was just some silly user error, but I decided to try and be a bit clever and munge the filename instead – something like http://myserver.com/ImageGenerator/10_London_Snow.tile.  Since we want to include a graphic indicating the type of weather we’re describing, I saved off all of Yochay’s images and reconstructed a URL to the static image using our good friend String.Format.

Once we know all the parameters, we need to load the XAML file from the server and call out to the replacement logic to update it’s dependency properties based on our supplied parameters.  Here’s a sample of the code I’m using to load the XAML and set these properties:

FileInfo xamlFile = new FileInfo(context.Server.MapPath(string.Format("~/XAML/{0}.xaml", xamlFileName)));

List<DependencyPropertyReplacement> replacements = null;

string[] customizeElements = new string[5]
{
    String.Format("BackgroundImage:{0}", _backgroundImage.Replace(":", "~")),
    String.Format("Temperature:{0}°", _temperature),
    String.Format("City:{0}", _city),
    String.Format("Conditions:{0}", FormatConditionsString(_conditions)),
    String.Format("LayoutRoot:Black")
};

PrepareCustomizableReplacements(customizeElements, ref replacements);

The method PrepareCustomizableReplacements and his friend MakeCustomizableReplacement use some really interesting code to attach to each of the supplied dependency properties and update them based on their type.  The code is not as clean as it could be, as we could have done some refactoring around the use of the switch statement, but for my purposes this is fine.

private void PrepareCustomizableReplacements(string[] elements,
    ref List<DependencyPropertyReplacement> replacements)
{
    if (replacements == null)
    {
        replacements = new List<DependencyPropertyReplacement>();
    }

    foreach (string nameValue in elements)
    {
        string[] nameValuePair = nameValue.Split(new char[] { ':' });

        // This cannot be handled in a generic way because the DP must be set with a value
        // of the correct type.
        switch (nameValuePair[0])
        {
            case "LayoutRoot":
                replacements.Add(MakeCustomizableReplacement(nameValuePair[0],
                    "Background", WpfUtility.MakeSolidColorBrush(nameValuePair[1], true)));
                break;
            case "BackgroundImage":
                var _image = WpfUtility.MakeBitmapImage(nameValuePair[1].Replace("~", ":"));
                replacements.Add(MakeCustomizableReplacement(nameValuePair[0], "Source", _image));
                break;
            case "Temperature":
            case "Conditions":
            case "City":
                replacements.Add(MakeCustomizableReplacement(nameValuePair[0], "Text", nameValuePair[1]));
                break;
        }
    }
}

private DependencyPropertyReplacement MakeCustomizableReplacement(string elementName, string propertyName, object value)
{
    DependencyPropertyReplacement replacement = new DependencyPropertyReplacement();
    replacement.ElementName = elementName;
    replacement.PropertyName = propertyName;
    replacement.Value = value;
    return replacement;
}

You can see from here that we're looping through each of the customization points that I specified in the last code sample, attaching to the specific dependency property on the XAML control, and updating their value.  There is some custom wizardry that’s going on behind the MakeBitmapImage method that I’ll talk about next.

Fun with images…

I ran into a snag trying to set the source on our Image control. As most of you probably know, we always preach that “anything you can do in XAML you can do in code”, which of course is true, but we don’t necessarily say how easy it’s going to be to create a code equivalent. Take the Image control for example.  It’s really easy for me to set the image up using something like this in XAML:

<Image Source=”MyImage.jpg” />

Now you know that we’re not actually setting the source of the Image object to a string value of “MyImage.jpg” right? We’re creating an Image object and loading it up from the image stored on disc specified in the path provided.  WPF has a really nice set of built-in ValueConverter objects that make things really easy for us to work with “magic strings”.  In my case, I had to do things a bit more manually:

public static object MakeBitmapImage(string filename)
{

    WebClient webClient = new WebClient();
    byte[] imageContent = webClient.DownloadData(filename);

    MemoryStream memoryStream = new MemoryStream(imageContent);

    BitmapImage imageSource = new BitmapImage();
    imageSource.CacheOption = BitmapCacheOption.None;
    imageSource.BeginInit();
    imageSource.StreamSource = memoryStream;
    imageSource.EndInit();

    if (imageSource.CanFreeze)
        imageSource.Freeze();

    return imageSource;
}

You'll first notice that we have to actually fetch the image from the web ourselves. Once we have the image, we need to load it into a BitmapImage object from which we can then assign to the Image.Source dependency property.  The last snag I ran into related to the Freezing of certain properties before you can assign them.  As it turns out, you need to “freeze” BitmapImage objects before you can assign them as a source for an ImageObject due to some interesting thread ownership issues.  I won’t go into detail here, but there are some nice references on StackOverflow and MSDN to help you understand the whys and wherefores.

Once I had the image created, Laurent’s code sample just writes it to the output stream and we’re golden.

    try
    {
        using (Stream stream = File.OpenRead(xamlFile.FullName))
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                XamlToPngConverter converter = new XamlToPngConverter();
                converter.Convert(stream, 173, 173, memoryStream, replacements);

                // set the content type
                context.Response.ContentType = "image/png";
                memoryStream.WriteTo(context.Response.OutputStream);
            }
        }
    }
    catch (Exception ex)
    {
        throw new HttpException(404, "Image not found:" + xamlFileName, ex);
    }

At this point we have a working dynamic image generator. Cool, isn’t it?  Now all we have to do is wire it up to the Driver program so that the phone knows where to go grab the correct tile image.

Sending the right tile

Luckily, the rest of the changes were really minimal. Although there were a few tweaks to other parts of the driver program, the key change is in the definition of the URI:

private void sendTile()
{
    //TODO - Add TILE notifications sending logic here
    string weatherType = cmbWeather.SelectedValue as string;
    int temperature = (int)sld.Value;
    string location = cmbLocation.SelectedValue as string;
    List<Uri> subscribers = RegistrationService.GetSubscribers();
    var _uri = String.Format("http://localhost/ImageGenerator/{0}_{1}_{2}.tile", location, temperature, weatherType);
    ThreadPool.QueueUserWorkItem((unused) => notifier.SendTileNotification(
        subscribers, "PushNotificationsToken",
        _uri, temperature, weatherType.Replace("-", " "), OnMessageSent));
}

Once we had the proper URI for the image (one based on all the parameters we needed to decode) we only had one more important change to make – this time in the NotificationSenderUtility library.

In here, the expected behavior for sending a tile message is to send all three parts of the data – count, title and image.  In our case, we DON’T want the count coming across because it will just mess up our UX.  To accommodate, I just commented out the line that sets the Count value in the outgoing XML and we were good to go:

private static byte[] prepareTilePayload(string tokenId, string backgroundImageUri, int count, string title)
{
    MemoryStream stream = new MemoryStream();

    XmlWriterSettings settings = new XmlWriterSettings() { Indent = true, Encoding = Encoding.UTF8 };
    XmlWriter writer = XmlTextWriter.Create(stream, settings);
    writer.WriteStartDocument();
    writer.WriteStartElement("wp", "Notification", "WPNotification");
    writer.WriteStartElement("wp", "Tile", "WPNotification");
    writer.WriteStartElement("wp", "BackgroundImage", "WPNotification");
    writer.WriteValue(backgroundImageUri);
    writer.WriteEndElement();
    writer.WriteStartElement("wp", "Count", "WPNotification");
    //writer.WriteValue(count.ToString());
    writer.WriteEndElement();
    writer.WriteStartElement("wp", "Title", "WPNotification");
    writer.WriteValue(title);
    writer.WriteEndElement();
    writer.WriteEndDocument();
    writer.Close();

    byte[] payload = stream.ToArray();
    return payload;
}

With these changes in place, everything was wired up and worked exactly as I had hoped it would Smile

Now what?

It’s still not perfect – there is some code cleanup I’d like to do, and the images with text added to them don’t show up as crisp and clear as if we’d used the push infrastructure the way it was designed, but it’s a good start.  If you have any questions, post a comment and lets discuss it.  I’ve posted my code sample on Skydrive, so it should be easy to find:

Happy Push Notification-ing!

Series NavigationWP7 Part 4: Morphing and MappingInputScopes for Windows Phone 7

Comments (14)

 

  1. [...] quiser pode conferir o tutorial clicando no link (em inglês): Customizing WP7 Push Notification Tiles [...]

  2. [...] Chris Koenig napisał na swoim blogu szczegółowy artykuł wyjaśniający jak wyświetlać dane swojego [...]

  3. Hi! I’d love to have your opinion on some code I’ve been writing to help ease the pain of Live Tiles and Toasts (I’m calling it “Entile”). I have a blog post describing it here:
    http://coding-insomnia.com/2011/01/11/introducing-entile-notification-framework/
    and you can download a preview of the code here:
    http://coding-insomnia.com/2011/01/13/preview-of-entile-notification-framework/

    Thanks!

  4. Yes, this has been really helpful. Thanks

  5. John says:

    Great tutorial even if I’m a bit uncertain if I can get it up and running. Where can I find your downloadable sample code?

  6. Helloaummy says:

    So Thank

  7. [...] quiser pode conferir o tutorial clicando no link (em inglês): Customizing WP7 Push Notification Tiles Tweet (function() { var po = document.createElement('script'); po.type = [...]