다음을 통해 공유


Creating Dynamic Live Tiles from UI Controls (Windows 8.1 XAML/VB.net)

Introduction

Windows Store apps can show dynamic information to users via live tiles. The standard tile templates can limit your options in applying style to suit your app or game.

This tutorial explains how to build your own live tiles with user controls, which you save as bitmaps, and then add to your live tile. Instead of the standard Windows 8 tile layouts you can have something as amazing as this:

http://pumpkinszwan.files.wordpress.com/2013/12/121513_1436_creatingdyn1.png

While that's perhaps not the most attractive tile you'll ever see, it's great because it's made up of a totally customisable user control rendered as an image. Any of the UI elements in the above image can be changed to reflect the state of your app.

There's a downloadable project with all the code in at the bottom of this post, but read through the tutorial if you want to see every step explained.

The Main Steps

Creating custom live tiles requires 3 steps:

  1. Create a user control with your tile layout
  2. Render your user control to a bitmap
  3. Update your app's live tile using the image you rendered in Step 2.

Before you Start

You need any version of Visual Studio 2013 for Windows (including Visual Studio Express) running on Windows 8.1. This technique does not work for Windows 8.0 apps unfortunately.

Create a new project in Visual Studio 2013 (Visual Basic > Windows Store > Blank App (XAML) ).

Step 1: Create the User Controls

There is no limit to what you can render to your live tile. We will create 2 UI elements in this case (we'll be making a wide tile and a medium/square tile). For this tutorial we will 'hard code' some content into our tiles, though the real power comes when you databind the tile to you app in a way that displays a player's progress or some important app content dynamically.

Create two new user controls like this:

1) Right-click the project name in the Solution Explorer and select Add > New Item

2) Select User Control

3) Type a name (use WideTileTemplate.xaml and MediumTileTemplate.xaml)

4) Finally, click Add.

http://pumpkinszwan.files.wordpress.com/2013/12/121513_1436_creatingdyn2.png

Add content to your user controls exactly how you want it to appear on the live tiles. You can copy and paste the code below or design your own.

NOTE: To use the code below you'll need to add an image to your project's root folder called 'image.png'. You can use any image you have on your computer or from the Internet. Right-click on your project name and choose:* Add > Existing Item*, then browse to the .png file on your computer. Remember to rename it or change the code so that the correct filename is referenced or your image won't show.

NOTE: We need to make the control the correct size/proportion so it looks correct. For wide tiles use 691 x 336 pixels, for medium/square tiles it's 336x336.

Wide Live Tile User Control Template

<UserControl     x:Class="TileSamplerApp.WideTileTemplate"       
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"      xmlns:local="using:TileSamplerApp"   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"   mc:Ignorable="d"   d:DesignHeight="336" d:DesignWidth="691"> <Canvas x:Name="LayoutRoot" Width="691" Height="336" Background="Yellow">  <Image Source="image.png" HorizontalAlignment="Left" Height="316" VerticalAlignment="Top"     Width="332" Canvas.Left="10" Canvas.Top="10"/>  <TextBlock Text="Hello World" HorizontalAlignment="Left" Height="39" TextWrapping="Wrap" VerticalAlignment="Top" 
      Width="266" FontSize="26" Foreground="Black" FontWeight="Bold" Canvas.Left="347" Canvas.Top="46"/>       <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Some more text!" VerticalAlignment="Top" Width="277" 
      RenderTransformOrigin="0.5,0.5" Canvas.Left="347" Canvas.Top="159" FontSize="26" Height="30" Foreground="#FFE81C1C"/>   <TextBlock Text="37" x:Name="Number" HorizontalAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Top" 
      Height="97" Width="122" FontSize="72" TextAlignment="Center" Canvas.Left="559" Canvas.Top="229" Foreground="#FF42105F"/></Canvas></UserControl>

Medium Live Tile User Control Template

Do the same for the medium-sized user control. Here is the code for that one:

