Working with tiles and the splash screen in Hilo (Windows Store apps using C++ and XAML)

From: Developing an end-to-end Windows Store app using C++ and XAML: Hilo

Previous page | Next page

Using tiles and the splash screen effectively can give your users a great first-impression of your Windows Store app using C++ and XAML.

Download

After you download the code, see Getting started with Hilo for instructions.

You will learn

  • How we incorporated an app tile that displays the user's photos.
  • How we added the splash screen.
  • What considerations to make in your own app.

Applies to

  • Windows Runtime for Windows 8
  • Visual C++ component extensions (C++/CX)
  • XAML

Why are tiles important?

Traditional Windows desktop apps use icons. Icons help visually connect an app or file type with its brand or use. Because an icon is a static resource, you can often wait until the end of the development cycle to incorporate it. However, tiles are different from icons. Tiles add life and personality and can create a personal connection between the app and the user. The goal of tiles is to keep your users coming back by offering a personal connection.

Tip  For Hilo, we knew early that we wanted to display the user's photos on the tile. But for other apps, it might not be apparent until later what content will keep your users coming back. We recommend that you add support for tile updates when you first create your project, even if you're not yet sure what the tile will show. When you decide later what to show on the tile, the infrastructure will already be in place. This way, you won't need to retrofit your code to support tile updates later.

 

[Top]

Choosing a tile strategy

You have a few options when choosing a tile strategy. You can provide a wide tile, which your user can then change to a square tile if they prefer. You can also display badges and notifications on your tile.

Note  Because Hilo is a photo app, we wanted to show the user's photos on the tile. You can show images on both the square and wide tile. The wide tile enables us to show multiple images, so we decided to support both. See Choosing between a square and wide tile size for info on how to choose the right tiles for your app.

 

We settled on the following rules for the tile behavior:

  • Display wide default tile that shows the Hilo logo before the app is ever launched.
  • Each time the app is launched, update the square and wide tiles according to these rules:
    • If the user has less than 5 pictures in the Pictures folder, display the default tile that shows the Hilo logo.
    • Otherwise, randomly choose 15 pictures from the most recent 30 and set up the notification queue to cycle among 3 batches of 5 pictures. (If the user chooses the square tile, it will show only the first picture from each batch.)

Read Guidelines and checklist for tiles to learn how tile features relate to the different tile styles. Although you can provide notifications to the square tile, we wanted to also enable the wide tile so that we could display multiple images.

Tip  

You can also enable secondary tiles for your app. A secondary tile enables your users to pin specific content or experiences from an app to the Start screen to provide direct access to that content or experience. For more info about secondary tiles, read Pinning secondary tiles.

 

[Top]

Designing the logo images

Our UX designer created the small, square, and wide logos according to the pixel size requirements for each. The designer suggested a theme that fitted the Hilo brand. Choosing a small logo that represents your app is important so that users can identify your app when the tile displays custom content. This is especially important when the contents of your tile changes frequently—you want your users to be able to easily find and identify your app. The small Hilo logo has a transparent background so that it looks good when it appears on top of a tile notification or other background.

The Assets folder contains the small, square, and wide logo images. For more info about working with image resources, see Quickstart: Using file or image resources and How to name resources using qualifiers.

30 x 30 pixels

150 x 150 pixels

310 x 150 pixels

Important  Because the small logo appears on top of a tile notification, consider the color scheme that you use for the foreground color versus the background color. For Hilo, this decision was challenging because we display users' photos and cannot know what colors will appear. We experimented with several foreground colors and chose white because we felt it looked good when displayed over most pictures. (We also considered the fact that most photos do not have white as the dominant color.)

 

Note  Image assets, including the Hilo logo, are placeholders and meant for training purposes only. They cannot be used as a trademark or for other commercial purposes.

 

[Top]

Placing the logos on the default tiles

The Visual Studio manifest editor makes the process of adding the default tiles relatively easy. To learn how, read Quickstart: Creating a default tile using the Visual Studio manifest editor.

[Top]

Updating tiles

You use tile templates to update the tiles. Tile templates are an XML-based approach to specify the images and text used to customize the tile. The Windows::UI::Notifications namespace provides classes to update Start screen tiles. For Hilo, we used the TileUpdateManager and TileUpdater classes to get the tile template and queue the notifications. Hilo defines the TileUpdateScheduler and WideFiveImageTile classes to choose the images to show on the tile and form the XML that is provided to Windows Runtime.

