古事連記帖

趣味のこと、技術的なこと、適当につらつら書きます。

Windows Phone 8.1 で XamlRenderingBackgroundTask を使ったタイル更新

すでに id:kazuakix さんがバックグラウンド タスクタイル更新をゴリゴリ書いていらっしゃいますが、僕からは、自由なレイアウトでタイルを更新する方法を書いてみようかと。


Windows Phone 7.x などの Silverlight では、タイルの更新はすべて画像として用意する必要がありましたが、Windows Phone 8.1 からの Windows Runtime では、XML で書かれたテンプレートをもとにタイルを更新するようになりました。ただ、自分で好きにレイアウトを組んで作ることができなくなっています。
幸いにも画像を貼るだけのテンプレートが用意されていますので、それをうまく使うことで従来と同じような感覚でタイル更新ができますが、いままでとは違い、WriteableBitmap クラスで XAMLレンダリングすることができなくなりました。

代わりに RenderTargetBitmap を利用しますが、WriteableBitmap クラスでやってたのとは違い、一度画面に描画されないと画像化できないという問題があります。

RenderTargetBitmap.RenderAsyncメソッドの実行は画面に表示されたUIElementに限る - Yuya Yamaki’s blog

それだとあんまりという声があったのかなかったのか、Windows Phone 8.1 専用のバックグラウンドタスク XamlRenderingBackgroundTask を使うことで、画面に描画しなくても、バッググラウンドタスクの中に限って画像化できるようになります。


前置きが長くなりましたが、ここからが本題。
バックグラウンド タスクの作り方タイルの更新の仕方については前述のブログを見ていただくことにして、まずはタイルのテンプレートを作ります。
バックグラウンドタスクのプロジェクトにユーザー コントロールで新しく項目を作り、以下のような XAML を書きました。

<UserControl
    x:Class="BackgroundTasks.TileTemplate"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BackgroundTasks"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="150"
    d:DesignWidth="150">

    <Grid x:Name="LayoutRoot" Width="150" Height="150">
        <Grid.RowDefinitions>
            <RowDefinition Height="24"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="24"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Image Source="kazuakix.png" Grid.Column="1" Grid.Row="1"/>
        <TextBlock x:Name="tileText" Text="Test" FontWeight="Bold" FontSize="{StaticResource TextStyleLargeFontSize}" Grid.ColumnSpan="2" Margin="0,2,0,0"/>
    </Grid>
</UserControl>

タイルに書く情報は外から書き換えられるようにしたいので、コードビハインドに

public Grid Root
{
    get { return this.LayoutRoot; }
}

public string TileText
{
    get { return this.tileText.Text; }
    set { this.tileText.Text = value; }
}

こんな感じのを書いておきました。


次にバックグラウンド タスクのロジックを以下のように書き換えます。

using NotificationsExtensions.TileContent;
using System;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.ApplicationModel.Background;
using Windows.Graphics.Display;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.UI.Notifications;
using Windows.UI.Xaml.Media.Imaging;

namespace BackgroundTasks
{
    public sealed class TileUpdateTask : XamlRenderingBackgroundTask
    {
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            OnRun(taskInstance);
        }

        protected override void OnRun(IBackgroundTaskInstance taskInstance)
        {
            var deferral = taskInstance.GetDeferral();

            CreateTile(deferral);
        }

        private async void CreateTile(BackgroundTaskDeferral deferral)
        {
            // タイルテンプレートを呼び出し
            var template = new TileTemplate();
            template.TileText = DateTime.Now.ToString("yyyy/MM/dd");

            // 描画
            var render = new RenderTargetBitmap();
            await render.RenderAsync(template.Root, 150, 150);

            // 保存
            var localFolder = ApplicationData.Current.LocalFolder;
            // タイル画像を保存する場所を開く
            var tileFolder = await localFolder.CreateFolderAsync("tilecontent/", CreationCollisionOption.OpenIfExists);
            // タイル画像のファイルを開く
            var tileFile = await tileFolder.CreateFileAsync("tile.png", CreationCollisionOption.ReplaceExisting);

            var pixelBuffer = await render.GetPixelsAsync();
            
            using(var fs = await tileFile.OpenAsync(FileAccessMode.ReadWrite))
            {
                // PNG で保存する
                var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, fs);
                encoder.SetPixelData(
                    BitmapPixelFormat.Bgra8,
                    BitmapAlphaMode.Premultiplied,
                    (uint)render.PixelWidth,
                    (uint)render.PixelHeight,
                    DisplayInformation.GetForCurrentView().LogicalDpi,
                    DisplayInformation.GetForCurrentView().LogicalDpi,
                    pixelBuffer.ToArray());

                await encoder.FlushAsync();
            }

            // タイル作成
            var squareTile = TileContentFactory.CreateTileSquare150x150Image();
            squareTile.Image.Src = tileFile.Path;
            squareTile.Branding = TileBranding.None;

            // タイル更新
            var updater = TileUpdateManager.CreateTileUpdaterForApplication();
            updater.Update(squareTile.CreateNotification());

            deferral.Complete();
        }
    }
}


これで、タイル画像の生成と、タイルに生成した画像を割り当て登録するバックグラウンド タスクができあがりました。あとはバックグラウンド タスクを動かすようにしてあげれば OK です。


f:id:ChiiAyano:20141010122028p:plain
どうです?簡単でしょう?


ちなみに、これを使うことでタイル時計アプリも作れたりします。その辺は追々…