<UserControl    x:Class="TileSamplerApp.MediumTileTemplate"    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    xmlns:local="using:TileSamplerApp"    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    mc:Ignorable="d"    d:DesignHeight="336" d:DesignWidth="336"> <Canvas x:Name="LayoutRoot"  Width="336" Height="336"  Background="Yellow">    <Image Source="image.png" HorizontalAlignment="Left"  Height="167" Width="178"        VerticalAlignment="Top" Canvas.Left="27" Canvas.Top="70"/>    <TextBlock Text="Hello World" HorizontalAlignment="Left" Height="39" TextWrapping="Wrap" 
VerticalAlignment="Top" Width="266" FontSize="26" Foreground="Black" FontWeight="Bold" Canvas.Left="36"     Canvas.Top="10"/>    <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Some more text!"       VerticalAlignment="Top" Width="277" RenderTransformOrigin="0.5,0.5" Canvas.Left="36" Canvas.Top="270"       FontSize="26" Height="30" Foreground="#FFE81C1C"/>    <TextBlock Text="37" x:Name="Number" HorizontalAlignment="Center" TextWrapping="Wrap"       VerticalAlignment="Top" Height="97" Width="122" FontSize="72" TextAlignment="Center" Canvas.Left="210"       Canvas.Top="102" Foreground="#FF42105F"/></Canvas></UserControl>

Add the User Controls to MainPage.xaml

  1. Open MainPage.xaml in the designer by double-clicking it in the Solution Explorer.
  2. Replace the XAML in the page with the code below:
<Page x:Class="TileSamplerApp.MainPage"   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     xmlns:local="using:TileSamplerApp"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"     mc:Ignorable="d">    
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">        <local:WideTileTemplate x:Name="WideTile" HorizontalAlignment="Left" VerticalAlignment="Top"/>        <local:MediumTileTemplate x:Name="MediumTile" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
           <Button Content="Create Tile" HorizontalAlignment="Left" Margin="170,606,0,0" VerticalAlignment="Top" 
     Height="116" Width="210" Click="Button_Click"/></Grid></Page>

This will add two controls to the page, and the designer will now look something like this:

http://pumpkinszwan.files.wordpress.com/2013/12/live_tile_controls.png?w=300

Recap

Now you should have a new project with the following in the root folder:

  • An image called 'image.png'
  • A user control called WideTileTemplate.xaml
  • A user control called MediumTileTemplate.xaml
  • A MainPage.xaml which includes the two controls.

If you have all that you're ready to continue to Step 2.

Step 2: Render the User Controls to Bitmaps and Save

Now that you have your user controls created and displayed on a page you can render them as bitmap images and save them in your app's storage folder.

BitmapRender creates a 'stream' of data from the pixels that make up your user control. That stream of data is encoded (in this case in .PNG format), and then stored in a file.

For this step we'll use two methods; one converts the pixels that make up our user controls into a stream of data in .PNG format, and the other saves this as a file.

Put this code in your MainPage.xaml.vb page.

Before creating the methods that will encode our image and store it as a file, you need to include some namespaces in your page. Paste the following code at the very top of MainPage.xaml.vb. These lines of code allow us to access certain functions in Visual Basic that we otherwise couldn't access from within our page.

Imports Windows.StorageImports Windows.Graphics.ImagingImports Windows.Storage.StreamsImports Windows.UI.NotificationsImports NotificationsExtensions.TileContent

Install the Notifications Extensions

You need to add the NotificationsExtensions:

1) From the menu bar at the top of the Visual Studio window select: TOOLS > Library Package Manager > Package Manager Console.

2) At the bottom of the window the Package Manager Console should have appeared with a PM> prompt. At the PM> prompt type: Install-Package NotificationsExtensions.WinRT

3) Press Enter.

Add the Code to your MainPage.xaml.vb

Here is the code for creating a .PNG file from a user control. It consists of two methods. Paste this code in your MainPage.xaml.vb page file:

