Live Tiles are one of the best things about Windows Phone. But, what I find frustrating about developing a Windows Phone app is the lack of Live Tile customization. The SDK does provide some defaults, but you’re limited to an icon and some text regions defined for each tile size. I guess the defaults are okay for most uses, but I really want my tiles to look the way I want them to look and that falls outside the predefined templates.

The good news is, you can create your own custom tiles. The bad news is, it’s a bit tricky, especially when you want to update those tiles. To create my custom tiles, this is what I did (after much frustration because of all of the gotchas):

  • Create a project just for my tiles (this is necessary so that both my app and my scheduled task can use the same code)
  • Create 4 user controls representing the medium tile (front and back) and the wide tile (front and back)
  • Create the logic to render the user controls to images and save them to isolated storage (there’s a trick to this so keep reading)
  • Create a background task agent to update my tiles (again, more sorcery required here too)

That’s a lot of complexity and a lot of code, but for me it’s worth it to have my live tiles totally customized. So let’s get into it. Below is what my project structure looks like.

solution

I have my phone app project at the top, a scheduled task project, my portable class library and my tiles project. Again, the reason for giving the tiles their own project is because both the phone app and the scheduled task will need to reference my tiles and my tile logic.

Within the tile project there are 4 Windows Phone user controls that I created to represent my tiles. This is really nice because you can make use of XAML and the design surface to get your tiles looking just right. Each tile will need a front and back control. In my case, the back of each tile is static text with a custom background color, but the front has a few dynamic elements that need to updated frequently (or every 30 minutes as defined by the SDK).

This is what my medium tile user controls look like:

