Saturday 24 March 2012

Live Tiles revisited

Back in December, I wrote about dynamically generating live tiles on the phone from a xaml template.  I released first live tiles application Terminator which shows sunrise and sunset around the world using NASA blue marble imagery.  Some of our customers asked for sunrise and sunset times, and where better than on a live tile.

However, occasionally the tile would not update correctly using code based on that I showed back in December. 

The symptoms were odd.  The layout was all over the place, and as I started changing things, I found that things were about to get even more peculiar.

Originally, the tile was defined as a user control with a defined height and width of 173 pixels, containing a grid with some auto resize columns.  So, I simplified my control to use a canvas as follows:

<Canvas Background="Red" >
<Image Canvas.Left="0" Canvas.Top="27"
Width="173" Height="89" x:Name="imgSunrise"
Source="/images/lowsunrise.png"/>

<TextBlock Canvas.Left="12" TextAlignment="Right"
Width="149" Text="{Binding SunriseTime}"/>
<TextBlock Canvas.Left="12">Sunrise</TextBlock>
<TextBlock Canvas.Left="12" Canvas.Top="27" TextAlignment="Right"
Width="149" Text="{Binding SunsetTime}"/>
<TextBlock Canvas.Left="12" Canvas.Top="27" >Sunset</TextBlock>
<TextBlock Canvas.Left="12" Canvas.Top="112"
Width="149" TextWrapping="Wrap" Text="{Binding Location}" />
</Canvas>

Previously, my control was rendering on a transparent background so that it would match the theme the user had selected, but I thought I’d try it with a fixed background so I could see what was happening.


What I found was that occasionally, my color was not being applied to my canvas, and it was still showing through with the theme color.


That discovery led me to believe that the layout had not yet completed.


Now, as was described back in December, the behavior of the notifications changes based on where they are run.  In the user agent, the load happens immediately, when the tile is used on a panel, it gets run multiple times.


My original strategy was to handle the change when the first LayoutUpdated event was fired, however, to my surprise, calling _fe.Arrange(layoutRect); where _fe is the framework element we’re trying to lay out, only generates a single call to LayoutUpdated in some circumstances, partcularly after the app has been Tombstoned.


In addition, it transpired that the values of ActualHeight and ActualWidth in this case were both 0.  I guess it’s no surprise that it rendered incorrectly in that case!  What appeared to have happened was that the image was way to big, and grainy.  Eventually I downloaded from Isolated storage, and found this is what was actually produced:


sunrise


One of the interesting things to note about this image is that it is not square, certainly not 173x173 like a tile should be, despite that being the size requested.


In fact it’s 173x135.  So, when rendering a tile, it turns out that metro will stretch the smallest dimension to 173, which explained the odd look described earlier.


So, what did I do to fix it? 


I tried subscribing to the SizeChanged event.  That didn’t work:  In fact it never fired. 


I tried setting up a DispatcherTimer, and handling it in 500msec, but that didn’t make any difference either.


Eventually I found a horrible hack that worked, though I don’t know why:


I did this in a loop (don’t try this at home, kids):



for (int i = 0; i < 3; i++)
{

_fe.Measure(new Size(173, 173));
_fe.Arrange(layoutRect);
_fe.UpdateLayout();

if (!bSynchronous || _fe.ActualHeight == 173) break;
// we do this in a loop because sometimes it doesn't work!
}




This code, surprisingly did work, though all Measure/Arrange/UpdateLayout were all required.  Initiallly I started with just Arrange.


But, in playing with this code to write the blog, I found an even simpler solution.  Set the size on the Canvas in the first place, and the problem seems to disappear (that is to say, the way that I used to be able to break it doesn’t break it any more).


This all begs the question:  microsoft, Why is this so hard?


That’s about 12 hours more of my life wasted.  The whole implementation of Silverlight/.NET is in a black box, where we can’t see the code, particularly on the phone where we can’t easily disassemble the source.  Even then it quickly drops down into native code, becoming inaccessible again.


Back in the old days of C++/MFC, it was at least possible to see what the source code was doing, and figure it out.  Now we just have to guess.  It’s turned programming into a giant game, where you need to figure out that right move to proceed, and that’s incredibly frustrating.


It all comes down to a lack of documentation about what actually is going on.  The trouble with blogs like these and all the other search engine led development, is we’re documenting workarounds for current implementations.  There’s no contract that says this behaviour will stay the same, so the next update may break your programs.


If you found this useful, and it saves you any time please consider purchasing one of our apps.  I have to admit, at the moment, sales are terrible for windows phone apps, and if things don’t improve soon, I won’t be wasting any more time developing new phone apps.  I make more money off my 2kW solar panels in a day than I do from phone apps in a month.  And, WM6.5 apps are still bringing in as much money per month as WP7 ones do.

No comments:

Post a Comment