Note  Each tile template enables you to display images, text, or both. We chose TileTemplateType::TileWideImageCollection for the wide tile because it shows the greatest number of images. We also chose it because we did not need to display additional text on the tile. We also use TileSquareImage to display the first image in the user's collection when they choose to show the square tile. For the complete list of options, see TileTemplateType.

 

Tile updates occur in the App::OnLaunched method, which is called during app initialization.

m_tileUpdateScheduler = std::make_shared<TileUpdateScheduler>();
m_tileUpdateScheduler->ScheduleUpdateAsync(m_repository, m_exceptionPolicy);

Note  We considered updating the tiles when the app suspends, instead of when the app initializes, to make the updates more frequent. However, every Windows Store app should suspend as quickly as possible. Therefore, we did not want to introduce additional overhead when the app suspends. For more info, read Minimize suspend/resume time.

 

The TileUpdateScheduler::ScheduleUpdateAsync method performs the following steps to update the tile in the background:

  • Create a local folder that will store a copy of the thumbnails to be displayed on the tile.
  • If there are at least 30 photos in the user's collection:
    • Randomly select 15 of the user's 30 most recent photos.
    • Create 3 batches of 5 photos.
    • Generate thumbnails for the randomly selected photos in the local app folder.
  • Create the notification and update the tile.

Verify your URLs describes the ways you can reference images that appear in your tile notification. We use ms-appdata:///local/ for Hilo because we copy thumbnails to a local app folder.

Note  The number of bytes consumed by the thumbnails is small, so even if very large pictures are chosen to use on the tile, copying the thumbnails doesn't take much time or disk space.

 

The following example shows the TileUpdateScheduler::ScheduleUpdateAsync method. This code controls the process that creates the thumbnails folder, selects the images, and updates the tile. Each call to the Windows Runtime is asynchronous; therefore, we create a chain of continuation tasks that perform the update operations.

TileUpdateScheduler.cpp

task<void> TileUpdateScheduler::ScheduleUpdateAsync(std::shared_ptr<Repository> repository, std::shared_ptr<ExceptionPolicy> policy)
{
    // The storage folder that holds the thumbnails.
    auto thumbnailStorageFolder = make_shared<StorageFolder^>(nullptr);

    return create_task(
        // Create a folder to hold the thumbnails.
        // The ReplaceExisting option specifies to replace the contents of any existing folder with a new, empty folder.
        ApplicationData::Current->LocalFolder->CreateFolderAsync(
            ThumbnailsFolderName, 
            CreationCollisionOption::ReplaceExisting)).then([repository, thumbnailStorageFolder](StorageFolder^ createdFolder) 
    {
        assert(IsBackgroundThread());
        (*thumbnailStorageFolder) = createdFolder;

        // Collect a multiple of the batch and set size of the most recent photos from the library. 
        // Later a random set is selected from this collection for thumbnail image generation.
        return repository->GetPhotoStorageFilesAsync("", 2 * BatchSize * SetSize);
    }, task_continuation_context::use_arbitrary()).then([](IVectorView<StorageFile^>^ files) -> task<IVector<StorageFile^>^>
    {
        assert(IsBackgroundThread());
        // If we received fewer than the number in one batch,
        // return the empty collection. 
        if (files->Size < BatchSize)
        {
            return create_task_from_result(static_cast<IVector<StorageFile^>^>(
                ref new Vector<StorageFile^>()));
        }
        auto copiedFileInfos = ref new Vector<StorageFile^>(begin(files), end(files));
        return RandomPhotoSelector::SelectFilesAsync(copiedFileInfos->GetView(), SetSize * BatchSize);
    }, task_continuation_context::use_arbitrary()).then([this, thumbnailStorageFolder, policy](IVector<StorageFile^>^ selectedFiles) -> task<Vector<StorageFile^>^>
    {
        assert(IsBackgroundThread());
        // Return the empty collection if the previous step did not
        // produce enough photos.
        if (selectedFiles->Size == 0)
        {
            return create_task_from_result(ref new Vector<StorageFile^>());
        }
        ThumbnailGenerator thumbnailGenerator(policy);
        return thumbnailGenerator.Generate(selectedFiles, *thumbnailStorageFolder);
    }, task_continuation_context::use_arbitrary()).then([this](Vector<StorageFile^>^ files)
    {
        assert(IsBackgroundThread());
        // Update the tile.
        UpdateTile(files);
    }, concurrency::task_continuation_context::use_arbitrary()).then(ObserveException<void>(policy));
}

