Customize a Windows Phone 7 Live Tile

Windows Phone 7 “Mango” adds a lot of great new features to Live Tiles.  An app can now create Secondary Tiles that are controlled programmatically and deep link directly into the app.  One feature that I could not find, however, is the ability to control the appearance of text on a tile.  This seems to be particularly important given that the background of the tile can now be updated to display additional information.  It is possible, however, to generate a tile on the fly and display it, thus giving greater control of the appearance of the tile.

The general process we will follow:

1. Create a WriteableBitmap
2. Grab images from our app Content (optional)
3. Create and format some UI Elements to add to our Bitmap
4. Build our Bitmap how we like it
5. Save our Bitmap to our Phone
6. Create our Secondary Tile and display our Bitmap on the front or back of the Tile.

As the basis of my app, I’ve created a solution with a content item I want to display on my Tile …

Sample Solution

I create a WriteableBitmap of the correct dimensions and a UI Element to host the Bitmap …

 var bmp = new WriteableBitmap(173, 173);

var logo = new BitmapImage(new Uri("/Content/dpe_green.png", UriKind.Relative));
var img = new Image { Source = logo };

// Force the bitmapimage to load it's properties so the transform will work
 logo.CreateOptions = BitmapCreateOptions.None;

The size of a Live Tile is 173 x 173.  A digression here.  The little trick I pulled with logo.CreateOptions.  Normally, the image will not be loaded until it is displayed.  It’s a Silverlight thang.  But, I want to load the image immediately so that I can understand its size and use that information in my render transform.  Setting the property to BitmapCreateOptions.Noneforces the image to load immediately and thus the PixelHeight and PixelWidth properties become available.  Note, this has performance implications especially if you are loading lots of images.  [This is apparently no guarantee that the image will be loaded. Will update. In the meantime, see comments. – JPA]

Next, we create the UI Elements that we want to push into our WriteableBitmap using the Render method.  We will need to format our Tile to our liking.  We will also need to add our image to our bitmap and position it properly.  In order to do that, we will need to use Transforms.  I’m lazy and uncreative, so I will use a TranslateTransform, but the idea is the same even if you use a MatrixTransform.

 var bl = new TextBlock();
bl.Foreground = new SolidColorBrush(Colors.White);
bl.FontSize = 24.0;
bl.Text = "Hello Tile!";

bmp.Render(bl, null);

var tt = new TranslateTransform();
tt.X = 173 - logo.PixelWidth;
tt.Y = 173 - logo.PixelHeight;
          
bmp.Render(img, tt);
            
bmp.Invalidate();

The fist thing I do is create a TextBlock.  This will allow me to style my text in any way I want.  It gives me more flexibiltiy than setting the Tile’s BackContent.  We set a few properties to get the text to look the way we want it and push it into the bitmap by calling Render.  Next we setup the transform.  This is where we needed to know the size of the image we loaded.  We push that into the bitmap calling Render and passing our transform.  We then signal the Bitmap that it needs to redraw itself.  (When all else fails, Invalidate the Bitmap Smile).

Next is a little bit of complexity with IsolatedStorage.  In addition to providing local storage for our application, on the Phone, IsolatedStorage has some shared areas.  Specifically, the Shared/ShellContent section.  We need to store our Bitmap in this area in order to be used by a Tile.

 using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
    var filename = "/Shared/ShellContent/testtile.jpg";
    using (var st = new IsolatedStorageFileStream(filename, FileMode.Create, FileAccess.Write, store))
    {
        bmp.SaveJpeg(st, 173, 173, 0, 100);
    }
}

It’s not hard to do that .. we just do the standard IsolatedStorage process and use our Media Extension Methods to save off our JPEG.  If you run this, you can now see, using the IsolatedStorage Explorer Tool that our JPEG is in the correct place to be used by our Live Tile.  NB: A Live Tile cannot access an application’s IsolatedStorage!

Last but not least, we build our Tile …

 var tile = new StandardTileData();

tile.BackgroundImage = new Uri("isostore:/Shared/ShellContent/testtile.jpg", 
                                                              UriKind.Absolute);
tile.BackTitle = "Back";

ShellTile.Create(new Uri("/MainPage.xaml?DefaultTitle=FromSecondaryTile", 
                                                            UriKind.Relative), tile ); 

You will note that we need to use the isostore URI scheme to access the data in the shared locations.  The URI in this case that the Tile is created with is the location within your app that will be launched when the Tile is clicked by the user.  You will note that you can pass parameters if desired.

That’s it … sorry about the goofyness of my Tile …

My Goofy Tile

A little bit of complexity, but the trade-off is a lot of control over how your Live Tiles look.