I struggle with my Live Tiles. It seems like such an easy proposition, and indeed, all of the samples make it look trivial! As long as you are just setting text and a static image, you'll have no problem. As soon as you want to generate dynamic images you will have problems!
There are a few things to remember. First of all, you will usually want to use a UserControl
for layout and rendering of your content, then use WriteableBitmap
to encode and save the bitmap. Ideally you should use an extension package to encode as PNG in order to get a transparent background (especially with this being so popular in WP 8.1). As long as you generated the tile content from within the app you are probably able to run it from a click handler or within a dispatcher pretty easily (you must have a dispatcher to create a UserControl
). If you are in a background agent, or on a background thread in the main app then you will get a cross-thread access exception. From the agent you can't just get the current dispatcher like you can from within a control. Not exactly the same anyway. Instead of getting it from another control, just take it from the current deployment:
That part's easy! What's not so easy is what happens if you include an image in your control. You'll notice that the rendered tile is blank where the image should be. This is incredibly frustrating to figure out! If you test from within the app it will look fine. You might get sites telling you to just add arbitrary delays to fix this. It might even work, but it's not a great way to do it. Instead, you need to subscribe to the image's ImageOpened
event. Once this fires (or the ImageFailed
fires) you know that it's ready to save as a bitmap. This causes some issues with synchronization, but in an agent, remember that the agent isn't complete until you call NotifyComplete
(), so just make sure it's wherever the code is actually done.
The next thing to worry about is databinding. If you set the data context, you expect your fields to be bound! Unfortunately, that's not always the case. For example, any "simple" scenarios such as TextBlock.Text
will look fine. Issues come in with "multi-level" scenarios such as ItemControl
items and TextBlock
Run's. What I discovered is that the binding all starts to take place, which is to say, an ItemsControl
will create four templated items if there are four items in the collection, but the individual fields will never bind to the context. If you set a FallbackValue
, you will see that come through, but never the bound value. In the same vein, a TextBlock
with Run elements for text control, will show only the FallbackValue
for each Run
. It's incredibly frustrating! Unfortunately, I haven't found a solution. I tried binding to many different events to see when the actual binding takes place, and if I run the UserControl
within a running instance of the app it looks perfect, but I had to give up from an agent.
The solution is to convert a TextBlock
with Run elements to a StackPanel
elements. Even worse, for an ItemsControl
you need to actually create static controls for each item which should be generated dynamically! This looks stupid in the XAML, but works fine. Key to remember, is that you can use array indexing within binding. For example, if your ItemsSource
would have been "People" and in your GeneratedItem
template you bound to "Name", then you can bind to "People.Name". Really! It works. Of course it will cause silent errors if you go higher in index than actual values, but it won't cause issues with the final image.
It would be great if Microsoft gave us a way to pass a control type and data context to an UpdateLiveTile
method, but until then, you will save much frustration by remembering these lessons I've learned!