Note  We considered multiple options for how the pictures are chosen. Some alternatives we considered were to choose the most recent pictures or enable the user to select them. We went with randomly choosing 15 of the most recent 30 to get both variety and recent pictures. We felt that having users choose the pictures would be inconvenient and might not entice them to come back to the app later.

 

To create a thumbnail image, the ThumbnailGenerator::CreateThumbnailFromPictureFileAsync method gets the thumbnail from the image, decodes it, and then encodes it as a .jpg image. Because tile notifications only support the .jpg/.jpeg, .png, and .gif image formats, we re-encode each image to enable the app to use the .bmp, and .tiff image formats. We chose .jpg as the target format because it produces the smallest images, while still providing the desired image quality.

ThumbnailGenerator.cpp

task<InMemoryRandomAccessStream^> ThumbnailGenerator::CreateThumbnailFromPictureFileAsync(
    StorageFile^ sourceFile, 
    unsigned int thumbSize)
{
    (void)thumbSize; // Unused parameter
    auto decoder = make_shared<BitmapDecoder^>(nullptr);
    auto pixelProvider = make_shared<PixelDataProvider^>(nullptr);
    auto resizedImageStream = ref new InMemoryRandomAccessStream();
    auto createThumbnail = create_task(
        sourceFile->GetThumbnailAsync(
        ThumbnailMode::PicturesView, 
        ThumbnailSize));

    return createThumbnail.then([](StorageItemThumbnail^ thumbnail)
    {
        IRandomAccessStream^ imageFileStream = 
            static_cast<IRandomAccessStream^>(thumbnail);

        return BitmapDecoder::CreateAsync(imageFileStream);

    }).then([decoder](BitmapDecoder^ createdDecoder)
    {
        (*decoder) = createdDecoder;
        return createdDecoder->GetPixelDataAsync( 
            BitmapPixelFormat::Rgba8,
            BitmapAlphaMode::Straight,
            ref new BitmapTransform(),
            ExifOrientationMode::IgnoreExifOrientation,
            ColorManagementMode::ColorManageToSRgb);

    }).then([pixelProvider, resizedImageStream](PixelDataProvider^ provider)
    {
        (*pixelProvider) = provider;
        return BitmapEncoder::CreateAsync(
            BitmapEncoder::JpegEncoderId, 
            resizedImageStream);

    }).then([pixelProvider, decoder](BitmapEncoder^ createdEncoder)
    {
        createdEncoder->SetPixelData(BitmapPixelFormat::Rgba8,
            BitmapAlphaMode::Straight,
            (*decoder)->PixelWidth,
            (*decoder)->PixelHeight,
            (*decoder)->DpiX,
            (*decoder)->DpiY,
            (*pixelProvider)->DetachPixelData());
        return createdEncoder->FlushAsync();

    }).then([resizedImageStream]
    {
        resizedImageStream->Seek(0);
        return resizedImageStream;
    });
}

The TileUpdateScheduler::UpdateTile method uses the TileUpdateManager class to create a TileUpdater object. The TileUpdater class updates the content of the app's tile. The TileUpdateScheduler::UpdateTile method calls the TileUpdater::EnableNotificationQueue method to queue notifications for each batch. The TileUpdateScheduler::UpdateTile method then builds a list of image paths for each batch of pictures and passes that list to a WideFiveImageTile object. The WideFiveImageTile object formats the XML for the tile update and for each batch of pictures, and then the TileUpdateScheduler::UpdateTile method calls the TileUpdater::Update method to update the tile.

ThumbnailGenerator.cpp

