Question

I'm looking to create a Live Tile which is similar to the Iconic tile but allows me to use a custom Count value (i.e. non-integer string).

The closest I've come is that I must create the contents using a bitmap and then use that image as the tile. Unfortunately I don't know how this is commonly done.

I'm looking to create tiles similar to the one that's described in this question (though this question is orthogonal to my issue): Custom live tile rendering issue on Windows Phone (7/8)

In short,

  • Is WriteableBitmap the best way of creating Live Tile layouts?
  • Is there a mechanism by which I can convert XAML into the Live Tile?

An example of the layout I'd like to achieve is somewhat displayed in the Skype Live Tile seen here.

Was it helpful?

Solution

As far as I can tell, creating a custom bitmap is the way to go. I found this answer along with this article to be very helpful when I was doing my live tiles.

If you don't mind purchasing third-party controls you can check out Telerik's LiveTileHelper control (if you're a member of Nokia's developer program you already have access to this).

For my first app I opted to roll my own solution based on the first two links. I have a base class that handles the work of taking a FrameworkElement (each derived class is responsible for generating the FrameworkElement that contains the information to render) and creating the corresponding WritableBitmap instance which I then save as a .PNG using the ToolStack C# PNG Writer Library.

As an example, here's my code to generate the control that represents a small pinned secondary tile in one of my apps:

    /// <summary>
    /// Returns the fully populated and initialized control that displays
    /// the information that should be included in the tile image.
    /// </summary>
    /// <remarks>
    /// We manually create the control in code instead of using a user control
    /// to avoid having to use the XAML parser when we do this work in our
    /// background agent.
    /// </remarks>
    /// <returns>
    /// The fully populated and initialized control that displays
    /// the information that should be included in the tile image.
    /// </returns>
    protected override FrameworkElement GetPopulatedTileImageControl()
    {
        var layoutRoot = new Grid()
                            {
                                Background          = new System.Windows.Media.SolidColorBrush( System.Windows.Media.Color.FromArgb( 0, 0, 0, 0 ) ),
                                HorizontalAlignment = HorizontalAlignment.Stretch,
                                VerticalAlignment   = VerticalAlignment.Stretch,
                                Height              = TileSize.Height,
                                Width               = TileSize.Width,
                                Margin              = new Thickness( 0, 12, 0, 0 )
                            };
        var stopName = new TextBlock()
                            {
                                Text                = Stop.Description,
                                TextTrimming        = TextTrimming.WordEllipsis,
                                TextWrapping        = TextWrapping.Wrap,
                                Margin              = new Thickness( 7, 0, 7, 12 ),
                                MaxHeight           = 135,
                                Width               = TileSize.Width - 14,
                                VerticalAlignment   = VerticalAlignment.Bottom,
                                HorizontalAlignment = HorizontalAlignment.Stretch,
                                FontFamily          = (System.Windows.Media.FontFamily) Application.Current.Resources[ "PhoneFontFamilySemiBold" ],
                                FontSize            = (double) Application.Current.Resources[ "PhoneFontSizeMediumLarge" ],
                                Style               = (Style) Application.Current.Resources[ "PhoneTextNormalStyle" ]
                            };

        Grid.SetColumn( stopName, 0 );
        Grid.SetRow( stopName, 0 );

        layoutRoot.Children.Add( stopName );
        return layoutRoot;
    }

This is a super-simple control with just a TextBlock, but you can easily expand on this. Note that I don't use a UserControl here as I also run this code in a background agent where you have significant memory constraints.

Once I have a control I generate a WritableBitmap like this:

    /// <summary>
    /// Renders the tile image to a <see cref="WritableBitmap"/> instance.
    /// </summary>
    /// <returns>
    /// A <see cref="WritableBitmap"/> instance that contains the rendered
    /// tile image.
    /// </returns>
    private WriteableBitmap RenderTileImage()
    {
        var tileControl = GetPopulatedTileImageControl();
        var controlSize = new Size( TileSize.Width, TileSize.Height );
        var tileImage   = new WriteableBitmap( (int) TileSize.Width, (int) TileSize.Height );

        // The control we're rendering must never be smaller than the tile
        // we're generating.
        tileControl.MinHeight   = TileSize.Height;
        tileControl.MinWidth    = TileSize.Width;

        // Force layout to take place.
        tileControl.UpdateLayout();
        tileControl.Measure( TileSize );
        tileControl.Arrange( new Rect( new Point( 0, 0 ), TileSize ) );
        tileControl.UpdateLayout();

        tileImage.Render( tileControl, null );
        tileImage.Invalidate();

        tileControl = null;
        GC.Collect( 2, GCCollectionMode.Forced, true );

        // Adjust the rendered bitmap to handle the alpha channel better.
        CompensateForRender( tileImage );

        return tileImage;
    }

Again, I'm making explicit calls to GC.Collect to help keep my memory consumption under control when running this code as part of my background agent. The CompensateForRender method is based on the code in the linked article.

Hope this helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top