How to create a Windows Phone Live Tile in the style of the People Hub

How to create a Windows Phone Live Tile in the style of the People Hub

Since Windows Phone 7 Mango you are able to create your own Live Tiles. To be more precisely, you can specify content, titles and images for the front side as well as for the back side. The images can be taken from the web or from the app’s isolated storage. Sometimes it may be necessary to build a Live Tile like the one of the People Hub.

As you can see I’ve created a tile with 9 custom images inside of it. These photos are loaded by a background agent and dynamically merged into one image. I’d like to explain how I did this.

At first, provide a list of images to load:

        private const string FLICKR_LIVE_TILE = "/Shared/ShellContent/flickr_live_tile.jpg";
        private object imageCountLock = new object();
        private const int IMAGES_TO_LOAD = 9;
        private const int IMAGE_DIMENSIONS = 57;
        private static string PhotoLiveTileNavUri = "/MainPage.xaml?list=flickr";
        private static string TwitterLiveTileNavUri = "/MainPage.xaml?list=twitter";

        // Just using the DisplayImage class with an attribut called "ThumbnailImage".
        //The size of the image has to be really small.
        public void UpdateTileImages(IList images)
        {
            WebClient webClient = new WebClient();
            int imageLoadedCount = 0;
            var maxItems = Math.Min(images.Count, IMAGES_TO_LOAD);
            Stream[] streams = new Stream[maxItems];

            webClient.OpenReadCompleted += ((s, e) =>
            {
                lock (imageCountLock)
                {
                    streams[imageLoadedCount] = e.Result;
                    imageLoadedCount++;

                    if (imageLoadedCount == maxItems)
                    {
                        CreateBackgroundImage(streams);
                    }
                    else
                        webClient.OpenReadAsync(new Uri(images[imageLoadedCount].ThumbnailImage, UriKind.Absolute));
                }

            });
            webClient.OpenReadAsync(new Uri(images[imageLoadedCount].ThumbnailImage, UriKind.Absolute));
        }

As you can see I’ve used the WebClient class to load the images sequentially. After one has completed, the next one is loaded. After all images are successfully loaded, the CreateBackgroundImage method is called:

 private void CreateBackgroundImage(Stream[] images)
        {
            Canvas can = new Canvas();
            can.Background = (SolidColorBrush)Application.Current.Resources["PhoneAccentBrush"];
            can.Width = 173;
            can.Height = 173;

            WriteableBitmap writeableBitmap = new WriteableBitmap(173, 173);

            for (int i = 0; i < images.Count(); i++ )
            {
                var bitmapImage = new BitmapImage();
                bitmapImage.SetSource(images[i]);
                Image image = new Image();
                image.Source = bitmapImage;

                image.Width = IMAGE_DIMENSIONS;
                image.Height = IMAGE_DIMENSIONS;
                image.Stretch = Stretch.UniformToFill;

                switch (i)
                {
                    case 0:
                        image.Width++;
                        break;
                    case 1:
                        Canvas.SetLeft(image, IMAGE_DIMENSIONS);
                        image.Width++;
                        break;
                    case 2:
                        Canvas.SetLeft(image, 2 * IMAGE_DIMENSIONS + 1);
                        image.Width++;
                        break;
                    case 3:
                        Canvas.SetTop(image, IMAGE_DIMENSIONS);
                        image.Height++;
                        break;
                    case 4:
                        Canvas.SetTop(image, IMAGE_DIMENSIONS);
                        Canvas.SetLeft(image, IMAGE_DIMENSIONS);
                        image.Height++;
                        image.Width++;
                        break;
                    case 5:
                        Canvas.SetTop(image, IMAGE_DIMENSIONS);
                        Canvas.SetLeft(image, 2 * IMAGE_DIMENSIONS + 1);
                        image.Height++;
                        image.Width++;
                        break;
                    case 6:
                        Canvas.SetTop(image, 2*IMAGE_DIMENSIONS + 1);
                        Canvas.SetLeft(image, 0);
                        image.Height++;
                        break;
                    case 7:
                        Canvas.SetTop(image, 2*IMAGE_DIMENSIONS + 1);
                        Canvas.SetLeft(image, IMAGE_DIMENSIONS);
                        image.Width++;
                        image.Height++;
                        break;
                    case 8:
                        Canvas.SetTop(image, 2*IMAGE_DIMENSIONS + 1);
                        Canvas.SetLeft(image, 2 * IMAGE_DIMENSIONS + 1);
                        image.Width++;
                        image.Height++;
                        break;
                }

                can.Children.Add(image);
            }

            writeableBitmap.Render(can, null);

            writeableBitmap.Invalidate();

            using (var isoFileStream = new IsolatedStorageFileStream(FLICKR_LIVE_TILE, FileMode.OpenOrCreate, IsolatedStorageFile.GetUserStoreForApplication()))
            {
                writeableBitmap.SaveJpeg(isoFileStream, 173, 173, 0, 100);
            }

            var activeTile = ShellTile.ActiveTiles.Where(t => t.NavigationUri == new Uri(PhotoLiveTileNavUri)).FirstOrDefault();

            StandardTileData shellTileData = new StandardTileData
            {
                BackBackgroundImage = new Uri("isostore:" + FLICKR_LIVE_TILE, UriKind.Absolute),
                BackContent = "Photos",
                BackgroundImage = new Uri(@"Background.png", UriKind.Relative),
                BackTitle = "Local Things",
                Count = images.Count(),
                Title = ""
            };

            if (activeTile != null)
            {
                activeTile.Update(shellTileData);
            }
            else
            {
                ShellTile.Create(new Uri(PhotoLiveTileNavUri, UriKind.Relative), shellTileData);
            }
        }

Let me explain what I did: I created a canvas object where I can add multiple images to. The size of the canvas object has to be 173×173 pixels because that’s exactly the size of a Live Tile pinned to the start.
After that, I created an instance of BitmapImage and an instance of the Image class for every little image I want to display. We need the Image class because one can only add elements to the Canvas which are subclasses of UIElement.

Rendering

The switch statement isn’t the most elegant way to calculate the position of each item. But I did it as easy as possible just for a better understanding. After positioning all items I had to render the Canvas object with the Render method of the WriteableBitmap class. One issue is that it is only allowed to save these graphics as a jpeg image. If you haven’t enough images to fill out the Live Tile you need a background color which fits to the accent color. Therefore, I loaded the phone’s accent brush into the background of my canvas object. After these steps one can simply save this collaboration image to the isolated storage and use it as a background image for a live tile.

Note

You have to ensure that your images you want to display are as small as possible if you’re using a background agent. The time and memory restrictions are very strict, so it might happen that the time is over or you get a memory exception while loading or processing images.

 

About the Author

Leave a Reply


*