По моему мнению, одной из сильных сторон Windows Phone является его дизайн. Точнее концепция дизайна, которую может использовать разработчик для получения максимума отдачи при разработке своих приложений.
Одна из ключевых концепций – Live Tiles или «живые тайлы» — «иконки приложений на стероидах». Правильное использование Live Tiles привязывает пользователя к приложению и даёт совсем другие ощущения при использовании. Например, когда я выбирал приложение для прогноза погоды, ключевым критерием для меня являлось то, чтобы приложение имело Live Tile и оперативно обновляло прогноз. В результате, после настройки приложения и закрепления Live Tile на стартовом экране, я практически больше это приложение не запускал, но при этом, сразу же после снятия лок-скрина вижу оперативную информацию о погоде.
Хороший пример, подумаете вы, но как нам сделать так?
Приложение, которым я пользуюсь обновляет свои Live Tile (а их может быть несколько, т.к. я могу закрепить Live Tile для нескольких городов) используя периодического фонового агента.
Давайте на примере разберёмся, как реализовать обновление Live Tile из фонового агента.
Сначала я подготовим 2 фоновые картинки для тайлов. Я сделал следующие:
и
Это PNG файлы, размера 173х173 на прозрачном фоне, чтобы через него была видна подложка цвета темы, установленной сейчас на телефоне.
Потом создадим простой проект на основе Windows Phone Application на C#. Добавим подготовленные картинки в него. Я их добавил под простыми названиями 1.png и 2.png. В настройках нужно установить для них тип Content и условия копирования Copy if newer.
Перейдём непосредственно к разработке приложения.
У нас будет обновляться основной тайл приложения и один вторичный тайл.
Замечание: У приложения всегда есть тайл приложения, даже если он не закреплён на стартовом экране. Вторичных тайлов может быть несколько и их необходимо создавать из приложения.
Добавим вторую страницу в приложение и назовём её LandingPage.xaml — она будет открываться, когда будет нажиматься вторичный тайл нашего приложения.
Перейдём к стартовой странице MainPage.xaml приложения и добавим на неё кнопку:
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="LIVE TILES UPDATE DEMO" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="настройки" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel>
<Button Height="100" Content="Добавить ещё тайл" Name="TileAdder" Click="TileAdder_Click"></Button>
</StackPanel>
</Grid>
В код добавим обработчик нажатия кнопки, в котором будем добавлять вторичный тайл с некой «демо-логикой»:
private void TileAdder_Click(object sender, RoutedEventArgs e)
{
IsolatedStorageFile CounterStorage = IsolatedStorageFile.GetUserStoreForApplication();
IsolatedStorageFileStream CounterStream = CounterStorage.OpenFile("counter", System.IO.FileMode.OpenOrCreate);
int count = CounterStream.ReadByte();
if (count < 0) count = 1;
StandardTileData appTileData = new StandardTileData();
if ((count % 2) == 0)
{
appTileData.Title = "Агент 002";
appTileData.BackgroundImage = new Uri("/1.png", UriKind.RelativeOrAbsolute);
}
else
if ((count % 7) == 0)
{
appTileData.Title = "Агент 007";
appTileData.BackgroundImage = new Uri("", UriKind.RelativeOrAbsolute);
}
else
{
appTileData.Title = "Агент 000";
appTileData.BackgroundImage = new Uri("/2.png", UriKind.RelativeOrAbsolute);
}
appTileData.Count = count;
CounterStream.Seek(0, SeekOrigin.Begin);
count = count + 5;
if (count > 99) count = 99;
CounterStream.WriteByte((byte)count);
CounterStream.Close();
ShellTile.Create(new Uri("/LandingPage.xaml", UriKind.RelativeOrAbsolute), appTileData);
}
Чтобы создать вторичный тайл, сначала мы создаём объект, содержащий данные тайла, затем вызываем метод, которому передаём URI страницы, которая будет вызываться и объект с данными — всё предельно просто.
Логика вариации содержимого вторичного тайла предназначена просто для того, чтобы при демонстрации приложения, тайл создавался с разными параметрами.
Обратите внимание на использование файла со счётчиком, который динамически меняется — это имитация получения внешних данных или демонстрация синхронизации между приложением и фоновым агентом, к которому мы ещё перейдём.
Поскольку, если свойство Count больше, чем 99, отображаться будет всё равно 99, мы считаем только до 99 и спокойно можем конвертировать из int в byte.
Теперь нам нужно создать фоновый агент, который бы обновлял данные на наших тайлах. Доавим в решение проект типа Windows Phone Scheduler Task Agent, после чего добавим в основной проект ссылку на него и добавим его namespace в блок using главной страницы основного приложения.
Также в блок using главной страницы осинового приложения надо не забыть добавить namespace для работы с Isolated Storage и агентами. Я назвал проект с агентом UpdateTileAgent и в моём случае этот блок выглядит следующим образом:
using UpdateTileAgent;
using Microsoft.Phone.Scheduler;
using System.IO.IsolatedStorage;
using System.IO;
Итак, нам нужно создать и зарегистрировать фоновый агент. Я буду это делать в обработчике события Loaded основной страницы. Для этого я добавлю этот обработчик, а в обработчик добавлю код создания агента:
const string UpdateTileAgentName = "Agent-Tile";
// Constructor
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
PeriodicTask myPeriodicTask = ScheduledActionService.Find(UpdateTileAgentName) as PeriodicTask;
if (myPeriodicTask != null)
{
try
{
ScheduledActionService.Remove(UpdateTileAgentName);
}
catch (Exception ex)
{
MessageBox.Show("Невозможно удалить ранее созданный сервис:" + ex.Message);
}
}
myPeriodicTask = new PeriodicTask(UpdateTileAgentName);
myPeriodicTask.Description = "Agent-Tile";
try
{
ScheduledActionService.Add(myPeriodicTask);
#if DEBUG
ScheduledActionService.LaunchForTest(UpdateTileAgentName, TimeSpan.FromSeconds(10));
#endif
}
catch (Exception ex)
{
MessageBox.Show("Невозможно создать сервис:" + ex.Message);
}
if (ShellTile.ActiveTiles.Count() > 1) TileAdder.IsEnabled = false;
}
В целом, код достаточно простой. Хочу обратить внимание на несколько особенностей.
Периодические агенты запускаются раз в полчаса. Для отладки это не очень удобно, поэтому я использую условную директиву компиляции, в которой в отладочной сборке прошу запустить агента через 10 секунд.
#if DEBUG
ScheduledActionService.LaunchForTest(UpdateTileAgentName, TimeSpan.FromSeconds(10));
#endif
Я не хочу, чтобы можно было создать несколько вторичных тайлов со ссылкой на одну и ту же страницу. Поэтому, я проверяю количество тайлов и в зависимости от этого включаю/выключаю кнопку добавления тайлов.
if (ShellTile.ActiveTiles.Count() > 1) TileAdder.IsEnabled = false;
Поле Description агента является обязательным и без него он не добавится в планировщик.
Всё, с самим приложением мы закончили. Теперь можно приступать к агенту.
Основная работа в агенте ведётся в методе OnInvoke:
protected override void OnInvoke(ScheduledTask task)
Для того, чтобы удобно работать с тайлами, не забудьте добавить необходимые директивы в using блок кода агента. У меня он выглядит следующим образом:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Microsoft.Phone.Scheduler;
using Microsoft.Phone.Shell;
using System.IO.IsolatedStorage;
using System.IO;
Добавим в код агента обновление тайла исходя из «внешних» данных в виде счётчика и добавим немного разнообразия, используя логику, аналогичную логике создания вторичного тайла.
Я выделил код обновления тайла в отдельную функцию.
void TileUpdater(ShellTile tile, int count)
{
StandardTileData appTileData = new StandardTileData();
if ((count % 2) == 0)
{
appTileData.Title = "Агент 002";
appTileData.BackgroundImage = new Uri("/1.png", UriKind.RelativeOrAbsolute);
}
else
if ((count % 7) == 0)
{
appTileData.Title = "Агент 007";
appTileData.BackgroundImage = new Uri("", UriKind.RelativeOrAbsolute);
}
else
{
appTileData.Title = "Агент 000";
appTileData.BackgroundImage = new Uri("/2.png", UriKind.RelativeOrAbsolute);
}
appTileData.Count = count+1;
tile.Update(appTileData);
}
Сам же метод OnInvoke выглядит так:
protected override void OnInvoke(ScheduledTask task)
{
ShellTile AppTile = ShellTile.ActiveTiles.FirstOrDefault();
IsolatedStorageFile CounterStorage = IsolatedStorageFile.GetUserStoreForApplication();
IsolatedStorageFileStream CounterStream = CounterStorage.OpenFile("counter", System.IO.FileMode.OpenOrCreate);
int count = CounterStream.ReadByte();
if (count < 0) count = 1;
//специальным образом обновляем тайл приложения
TileUpdater(AppTile, count+1);
//у нас есть тайлы кроме тайла приложения
if (ShellTile.ActiveTiles.Count() > 1)
{
//первый тайл - этой тайл приложения
IEnumerable<ShellTile> tiles = ShellTile.ActiveTiles.Skip(1);
//обовляем все доступные Secondary Tiles
foreach (ShellTile st in tiles)
{
TileUpdater(st, count);
}
}
CounterStream.Seek(0, SeekOrigin.Begin);
count++;
if (count > 99) count = 99;
CounterStream.WriteByte((byte)count);
CounterStream.Close();
#if DEBUG
ScheduledActionService.LaunchForTest(UpdateTileAgentName, TimeSpan.FromSeconds(10));
#endif
NotifyComplete();
}
Что мы здесь видим? Уже знакомую нам просьбу запустить агент через 10 секунд и разделённую логику обновления основного тайла приложения и дополнительных. Хочу напомнить, что это можно реализовать, потому что, тайл приложения первый в списке и есть всегда, даже если приложение не закреплено на стартовом экране.
Также вы здесь можете обновить в агенте и другие свойства тайла. Подробнее о тайлах и свойствах можно прочитать здесь: http://msdn.microsoft.com/ru-ru/windowsphone/hh505822.
Теперь же можно собрать и запустить приложение в эмуляторе. Закрепите само приложение на стартовом экране, не забудьте создать второй тайл. Посмотрите, как меняются тайлы со временем.
Теперь, вооружившись этими знаниями, я думаю, вы без проблем добавите продвинутую функциональность Live Tiles в свои Windows Phone приложения.
UPD: Спасибо max_sokolov за напоминание. Периодически запускающиеся фоновые агенты имеют следующие свойства:
- запускаются приблизительно раз в 30 минут
- время их работы ограничено 25 секунадами (некоторые источники говорят о 15 секундах)
- потребление ими мощностей процессора ограничено
- потребление ими памяти ограничено (зависит от платформы, мин. значение — 6Мб)
- система может решить не запускать агента, если ресурсов недостаточно (например, телефон разряжен)
- если агент 3 раза завершится с ошибкой, система перестанет его запускать
- агент добавляется в расписание запуска на 14 дней, потом можно/нужно обновить
Полезные ссылки:
Центр разработки Windows Phone на MSDN
Windows Phone SDK 7.1
Форумы по разработке под Windows Phone на русском языке