Public Async Function GenerateImageToFile(fileName As String, uiContent As FrameworkElement) As Task     Dim file As StorageFile = Await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting)     If file IsNot Nothing Then          CachedFileManager.DeferUpdates(file)          Using stream = Await file.OpenAsync(FileAccessMode.ReadWrite)               Dim b = Await CaptureToStreamAsync(uiContent, stream, BitmapEncoder.PngEncoderId)          End Using          Dim status = Await CachedFileManager.CompleteUpdatesAsync(file)     End IfEnd Function Private Async Function CaptureToStreamAsync(uielement As FrameworkElement, stream As IRandomAccessStream, encoderId As Guid) As Task(Of RenderTargetBitmap)     Dim renderTargetBitmap = New RenderTargetBitmap()     Await renderTargetBitmap.RenderAsync(uielement)     Dim pixels = Await renderTargetBitmap.GetPixelsAsync()     Dim logicalDpi = DisplayInformation.GetForCurrentView().LogicalDpi     Dim encoder = Await BitmapEncoder.CreateAsync(encoderId, stream)     encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, CUInt(renderTargetBitmap.PixelWidth), CUInt(renderTargetBitmap.PixelHeight), logicalDpi, logicalDpi, pixels.ToArray())     Await encoder.FlushAsync()     Return renderTargetBitmapEnd Function

Let's go through the above code step-by-step.

Public Async Function GenerateImageToFile(fileName As String, uiContent As FrameworkElement) As Task
This method - GenerateImageToFile - takes a filename and a user control as arguments, then creates a .PNG image file in your app's storage folder. This method calls the 2nd method to do part of the work, but within your app you only call GenerateImageToFile.
Dim file As StorageFile = Await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting)
The creates a StorageFile using the filename we gave it as an argument, and if a file with that name already exists it will be replaced by the new file.
If file IsNot Nothing Then  CachedFileManager.DeferUpdates(file)

This checks that the file we created exists, then stops the system from making changes/updates to the file while we work with it.

Using stream = Await file.OpenAsync(FileAccessMode.ReadWrite)
     Dim b = Await CaptureToStreamAsync(uiContent, stream, BitmapEncoder.PngEncoderId)
End Using

This block of code opens the file and sets a data stream as its contents. Then the 2nd line 'pours' all the data from your user control into the stream using the CaptureToStreamAsync function (below), encoding it with the PngEncoderId (i.e. converts the control into a .PNG bitmap image).

Dim status = Await CachedFileManager.CompleteUpdatesAsync(file)End If
This line makes sure the method doesn't complete until the file is updated with the .PNG data stream. The End If statement closes off the If/Then block started above. Next up we will go through the code that actually creates our image.
Private Async Function CaptureToStreamAsync(uielement As FrameworkElement, stream As IRandomAccessStream, encoderId As Guid) As Task(Of RenderTargetBitmap)

CaptureToStreamAsync copies the pixel data from our user control and converts it into the correct format for an image. The function takes the name of the user control/framework element you wish to convert to an image, a data stream (which is set up by the method that we use to call this function), and an encoder, which tells the function how to format the image. This function returns a RenderTargetBitmap, which is the image we will subsequently save as an image file.

Dim renderTargetBitmap = New RenderTargetBitmap()
Await renderTargetBitmap.RenderAsync(uielement)

Firstly we initialise our image as a variable of type RenderTargetBitmap. This is where we will store our image, and will be the output/result of this function.

Dim pixels = Await renderTargetBitmap.GetPixelsAsync()

This line converts our bitmap image into its constituent pixels and stores that data in a variable called 'pixels'.

Dim logicalDpi = DisplayInformation.GetForCurrentView().LogicalDpi
This gets the appropriate DPI (dots per inch) of your display to use in calculating the bitmap image dimensions.
Dim encoder = Await BitmapEncoder.CreateAsync(encoderId, stream)
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, CUInt(renderTargetBitmap.PixelWidth), CUInt(renderTargetBitmap.PixelHeight), logicalDpi, logicalDpi, pixels.ToArray())