[![front tile in VS designer](http://res.cloudinary.com/don-of-the-day/image/upload/v1421533011/tile-front_y4vbwk.png)](http://res.cloudinary.com/don-of-the-day/image/upload/v1421533011/tile-front_y4vbwk.png)front tile in VS designer
[![back tile in VS designer](http://res.cloudinary.com/don-of-the-day/image/upload/v1421533009/tile-back_x3qddl.png)](http://res.cloudinary.com/don-of-the-day/image/upload/v1421533009/tile-back_x3qddl.png)back tile in VS designer
That’s what my tiles look like in the VS designer (and yes, I’m building a countdown app). For each control, I set public properties so that I can set my values later.

public partial class MediumTile : UserControl { public MediumTile() { InitializeComponent(); } public string Name { set { this.uxName.Text = value; } get { return this.uxName.Text; } } public string Count { set { this.uxCountValue.Text = value; } get { return this.uxCountValue.Text; } } public Brush BackgroundColor { set { this.uxBackGroundCanvas.Background = value; } } public string TimeLabel { set { this.uxTimeLabel.Text = value; } } }

To generate my tiles, I created a tile manager class called…TileManager. This is where I instantiate my user controls, set values and then render and save the images (this is also where things get messy). Here’s the important bits and note that much of this code was adapted from the Nokia Development Wiki:

This is the method I call to create my tiles:

public static void SetDetailTile(CountdownItem item) { Uri navigationUri = new Uri("/Pages/Detail.xaml?id=" + item.Id.ToString() + "&from=tile", UriKind.Relative); var flipTileData = new FlipTileData { Title = "", BackContent = "", WideBackContent = "", BackTitle = "", Count = 0 }; //create medium and wide tile images RenderMediumTileImage(item); RenderWideTileImage(item); flipTileData.BackgroundImage = new Uri("isostore:/Shared/ShellContent/" + item.Id.ToString() + "-mediumtile.jpg"); flipTileData.BackBackgroundImage = new Uri("isostore:/Shared/ShellContent/" + item.Id.ToString() + "-mediumbacktile.jpg"); flipTileData.WideBackgroundImage = new Uri("isostore:/Shared/ShellContent/" + item.Id.ToString() + "-widetile.jpg"); flipTileData.WideBackBackgroundImage = new Uri("isostore:/Shared/ShellContent/" + item.Id.ToString() + "-widebacktile.jpg"); // Get the tile if it exists var tile = ShellTile.ActiveTiles.FirstOrDefault(t => t.NavigationUri == navigationUri); // Create the tile if it doesn't exist if (tile == null) { // create a new tile ShellTile.Create(navigationUri, flipTileData, true); } // Update existing tile else { tile.Update(flipTileData); } }

First I set the default content properties to empty strings, then I render the images (see below) and then I pull the images from isolated storage. **Important Note: ** You must save and retrieve your custom tiles from the “/Shared/ShellContent” directory or you will not see images. For whatever reason, the tiles must come from that directory. This caused me hours of frustration and many a Google search to figure out. Once I have my images, I save or update my tiles.

But, before we retrieve images or save tiles, we must create images. I’m going to break this down a bit to explain each part.

private static void RenderMediumTileImage(CountdownItem item) { WriteableBitmap frontTile = new WriteableBitmap(336, 336); MediumTile tile = new MediumTile(); TimeSpan timeSpan = item.EndDate.Subtract(DateTime.Now); //other things that are app specific

Here I create a WritableBitmap that ultimately represents the image and get an instance of the tile control (MediumTile). Next I set some tile properties and do something very important.

tile.Name = item.Name.Length >= 18 ? item.Name.Substring(0, 18) + "..." : item.Name; tile.BackgroundColor = new SolidColorBrush(ColorHelper.ConvertColor(item.AccentColor)); tile.UpdateLayout(); tile.Measure(new Size(336, 336)); tile.UpdateLayout(); tile.Arrange(new Rect(0, 0, 336, 336)); frontTile.Render(tile.LayoutRoot, null); frontTile.Invalidate(); //Draw bitmap

This is critical. After setting your tile properties, you must update the layout, measure it, update again, arrange it and chant “Mekah lekah high, mekah hinney ho”. Okay, not the last part, but you really do have to all of this updating and arranging to make sure the control renders to the image properly. If you don’t, it will look funky. Next, more magical things that will cost you time and frustration if you are unaware.

backImage.Render(backTile.LayoutRoot, null); backImage.Invalidate(); SaveTileImage(item.Id.ToString() + "-mediumbacktile.jpg", backImage); backImage = null; frontTile = null; tile = null; backTile = null; GC.Collect();

Next, I save the Image (I’ll show the code for that below). The invalidate method of the WriteableBitmap actually draws it (oddly named, yes). The crucial part is what comes after the image is saved. Because I’m also going to call this method from my ScheduledTask, which has serious time and memory limitations, we have to GC.Collect() manually and I honestly don’t know why, but I do know that processing multiple tiles without manual garbage collection causes an OutOfMemoryException from the ScheduledTask. I can only assume the garbadge collector doesn’t free up the image resources fast enough, quickly maxing out the SheduledTasks meager allotment. Once I made the controls and the images null and collected the trash, all was well. Took me many hours to figure that one out too.

Oh, and here’s the code to save the image (again, it must be saved to “/Shared/ShellContent”):

private static void SaveTileImage(string fileName, WriteableBitmap b) { //Save bitmap as jpeg file in Isolated Storage using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication()) { using (IsolatedStorageFileStream imageStream = new IsolatedStorageFileStream("/Shared/ShellContent/" + fileName, System.IO.FileMode.Create, isf)) { b.SaveJpeg(imageStream, b.PixelWidth, b.PixelHeight, 0, 100); } } }

And the final gotcha: if you use a scheduled task to update your tiles, you must execute your tile update code on the UI thread because we are using user controls and they need their UI thread to work. See below.

protected override async void OnInvoke(ScheduledTask task) { try { var tiles = ShellTile.ActiveTiles.ToList(); CountdownViewModel vm = new CountdownViewModel(new CountdownService()); if (!vm.IsDataLoaded) await vm.LoadCountdownItems(); foreach (var tile in tiles) { string query = tile.NavigationUri.OriginalString; if (query != "/") { string id = tile.NavigationUri.ToString(); string[] idPart = id.Split(new string[] { "=" }, StringSplitOptions.None); var item = vm.GetCountdownItem(new Guid(idPart[1])); if (item != null) { EventWaitHandle Wait = new AutoResetEvent(false); //Execute in the context of the UI thread Deployment.Current.Dispatcher.BeginInvoke(() => { TileManager.SetDetailTile(item); Wait.Set(); }); Wait.WaitOne(); } } } #if DEBUG_AGENT ScheduledActionService.LaunchForTest(task.Name, TimeSpan.FromSeconds(30)); System.Diagnostics.Debug.WriteLine("Periodic task is started again: " + task.Name); #endif } catch (Exception ex) { //log the exception } finally { NotifyComplete(); } }

That’s a lot of hoops to jump through to get custom tiles, but in the end, it’s worth it, at least to me. To recap, the main gotchas are:

  • You can create a user control and render it to an image as a live tile, you just have to update the layout, arrange it, etc.
  • Images must be stored in isolated storage under “/Shared/ShellContent”.
  • When using a ScheduledTask to update your custom tiles, you must execute on the UI thread and manually perform garbage collection.
 
Here’s the final product (still needs refining). [![tiles](http://res.cloudinary.com/don-of-the-day/image/upload/v1421533007/tiles_khclmb.png)](http://res.cloudinary.com/don-of-the-day/image/upload/v1421533007/tiles_khclmb.png)