Статьи

Как создать динамические живые плитки

Одной из самых инновационных функций Windows Phone является начальный экран с его Live Tiles. В оригинальном выпуске приложения были ограничены одной плиткой, которую пользователь мог прикрепить к стартовому экрану из списка приложений. Это изменилось с выпуском Mango, где пользователь может выбрать несколько плиток на начальном экране, представляющих разные части приложения. В этой статье мы рассмотрим, как вы можете создавать живые плитки и, в частности, как вы можете динамически создавать фоновое изображение для плиток.

Мы начнем с простого макета страницы, содержащего два элемента управления Button и изображение. Первая кнопка будет использоваться для запуска CameraCaptureTask для захвата фотографии с камеры. Фотография будет отображаться в элементе управления изображением. Наконец, вторая кнопка закрепит фотографию и будет использовать ее в качестве фона для новой живой плитки.

<StackPanel x:Name="ContentPanel" Grid.Row="1" 
                       Margin="{StaticResource PhoneHorizontalMargin}">
    <Button Content="Take Photo" Click="CaptureClick" />
    <Border Height="200" Width="200" Padding="5"
                    Background="{StaticResource PhoneInactiveBrush}" >
        <Image x:Name="PhotoImage" />
    </Border>
    <Button Content="Create Live Tile" Click="CreateClick" />
</StackPanel>

Чтобы сделать снимок, нам нужно создать экземпляр CameraCaptureTask, подключить событие Completed и вызвать метод Show, когда нажата кнопка «Сделать фото». Захваченная фотография копируется в изолированное хранилище, загружается в BitmapImage и отображается в элементе управления изображениями PhotoImage.

 private string filename;
private CameraCaptureTask camera = new CameraCaptureTask();
public MainPage() {
    InitializeComponent();

    camera.Completed += camera_Completed;
}

void camera_Completed(object sender, PhotoResult e) {
    filename = Guid.NewGuid() + ".jpg";
    using (var file = new IsolatedStorageFileStream(filename, FileMode.Create, 
                FileAccess.Write, IsolatedStorageFile.GetUserStoreForApplication())) {
        e.ChosenPhoto.CopyTo(file);
    }

    DisplayImage(filename);
}

private void CaptureClick(object sender, RoutedEventArgs e) {
    camera.Show();
}

private void DisplayImage(string filename) {
    using (var file = new IsolatedStorageFileStream(filename, FileMode.Open, FileAccess.Read, IsolatedStorageFile.GetUserStoreForApplication())) {
        var image = new BitmapImage();
        image.CreateOptions = BitmapCreateOptions.None;
        image.SetSource(file);

        this.PhotoImage.Source = image;
    }
}

Выполнение кода на этом этапе позволит пользователю запустить камеру, сделать снимок и отобразить его в приложении, как показано на рисунке 1.

Dynamic Live Tiles Figure 1

фигура 1

Следующим шагом является создание фонового изображения для Live Tile. Мы можем сделать это, просто создав WriteableBitmap и отрисовывая различные элементы по отдельности. Однако это затрудняет проектирование и настройку макета плитки. Альтернативой является создание UserControl и использование Expression Blend для разметки элементов. Следующий код UserControl создает простой макет с TextBlock, наложенным на элемент управления Image.

 <UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    x:Class="LiveTileSample.FrontTile"
    Width="173" Height="173"
    d:DesignWidth="173" d:DesignHeight="173">
    <Grid>
        <Image Stretch="Fill" Source="{Binding Image}"/>
        <TextBlock TextWrapping="Wrap" Text="{Binding Title}" VerticalAlignment="Top"
                                                  Margin="{StaticResource PhoneMargin}"/>
    </Grid>
</UserControl>

Вы также заметите, что свойство Text объекта TextBlock и свойство Source в изображении используют привязку данных для определения их значения. Это позволяет легко корректировать содержимое тайла без необходимости настройки самого UserControl, просто устанавливая DataContext для экземпляра UserControl. Следующий класс TileData будет представлять содержимое UserControl.

 public class TileData {
    public string ImageFilename { get; set; }
    public string Title { get; set; }
    public BitmapImage Image {
        get {
            using (var file = new IsolatedStorageFileStream(ImageFilename, FileMode.Open, 
                        FileAccess.Read, IsolatedStorageFile.GetUserStoreForApplication())) {
                var image = new BitmapImage();
                image.CreateOptions = BitmapCreateOptions.None;
                image.SetSource(file);
                return image;
            }
        }
    }
}