These lines creates the data for the final bitmap image using various bits of information. The pixels are stored in an array ready to use.

Await encoder.FlushAsync()Return renderTargetBitmap
These two lines finish off the bitmap creation by flushing the image data into the renderTargetBitmap variable and returning the bitmap as the result of the function.

NOTE: The image is returned to the calling method, which is where it is saved as a .PNG file. So at this point we have saved an image to the app's local storage folder in .PNG format.

All we have to do now is build a live tile using the saved file as the image.

Step 3: Create the Live Tile

Now we just create new tile data and update your app's tile with it. This sample updates an app's medium and wide tile.

NOTE: You need to include a default wide tile in your app's manifest to be able to use a wide tile. By default, apps support medium and small tiles.  To do this:

  1. Create any image in .png format that is 310 pixels wide and 150 pixels tall.
  2.  In Solution Explorer. double-click the Package.appxmanifest file
  3. Select the Visual Assets tab
  4. Select *Wide 310x150 Logo *in the left-hand column.
  5. In the Scaled Assets area click the three little dots icon under the Scale 100 box and browse to your 310x150 image file.

Now your app's wide tile is enabled.

This tutorial doesn't use the extra large tile size, though it should be adaptable to do so with minimal effort. Paste this code into MainPage.xaml.vb.

Here is the code for creating a live tile using wide and medium images:

Public Sub  SetLiveTileImage(wideImageFileName As String, mediumImageFileName As  String) TileUpdateManager.CreateTileUpdaterForApplication.Clear()
Dim tileContent As NotificationsExtensions.TileContent.ITileWideImage = TileContentFactory.CreateTileWideImage()
Dim squareTileContent = NotificationsExtensions.TileContent.TileContentFactory.CreateTileSquareImage() tileContent.Image.Src = "ms-appdata:///local/"  & wideImageFileName     squareTileContent.Image.Src = "ms-appdata:///local/" & mediumImageFileName tileContent.SquareContent = squareTileContent    tileContent.Branding = TileBranding.None
Dim MyTileNotification As TileNotification = tileContent.CreateNotification()    TileUpdateManager.CreateTileUpdaterForApplication.Update(MyTileNotification)
End Sub

NOTE: Different tile templates require different information, but we're just creating the most basic tile that includes only an image.

Final Step: Tie it all Together

Now that you have all the pieces in place you need to actually execute the code to generate a live tile. For this example we'll trigger the live tile from a button click, but in your apps you will want to use logic to lead to a tile update, such as incoming news or progress in a game.

Add a button to your MainPage.xaml. Paste the code below underneath the code for your two user controls:

 

      <    Button Content="Create Tile" HorizontalAlignment="Left" Margin="170,606,0,0" VerticalAlignment="Top" Height="116" Width="210" Click="Button_Click" />

Now add in the code that will run when the button is clicked. This code will create your live tiles. You can paste this code into your MainPage.xaml.vb or double-click on your button in the designer to automatically create the method, and then fill in the rest:

Private Async Sub Button_Click(sender As Object, e As  RoutedEventArgs)
      Await GenerateImageToFile("wideTile.png", WideTile)
      Await GenerateImageToFile("mediumTile.png", MediumTile)
      SetLiveTileImage("wideTile.png", "mediumTile.png")
End Sub

And that's it. Try it out! If you have two monitors you can put the Start menu on one screen and run this project on the other and watch the tile update when you click the button!

Try fiddling with the project. Here are some things to try:

  • Add a text box to the page and populate one of the text blocks in the tile with its text
  • Place your controls BEHIND some other UI. The tile will still render correctly even when your controls aren't visible!
  • Add a filepicker to select a picture from the picture library to add to the tile.
  • Experiment with different tile layouts.

Troubleshooting

I've noticed that the tiles sometimes don't update. It seems that Windows limits the amount of times you can update tiles on the system in some way. If you're sure the code is correct but the tile isn't updating when you click the button do a quick reboot.

Download

Download the sample app