void TileUpdateScheduler::UpdateTile(IVector<StorageFile^>^ files)
{
    // Create a tile updater.
    TileUpdater^ tileUpdater = TileUpdateManager::CreateTileUpdaterForApplication();
    tileUpdater->Clear();

    unsigned int imagesCount = files->Size;
    unsigned int imageBatches = imagesCount / BatchSize;

    tileUpdater->EnableNotificationQueue(imageBatches > 0);

    for(unsigned int batch = 0; batch < imageBatches; batch++)
    {
        vector<wstring> imageList;

        // Add the selected images to the wide tile template.
        for(unsigned int image = 0; image < BatchSize; image++)
        {
            StorageFile^ file = files->GetAt(image + (batch * BatchSize));
            wstringstream imageSource;
            imageSource << L"ms-appdata:///local/" 
                << ThumbnailsFolderName->Data() 
                << L"/" 
                << file->Name->Data();
            imageList.push_back(imageSource.str());
        }

        WideFiveImageTile wideTile;
        wideTile.SetImageFilePaths(imageList);

        // Create the notification and update the tile.
        auto notification = wideTile.GetTileNotification();
        tileUpdater->Update(notification);
    }
}

The WideFiveImageTile class, which is defined in WideFiveImageTile.cpp, encapsulates the creation of the XML for the tile update. It builds upon the TileTemplateType::TileWideImageCollection tile template by inserting the provided list of file names into the template's XML content. It also uses the TileSquareImage tile template to show just the first picture if the user chooses the square tile. The WideFiveImageTile class then creates a TileNotification object using the updated XML content.

This code example shows how the WideFiveImageTile::UpdateContentWithValues method updates the template's XML content.

WideFiveImageTile.cpp

void WideFiveImageTile::UpdateContentWithValues(XmlDocument^ content)
{
    if (m_fileNames.size() == 0) return;

    // Update wide tile template with the selected images.
    for(unsigned int image = 0; image < m_fileNames.size(); image++)
    {
        IXmlNode^ tileImage = content->GetElementsByTagName("image")->GetAt(image);
        tileImage->Attributes->GetNamedItem("src")->InnerText = ref new String(
            m_fileNames[image].c_str());
    }

    // Update square tile template with the first image.
    TileTemplateType squareTileTemplate = TileTemplateType::TileSquareImage;
    XmlDocument^ squareTileXml = TileUpdateManager::GetTemplateContent(squareTileTemplate);

    IXmlNode^ tileImage = squareTileXml->GetElementsByTagName("image")->First()->Current;
    tileImage->Attributes->GetNamedItem("src")->InnerText = ref new String(
        m_fileNames[0].c_str());

    auto node = content->ImportNode(squareTileXml->GetElementsByTagName("binding")->First()->Current, true);
    content->GetElementsByTagName("visual")->First()->Current->AppendChild(node);
}

The XML for a typical tile notification looks like this:

<tile>
  <visual>
    <binding template="TileWideImageCollection">
      <image id="1" src="ms-appdata:///local/thumbnails/thumbImage_0.jpg"/>
      <image id="2" src="ms-appdata:///local/thumbnails/thumbImage_1.jpg"/>
      <image id="3" src="ms-appdata:///local/thumbnails/thumbImage_2.jpg"/>
      <image id="4" src="ms-appdata:///local/thumbnails/thumbImage_3.jpg"/>
      <image id="5" src="ms-appdata:///local/thumbnails/thumbImage_4.jpg"/>
    </binding>
  <binding template="TileSquareImage">
      <image id="1" src="ms-appdata:///local/thumbnails/thumbImage_0.jpg"/>
    </binding>
  </visual>
</tile>

Note  The wide tile template also contains a template for the square tile. This way, both the square and wide tiles are covered by a single template.

 

If you use text with your tile, or your images are sensitive to different languages and cultures, read Globalizing tile and toast notifications to learn how to globalize your tile notifications.

Use these resources to learn more about tile templates:

[Top]

Adding the splash screen

All Windows Store apps must have a splash screen, which is a composite of a splash screen image and a background color, both of which you can customize. The splash screen is shown as your app loads. As with tiles, our designer created the splash screen image. We chose an image that resembled the default tile logos and fits the Hilo brand. It was straight forward to add the splash screen to the app. Read Quickstart: Adding a splash screen to learn how.

You might need to display the splash screen for a longer duration, to show real-time loading information to your users, if your app needs more time to load. This involves creating a page that mimics the splash screen by showing the splash screen image and any additional info. For Hilo, we considered using an extended splash screen, but because the app loads the hub page very quickly, we didn't have to add it. For more info about extending the duration of the splash screen, see How to extend the splash screen.

Use these resources to learn more about splash screens:

[Top]