Когда пользователь нажимает кнопку «Создать Live Tile», будет создан новый экземпляр класса TileData, содержащий имя файла фотографии, снятой ранее, и текст, который будет наложен. Это будет установлено как DataContext в экземпляре FrontTile UserControl. Поскольку этот элемент управления не будет добавлен в VisualTree на странице, вам необходимо вызвать методы Arrange и Measure в UserControl, чтобы заставить его правильно отображаться.

Затем FrontTile визуализируется в новый WriteableBitmap. Опять же, вам нужно вызвать Invalidate, чтобы убедиться, что содержимое WriteableBitmap правильно отображается при сохранении его в IsolatedStorage. Вы заметите, что мы используем EditableImage, чтобы упростить сохранение WriteableBitmap в виде png. Код для EditableImage и соответствующего PngEncoder можно загрузить из блога Джо Стегмана . Обратите внимание, что этот код использует дополнительный метод FromBitmapImage, который мы добавили в класс EditableImage. Смотрите конец этой статьи для этого кода.

 private void CreateClick(object sender, RoutedEventArgs e) {
    var td = new TileData() {
                        ImageFilename = filename,
                        Title = "My first live tile" };
    var ft = new FrontTile();
    ft.DataContext = td;
    ft.Arrange(new Rect(0, 0, 173, 173));
    ft.Measure(new Size(173, 173));

    var wbmp = new WriteableBitmap(ft, null);
    wbmp.Invalidate();

    var ei = EditableImage.FromBitmapImage(wbmp);
    var tileImage = "/Shared/ShellContent/" + filename.Replace(".jpg", ".png");
    using (var file = new IsolatedStorageFileStream(tileImage, FileMode.Create, FileAccess.Write,
                IsolatedStorageFile.GetUserStoreForApplication()))
    using (var strm = ei.GetStream()) {
        strm.CopyToStream(file);
    }

    var shellData = new StandardTileData() {
                            BackgroundImage = new Uri("isostore:" + tileImage, UriKind.Absolute)
                        };
    ShellTile.Create(new Uri("/MainPage.xaml?image=" + filename, UriKind.Relative), shellData);
}

Чтобы изображение использовалось в качестве фонового изображения для Live Tile, оно должно иметь размер 173 пикселя и располагаться в папке /Shared/ShellContent После сохранения WriteableImage в изолированном хранилище вновь созданный файл можно установить в качестве BackgroundImage для нового ShellTile. Для метода Create требуется экземпляр класса StandardTileData, а также уникальный Uri, который будет запущен при нажатии пользователя на плитку. В этом случае Uri содержит страницу MainPage.xaml, которая должна быть запущена, и параметр строки запроса, image, который будет использоваться для определения того, какое изображение отображать. Последний этап этого процесса — обработка параметра строки запроса и отображение соответствующего изображения.

 protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) {
    base.OnNavigatedTo(e);

    string filename;
    if (NavigationContext.QueryString.TryGetValue("image", out filename)) {
        DisplayImage(filename);
    }
}

На рисунке 2 показано приложение, запущенное с захваченным изображением, а затем второе изображение, показывающее Live Tile, созданный, когда пользователь нажимает кнопку «Create Live Tile».

Dynamic Live Tiles Figure 2

фигура 2

И наконец, вот код, добавленный в класс EditableImage для генерации EditableImage из WriteableBitmap.

 public static EditableImage FromBitmapImage(WriteableBitmap source) {
    var ei = new EditableImage(source.PixelWidth, source.PixelHeight);
    for (int idx = 0; idx < source.PixelHeight; idx++)  {
        for (int jdx = 0; jdx < source.PixelWidth; jdx++) {
            var px = source.Pixels[idx * source.PixelWidth + jdx];
            var a = (byte)((px >> 24));
            var r = (byte)((px >> 16) & 0xff);
            var g = (byte)((px >> 8) & 0xff);
            var b = (byte)((px >> 0) & 0xff);

            ei.SetPixel(jdx, idx, r, g, b, a);
        }
    }
    return ei;
}