Разработка под Windows Phone: Часть 6: Оповещения, Live Tiles и Push Notification

Так ли нужна одновременная работа нескольких приложений в фоновом режиме? На самом деле, в большинстве случаев, нам нужно, чтобы приложение имело возможность оповестить пользователя о том, что что-то изменилось или необходимы какие-то действия с его стороны. И в этой части серии статей мы познакомимся с возможностями системы, которые позволяют нам оповещать пользователя, напоминать ему или даже предоставлять саму востребованную информацию без необходимости запускать приложение!

Предупреждения (Alarm) и напоминания (Reminder)

Приложения в Windows Phone могут использовать оповещения двух типов: предупреждения (Alarms) и напоминания (Reminders), которые будут отображаться пользователю в виде диалоговых окон, по расписанию. Интерфейс оповещений и напоминаний соответствует тому, который используют системные приложения. Это позволяет сторонним приложениям общаться с пользователем на знакомом ему языке системного интерфейса.

Объекты Alarm и Reminder наследуются от класса ScheduledNotification и имеют достаточно много общего, однако, есть и определенный отличия. Классический вариант оповещения Alarm – это будильник, а оповещения Reminder – напоминание о событии в календаре.

Обратите внимание, что каждое приложение может зарегистрировать до 50 оповещений, а точность отображения пользователю – около минуты.

Давайте кратко рассмотрим основные параметры оповещений.

Предупреждения (Alarm)

Предупреждение (Alarm) отображается пользователю  в виде диалогового окна с 2-мя кнопками отложить (snooze) и закрыть (dismiss), а также тремя текстовыми блоками. Верхний блок – название приложения, которое зарегистрировало предупреждение. Далее, текстовый блок, который отображается всегда и не может быть изменён с надписью Будильник (Alarm) и, самый последний блок – текст, который был указан при создании предупреждения.

Предупреждение (Alarm) позволяет указать звуковой файл, который будет проигран при отображении его пользователю, он проигрывается с нарастающей громкостью.

Если пользователь коснётся любой области оповещения, кроме кнопок, будет загружено основное приложение и отобразится его начальная страница, так, как будто пользователь запустил его из  списка программ.

Напоминание (Reminder)

Напоминание (Reminder) отображается пользователю  в виде диалогового окна с 2-мя кнопками, выпадающим списком для выбора, на сколько отложить оповещение и тремя текстовыми блоками.

Верхний блок – название приложения, которое зарегистрировало предупреждение. Далее, текстовый блок, который отображает заголовок оповещения, который был указан при его создании, и, самый последний блок – текст – содержание напоминания, указанный при создании.

Если пользователь коснётся любой области оповещения, кроме кнопок, будет загружено основное приложение, при этом, разработчик, при создании напоминания может указать URI страницы с параметрами, на которые перейдёт пользователь.

В отличие от предупреждения (Alarm) будет вегда использоваться звук для оповещения установленный в настройках устройства.

Пишем менеджер оповещений

Давайте теперь на практике попробуем разобраться с оповещениями.

Создадим новое приложение на базе стандартного шаблона Windows Phone Application и назовём приложение SimpleNotificationManager.

После того, как проект приложения будет создан, добавьте в приложение поддержку Silverlight for Windows Phone Toolkit.

Убедитесь, что у вас установлен NuGet Package Manager (Tools -> Extension Manager …)

При необходимости – установите.

После этого в меню Visual Studio выберите, Tools -> Library Package Manager -> Manage NuGet Packages …

Откроется графическая утилита установки пактов NuGet. Найдите и установите пакет Silverlight for Windows Phone Toolkit.

После этого, ваш проект будет выглядеть следующим образом.

Перед тем, как продолжить, для всех картинок в папке Toolkit.Content установите тип сборки (Build Action) в Content (по умолчанию стоит Resource).

Итак, Silverlight for Windows Phone Toolkit подключён к проекту. Давайте добавим, как мы это уже делали ранее, пространство имён toolkit в XAML файл MainPage.xaml, чтобы использовать элементы управления из Silverlight for Windows Phone Toolkit на нашей главной странице:

Мы добавили следующую строку:

xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"

И в результате XAML будет выглядеть следующим образом:

<phone:PhoneApplicationPage 
    x:Class="SimpleNotificationManager.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

Теперь нужно сделать интерфейс создания оповещений. Нужен выбор типа, выбор времени оповещения, заголовка (в случае напоминания) и содержания. Я сделал простой интерфейс на базе комбинации элементов управления StackPanel:

При этом, XAML код страницы MainPage.xaml (внутри тега phone:PhoneApplicationPage)  выглядит следующим образом:

<!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
 
        <!--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="МЕНЕДЖЕР ОПОВЕЩЕНИЙ" 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>
                <RadioButton Content="предупреждение" GroupName="NotificationType" Name="AlarmType" IsChecked="True" />
                <RadioButton Content="напоминание" GroupName="NotificationType" Name="ReminderType" />
                <StackPanel Orientation="Horizontal" Margin="15,0,0,0">
                    <TextBlock Text="дата и время"  FontSize="22.667" VerticalAlignment="Center"/>
                    <toolkit:DatePicker Name="NotificationDate"/>
                    <toolkit:TimePicker Name="NotificationTime"/>
                </StackPanel>
                <StackPanel  Margin="15,15,0,0">
                    <TextBlock Name="NotificationTitleCaption" Text="заголовок оповещения"  FontSize="22.667" VerticalAlignment="Center" Visibility="Collapsed"/>
                    <TextBox Name="NotificationTitle" FontSize="22.667" Margin="-10,0,0,0"  Visibility="Collapsed"/>
                    <TextBlock Text="текст оповещения"  FontSize="22.667" VerticalAlignment="Center"/>
                    <TextBox Name="NotificationText" FontSize="22.667" Margin="-10,0,0,0"/>
                </StackPanel>
                <Button Content="установить" Name="SetNotification" FontSize="22.667" Width="450" />
                <Button Content="посмотреть список" Name="CheckNotification" FontSize="22.667" Width="450" />
                </StackPanel>
        </Grid>
    </Grid>

Добавьте в проект ещё одну страницу и назовите её NotificationList. На этой странице мы будем отображать список оповещений, созданных приложением. Отложим на время дизайн этой страницы и вернёмся к основной странице приложения.

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

Если мы назначим обработчики изменения состояний радио-кнопок в XAML коде, может сложиться такая ситуация, что они сработают ещё до того, как создадутся элементы управления, которые мы хотим скрывать/показывать.  Поэтому, мы добавим в код обработчик события Loaded страницы, а уже в него добавим регистрацию обработчиков изменения состояния радио-кнопок:

// Constructor
        public MainPage()
        {
            InitializeComponent();
 
            this.Loaded += new RoutedEventHandler(MainPage_Loaded);
        }
 
        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            AlarmType.Checked +=new RoutedEventHandler(AlarmType_Checked);
            ReminderType.Checked +=new RoutedEventHandler(ReminderType_Checked);
        }

И напишем обработчики событий радио-кнопок:

private void AlarmType_Checked(object sender, RoutedEventArgs e)
        {
             
            NotificationTitleCaption.Visibility = System.Windows.Visibility.Collapsed;
            NotificationTitle.Visibility = System.Windows.Visibility.Collapsed;
            
        }
 
        private void ReminderType_Checked(object sender, RoutedEventArgs e)
        {
            NotificationTitleCaption.Visibility = System.Windows.Visibility.Visible;
            NotificationTitle.Visibility = System.Windows.Visibility.Visible;
        }

Кнопка «посмотреть список» - позволяет перейти на страницу оповещений, зарегистрированных приложением. Добавим в XAML ссылку на обработчик события Click и напишем уже знакомый по предыдущим примерам код.

В результате XAML для кнопки будет выглядеть следующим образом:

<Button Content="посмотреть список" Name="CheckNotification" FontSize="22.667" Width="450" Click="CheckNotification_Click" />

А код, следующим образом:

private void CheckNotification_Click(object sender, RoutedEventArgs e)
        {
            NavigationService.Navigate(new Uri("/NotificationList.xaml",UriKind.RelativeOrAbsolute));
        }

Теперь осталось добавить код, который собственно и создаёт предупреждения и напоминания.

Для упрощения кода, разрешим нашему приложению в каждый момент времени иметь только одно предупреждение и одно напоминание. Зададим для них имена (должны быть уникальными среди всех, создаваемых приложением оповещений) в виде констант в классе:

const string MY_ALARM = "My Alarm";
const string MY_REMINDER = "My Reminder";

Обратите внимание, что для использования объектов Alarm и Reminder необходимо добавить следующую запись в блок using:

using Microsoft.Phone.Scheduler;

Теперь напишем две простые функции, которые будут создавать Alarm и Reminder соответственно.

private void CreateAlarm()
        {
            DateTime date = (DateTime)NotificationDate.Value;
            DateTime time = (DateTime)NotificationTime.Value;
            DateTime beginTime = date + time.TimeOfDay;
 
            if (beginTime < DateTime.Now)
            {
                MessageBox.Show("Указанное время оповещения уже прошло!");
                return;
            }
            
            if (ScheduledActionService.Find(MY_ALARM) != null)
                ScheduledActionService.Remove(MY_ALARM);
 
            Alarm myAlarm = new Alarm(MY_ALARM);
            
            myAlarm.Content = NotificationText.Text;
                        
            
            myAlarm.RecurrenceType = RecurrenceInterval.None;
            myAlarm.BeginTime = beginTime;
            myAlarm.ExpirationTime = beginTime.AddMinutes(5);
 
            ScheduledActionService.Add(myAlarm);
            
        }
 
 
        private void CreateReminder()
        {
            DateTime date = (DateTime)NotificationDate.Value;
            DateTime time = (DateTime)NotificationTime.Value;
            DateTime beginTime = date + time.TimeOfDay;
 
            if (beginTime < DateTime.Now)
            {
                MessageBox.Show("Указанное время оповещения уже прошло!");
                return;
            }
            
            if (ScheduledActionService.Find(MY_REMINDER) != null)
                ScheduledActionService.Remove(MY_REMINDER);
            
            Reminder myReminder = new Reminder(MY_REMINDER);
            myReminder.Title = NotificationTitle.Text;
            myReminder.Content = NotificationText.Text;
 
            
            myReminder.RecurrenceType = RecurrenceInterval.None;
            myReminder.BeginTime = beginTime;
            myReminder.ExpirationTime = beginTime.AddMinutes(5);
            myReminder.NavigationUri = new Uri("/NotificationList.xaml",UriKind.RelativeOrAbsolute);
 
 
 
            ScheduledActionService.Add(myReminder);
 
        }

Давайте взглянем на код функций поближе.

Перед созданием мы выполняем простую проверку, что время, на которые мы хотим назначить оповещение еще не прошло:

DateTime date = (DateTime)NotificationDate.Value;
            DateTime time = (DateTime)NotificationTime.Value;
            DateTime beginTime = date + time.TimeOfDay;
 
            if (beginTime < DateTime.Now)
            {
                MessageBox.Show("Указанное время оповещения уже прошло!");
                return;
            }

Затем, поскольку по дизайну приложения, мы можем иметь только одно предупреждение и одно оповещение, мы ищем, присутствует ли уже ранее созданное оповещение и если оно присутствует, удаляем его.

Ниже,  для примера приведён пример для Alarm:

if (ScheduledActionService.Find(MY_ALARM) != null)
                ScheduledActionService.Remove(MY_ALARM);

Дальше, в обоих функциях создаётся однократное оповещение, которое закончится через 5 минут, после указанного времени. При этом, для типа Reminder мы задаём заголовок и URI перехода, при нажатии на область уведомления.

Reminder myReminder = new Reminder(MY_REMINDER);
            myReminder.Title = NotificationTitle.Text;
            myReminder.Content = NotificationText.Text;
 
            
            myReminder.RecurrenceType = RecurrenceInterval.None;
            myReminder.BeginTime = beginTime;
            myReminder.ExpirationTime = beginTime.AddMinutes(5);
            myReminder.NavigationUri = new Uri("/NotificationList.xaml",UriKind.RelativeOrAbsolute);
 
 
 
            ScheduledActionService.Add(myReminder);

Теперь осталось добавить в XAML код ссылку на обработчик события Click:

<Button Content="установить" Name="SetNotification" FontSize="22.667" Width="450" Click="SetNotification_Click" />

И добавить в код простой обработчик:

private void SetNotification_Click(object sender, RoutedEventArgs e)
        {
            if ((bool)AlarmType.IsChecked)
                CreateAlarm();
 
            if ((bool)ReminderType.IsChecked)
                CreateReminder();
        }

Соберите приложение, запустите его (F5) и проверьте, как оно работает.

Сделаем теперь возможность просматривать созданные приложение оповещения.

Перейдем к странице  NotificationList.xaml.

Используя наши знания по связыванию данных и шаблонам представления (см. SimpleRussianRSSReader), напишем XAML код отображения данных по оповещениям:

У меня в результате получился следующий XAML код (внутри тега phone:PhoneApplicationPage)

<!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
 
        <!--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="МЕНЕДЖЕР ОПОВЕЩЕНИЙ" 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">
            <TextBlock Text="нет зарегистрированных оповещений" Name="NoNotifications" Visibility="Collapsed" FontSize="22.667"/>
            <ListBox Name="Notifications">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid Background="Transparent" Margin="0,0,0,30">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="380"/>
                                <ColumnDefinition Width="50"/>
                            </Grid.ColumnDefinitions>
                            <Grid Grid.Column="0">
 
                                <StackPanel Orientation="Vertical">
                                    <TextBlock Text="{Binding Title}" TextWrapping="NoWrap" Foreground="{StaticResource PhoneAccentBrush}" FontWeight="Bold"/>
                                    <TextBlock Text="{Binding Content}" TextWrapping="Wrap" Foreground="{StaticResource PhoneAccentBrush}"/>
 
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock Text="начало "/>
                                        <TextBlock Text="{Binding BeginTime}" HorizontalAlignment="Right"/>
                                    </StackPanel>
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock Text="окончание "/>
                                        <TextBlock Text="{Binding ExpirationTime}" HorizontalAlignment="Right"/>
                                    </StackPanel>
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock Text="повторение "/>
                                        <TextBlock Text="{Binding RecurrenceType}" HorizontalAlignment="Right"/>
                                    </StackPanel>
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock Text="в расписании? "/>
                                        <TextBlock Text="{Binding IsScheduled}" HorizontalAlignment="Right"/>
                                    </StackPanel>
                                </StackPanel>
                            </Grid>
                            <Grid Grid.Column="1">
                                <Button Tag="{Binding Name}" Content="X" Background="{StaticResource PhoneBackgroundBrush}" Foreground="Red" VerticalAlignment="Top" BorderThickness="0" Width="50" Padding="0,0,0,0" Click="DeleteNotification_Click"/>
                            </Grid>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Grid>

В связывании (Binding) используются поля объекта ScheduledNotofcation, базового для Alarm и Reminder.

Обратите внимание, что для использования объектов Alarm и Reminder необходимо добавить следующую запись в блок using:

using Microsoft.Phone.Scheduler; 

Обратите внимание, что в XAML коде предусмотрена кнопка удаления конкретного оповещения. В поле Tag объекта Button мы пишем имя (Name) оповещения, которое уникально в рамках приложения. Это позволяет нам быстро написать обработчик этой кнопки:

private void DeleteNotification_Click(object sender, RoutedEventArgs e)
        {
            string NotificationName = (string)((Button)sender).Tag;
 
            ScheduledAction myAction = ScheduledActionService.Find(NotificationName);
 
            if (myAction != null)
            {
                ScheduledActionService.Remove(NotificationName);
 
            }
        }

Теперь нужно написать функцию, которая будет получать список всех оповещений приложения и устанавливать связывания данных для объекта ListBox  Notifications.

Получить перечисляемый список объектов оповещения может следующая функция:

ScheduledActionService.GetActions<ScheduledNotification>(); 

То есть код получения списка и связывания с ListBox будет выглядеть следующим образом:

IEnumerable<ScheduledNotification> notifications = ScheduledActionService.GetActions<ScheduledNotification>(); 
FakePre-9542f59239034504817ed25b9b72d19f-76e4cc6f7c3a41cda26628383b625299

Выделим его в  отдельную функцию и добавим обработку состояния, когда у на не зарегистрировано ни одного оповещения:

private void InitNotofocationsList()
        {
            IEnumerable<ScheduledNotification> notifications = ScheduledActionService.GetActions<ScheduledNotification>();
 
            if (notifications.Count<ScheduledNotification>() > 0)
                NoNotifications.Visibility = System.Windows.Visibility.Collapsed;
            else
                NoNotifications.Visibility = System.Windows.Visibility.Visible;
 
            Notifications.ItemsSource = notifications;
        }

Поскольку, после удаления оповещения нам необходимо обновить список, добавим эту функцию в обработчик кнопки удаления оповещения:

private void DeleteNotification_Click(object sender, RoutedEventArgs e)
        {
            string NotificationName = (string)((Button)sender).Tag;
 
            ScheduledAction myAction = ScheduledActionService.Find(NotificationName);
 
            if (myAction != null)
            {
                ScheduledActionService.Remove(NotificationName);
 
                InitNotofocationsList();
            }
        }

Дальше, переопределим метод OnNavigatedTo и добавим в него вызов функции InitNotificationList():

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

Соберите приложение, запустите его (F5) и проверьте работоспособность.

В качестве самостоятельного упражнения, модифицируйте код приложения таким образом, чтобы оно могло создавать больше чем 2 оповещения. Добавьте более тщательную обработку ошибок и ввода пользователя.

Живые тайлы (Live Tiles)

Для начала определимся с тем, что такое тайл (Tile).  Тайл – это ссылка на приложение, которая отображается на панели старт. Бывает два вида тайлов. Первый - это тайл приложения (Application Tile),  который появляется, когда пользователь закрепляет (pin) приложение в панели старт.  Он один и есть у приложения всегда, даже когда ссылка на приложение не отображается в стартовой панели. Второй вид – это вторичные тайлы (Secondary Tiles), которые создаются програмно приложением по запросу пользователя. Например, если это новостное приложение, оно может предлагать возможность создать вторичный тайл, который будет относиться к какому-нибудь отдельному новостному разделу, например, мобильная разработка, веб-разработка и т.д. При этом, обычно, нажатие на такой тайл приводит пользователя не на основную страницу приложения, а на некую выделенную. При этом такой тайл может обновляться отличным от тайла приложения образом: например, показывать количество новых новостей не всего, а по определённой тематике.

Удобно представить информацию о различии в типах тайлов в виде таблицы:

  Application Tile Secondary Tiles
Как создаётся

Только пользователем, который прикрепляет приложение (pin to start) из списка в стартовую панель.

Начальные свойства тайла приложения могут быть заданы в манифесте приложения.

Пользователем в приложении, когда он выбирает создать дополнительный тайл (Create(Uri, ShellTileData) API)
Как удаляется Не удаляется. Вне зависимости от присутствия на стартовой панели. Может обновляться также вне зависимости от присутствия на стартовой панели.

Пользователем, если он открепляет тайл от стартовой панели.

При де-инсталляции приложения.

Используя Delete() API

Как обновляется

ShellTile API

ShellTileSchedule API

Push Notifications

ShellTile API

ShellTileSchedule API

Push Notifications

Обратите внимание, что Application Tile всегда первый в коллекции ActiveTiles, вне зависимости от того,  закреплён ли он в стартовой панели.

Тайлы в Windows Phone – двухсторонние. Если у тайла определены обе стороны, и фронтальная и обратная, при отображении, между ними происходит переключение. Если обратная сторона не определена (не установлено не одно из свойств обратной стороны), переключения между ними не происходит.

Ниже, показан пример фронтальной стороны тайла

В правом верхнем углу тайла, в круге, отображается свойство Count, числовое значение от 1 до 99. Если свойство Count не определёно или установлено в 0, не отображается.

В нижней части тайла находится заголовок – Title. Это строка, она должна помещаться в размер тайла, то есть максимальная длина около 15 символов.

Сам тайл заполнен фоновым рисунком – BackgroundImage.

Обратная сторона тайла выглядит похожим образом:

В нижней части тайла находится заголовок обратной стороны тайла – BackTitle. Это строка, она должна помещаться в размер тайла, то есть максимальная длина около 15 символов.

В верхней части стороны находится содержание обратной стороны тайла – BackContent. Это строка, помещается около 40 символов.

Сам тайл заполнен фоновым рисунком – BackBackgroundImage.

Обратите внимание, что при создании вторичного тайла (Secondary Tile) фоновые рисунки для фронтальной и обратной стороны тайла должны быть в локальных ресурсах. При обновлении могут использоваться и локальные и удалённые ресурсы.

Перейдём к практическому знакомству с тайлами. Создадим новое приложение на базе стандартного шаблона Windows Phone Application и назовём приложение LiveTilesExample.

В этом приложении мы научимся изменять тайл приложения, создавать вторичный тайл и обновлять фоновый рисунок фронтальной стороны тайла по расписанию с использованием ShellTileSchedule API.

Добавьте в приложение 2 картинки формата PNG и размера 173 X 173. Одну назовите front.png, а вторую back.png. После добавления картинок, установите для них тип сборки (Build Action) в Content (по умолчанию Resource).

После этого, добавьте ещё одну страницу в приложение и назовите её SecondaryPage.xaml. После того, как её добавите измените 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" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="другая" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

Перейдите к MainPage.xaml. Итак, мы попробуем обновить свойства тайла приложения, создать вторычный тайл и обновить фоновую картинку фронтальной стороны тайла приложения. Добавим три кнопки с соответствующими названиями в область контента страницы:

<!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <StackPanel>
                <Button Name="AppTile" Content="изменить тайл приложения" />
                <Button Name="SecTile" Content="установить вторичный тайл" />
                <Button Name="UpdateAppTile" Content="обновление тайла приложения" />
            </StackPanel>
        </Grid>

В окне дизайнера щелкнем двойным щелчком по кнопке «изменить тайл приложения», будет автоматически создан обработчик события Click и мы перейдём в редактор кода.

Для работы с тайлами, в блок usingнеобходимо добавить следующую запись:

using Microsoft.Phone.Shell; 


Перейдём в код метода обработки события Click кнопки AppTile и добавим код, который изменяет вид тайла приложения:

private void AppTile_Click(object sender, RoutedEventArgs e)
        {
            ShellTile apptile = ShellTile.ActiveTiles.First();
 
            StandardTileData appTileData = new StandardTileData();
            appTileData.Title = "Тайл приложения";
            appTileData.Count = 5;
            appTileData.BackgroundImage = new Uri("/front.png", UriKind.RelativeOrAbsolute);
 
            appTileData.BackTitle = "Обратная сторона";
            appTileData.BackContent = "Очень важное сообщение";
            appTileData.BackBackgroundImage = new Uri("/back.png", UriKind.RelativeOrAbsolute);
 
            apptile.Update(appTileData);
 
        }

Обратите внимание, что тайл приложения всегда первый в активном списке тайлов, даже если он не закреплён в стартовой панели.

Соберите приложение и разверните его (Deploy Solution).

В эмуляторе, перейдите в список устройств и закрепите приложение на стартовой панели (долгое нажатие на приложение в списке и далее pin to start).

Обратите внимание, как выглядит тайл приложения на стартовой панели:

Запустите приложение из стартовой панели и нажмите на кнопку кнопке «изменить тайл приложения». Далее нажмите на кнопку Back на телефоне. Обратите внимание, что тайл приложение изменился и имеет 2 стороны, которые меняются со временем.

Закройте эмулятор, чтобы в следующий раз запускать приложение на чистом устройстве.

В окне дизайнера щелкнем двойным щелчком по кнопке «установить вторичный тайл», будет автоматически создан обработчик события Click и мы перейдём в редактор кода.

Создадим вторичный тайл, по нажатию на который, пользователь будет сразу попадать на вторую страницу приложения:

private void SecTile_Click(object sender, RoutedEventArgs e)
        {
            StandardTileData secTileData = new StandardTileData();
            secTileData.Title = "Вторичный тайл";
            secTileData.Count = 5;
            secTileData.BackgroundImage = new Uri("/back.png", UriKind.RelativeOrAbsolute);
 
            secTileData.BackTitle = "Обратная сторона";
            secTileData.BackContent = "Просто сообщение";
            secTileData.BackBackgroundImage = new Uri("/front.png", UriKind.RelativeOrAbsolute);
 
            ShellTile.Create(new Uri("/SecondaryPage.xaml", UriKind.RelativeOrAbsolute), secTileData);
        }

Запустите приложение (F5).

В приложении, нажмите кнопку «установить вторичный тайл». Обратите внимание, что приложение автоматически перешло в стартовую панель, куда уже добавлен вторичный тайл. Нажмите на него и убедитесь, что отображается вторая страница приложения.

Перейдем обратно в Visual Studio и в дизайнере основной страницы щелкнем двойным щелчком по кнопке «обновление тайла приложения», будет автоматически создан обработчик события Click и мы перейдём в редактор кода.

Добавим код в обработчик. Мы обновляем один раз, через две минуты после нажатия кнопки стартуем.

private void UpdateAppTile_Click(object sender, RoutedEventArgs e)
        {
            ShellTileSchedule appTileSchedule = new ShellTileSchedule();
 
            appTileSchedule.Recurrence = UpdateRecurrence.Onetime;
 
 
            appTileSchedule.StartTime = DateTime.Now.AddMinutes(2);
 
            appTileSchedule.RemoteImageUri = new Uri("http://замените на путь к вашей картинке на удалённом сервере");
            appTileSchedule.Start();
 
        }

Замените в коде путь к картинке на уделённом сервере. Обратите внимание, что поддерживается только HTTP, размер картинки должен быть не более 80K и скачиваться она должна не более 30 секунд. Поскольку задача скачивания ставится в пакет, то картинка может обновиться в течение часа.

Эта картинка заменит фоновый рисунок фронтальной стороны тайла приложения.

Самостоятельно добавьте кнопку и обработчик, чтобы заменить фоновый рисунок фронтальной стороны вторичного тайла (будет работать только когда тайл закреплён в стартовой панели, используйте констроуктор ShellTileSchedule с параметром).

Также попробуйте создать к приложению фоновый агент (Background Agent), который будет обновлять тайлы, используя ShellTile API.

Push Notification

Возможность Push оповещений, т.е. получения информации об изменении какого-то внешнего ресурса, без необходимости его постоянно опрашивать (Poll). Этот механизм реализован через специальный внешний сервис – Microsoft Push Notification Service, который и отвечает за отсылку Push оповещения на устройство.

Схематично работа с сервисом Microsoft Push Notification Service представлена на следующем рисунке.

Приложение, которое хочет использовать Push оповещения, запрашивает URI у клиента Push сервиса оповещений (1), клиент Push сервиса взаимодействует с Microsoft Push Notification Service (MSPN), получает от него URI(2) и отдаёт его приложению (3). Приложение передаёт этот URI своему сервису, который будет формировать сообщения приложению и отсылать их методом POST в сервис MSPN (5) по указанному URI. Когда сервис MSPN получает сообщение, он запускает процедуру доставки его на устройство (6).

Обратите внимание, что сервис MSPN не оповещает сервис, формирующий сообщения о том, когда сообщение получено устройством.

Прежде чем перейти к типам оповещений, перечислим несколько базовых технических ограничений:

  • на одно приложение – один канал Push оповещений;
  • не более 30 каналов Pushоповещений на устройство;
  • максимальный размер оповещения: 1 Кб для заголовка и 3 Кб на содержимое.

Всего доступно три типа Push оповещений:

  1. Toast;
  2. Tile;
  3. Raw.

Приложение может зарегистрировать на получение Toast оповещение. Тогда, если приложение запущено, и приходит Toast оповещение, оно не  отображается в верхней части экрана. Если приложение не зарегистрировано на получение Toast оповещения или не запущено, то  Toast оповещение, отображается в верхней части экрана, сообщая пользователю о том, что что-то требует его внимания. При нажатии на оповещение запускается приложение. Разработчику доступны Deep Toast оповещения, когда при запуске открывается не стартовая страница приложения, а определённая и с определёнными параметрами.  Пример Toast оповещения – оповещение о приходе SMS.

Основное назначение Tile оповещения – обновление тайлов приложения, закреплённых на стартовой панели. Приложение не получает Tile оповещение.

Оповещение типа Raw, позволяет отправить сообщение работающему приложению. Если приложение на момент получения не запущено, сообщение не будет отправлено на устройство.

Перейдём к практическому знакомству с возможностями Push Notification. Создадим новое приложение на базе стандартного шаблона Windows Phone Application и назовём приложение PushNotificationExample.

Как понятно, необходим какой-то сервис, чтобы посылать сообщения по полученному URI в MPNS. Мы не будем останавливаться здесь на этом подробно, а просто воспользуемся примерами  тестовых ASP.NET приложений:

В примере к этому разделу все три страницы включены в один проект и объединены в одно решение с проектом PushNotificationExample для Windows Phone.

Вернёмся к приложению для Windows Phone. У него будет очень простой функционал. Приложение будет регистрировать канал и на нём ожидать всех типов оповещений: Toast, Tile и Raw.

Сначала слегка изменим и дополним дизайн 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="PUSH NOTIFICATION" 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 Name="SetPushNotification" Content="установка оповещений" />                
            </StackPanel>
        </Grid>

Двойным щелчком по кнопке «установка оповещений» в коде дизайнера создадим и перейдём в обработчик события Click кнопки.

Добавим код, который ищет канал с имеем «MyChannel», если не находит, то создаёт, после чего регистрируется на обработку событий Toast и Raw (Tile сообщение до приложения не доходит, оно напрямую меняет тайл).

private void SetPushNotification_Click(object sender, RoutedEventArgs e)
        {
            HttpNotificationChannel myChannel;
            bool isNewChannel = false;
            string myChannelName = "MyChannel";
 
            myChannel = HttpNotificationChannel.Find(myChannelName);
 
            if (myChannel == null)
            {
                myChannel = new HttpNotificationChannel(myChannelName);
                isNewChannel = true;
 
            }
 
            //получаем URI и его изменения
            myChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(myToastChannel_ChannelUriUpdated);
 
            //обрабатываем ошибки
            myChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(myToastChannel_ErrorOccurred);
 
            //регируем на получение Toast оповещения при запущеном приложении
            myChannel.ShellToastNotificationReceived += new EventHandler<NotificationEventArgs>(myToastChannel_ShellToastNotificationReceived);
 
            //регируем на получение Raw оповещения при запущеном приложении
            myChannel.HttpNotificationReceived += new EventHandler<HttpNotificationEventArgs>(myToastChannel_HttpNotificationReceived);
 
            if (isNewChannel)
                myChannel.Open();
 
            // говорим о том, что у нас будут toast оповещения
            if (!myChannel.IsShellToastBound)
                myChannel.BindToShellToast();
 
            // говорим о том, что у нас будут tile оповещения
            if (!myChannel.IsShellTileBound)
                myChannel.BindToShellTile();
 
        // если канал был раньше – посылаем URI сервису
    if (!isNewChannel)
                SendURIToService(myChannel.ChannelUri);
            
        }

Добавим обработчики событий ошибок и получения/изменения URI

void myToastChannel_ErrorOccurred(object sender, NotificationChannelErrorEventArgs e)
        {
            // здесь мы должны были бы обработать ошибку 
            // здесь просто выводим, для использования в тестировании
            Dispatcher.BeginInvoke(() =>
            {
                System.Diagnostics.Debug.WriteLine(e.Message);
                MessageBox.Show(String.Format("Error: {0}", e.Message));
            });
        }
 
        void myToastChannel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
        {
            // здесь мы должны были бы отослать URI нашему сервису
            // просто выводим, для использования в тестировании
            // из-за взаимодействия c UI - используем Dispatcher
            Dispatcher.BeginInvoke(() => SendURIToService(e.ChannelUri));
        }

В нашем случае, функция SendURIToService() просто отображает сообщение в UI, поэтому и необходимо обернуть её в диспатчер:

void SendURIToService(Uri channelURI)
        {
            // здесь мы должны были бы отослать URI нашему сервису
            // просто выводим, для использования в тестировании
            System.Diagnostics.Debug.WriteLine(channelURI.ToString());
            MessageBox.Show(String.Format("Uri: {0}", channelURI.ToString()));
        }

В завершении, добавим обработчик Toast и Raw оповещений, который будут работать при запущенном приложении:

void myToastChannel_ShellToastNotificationReceived(object sender, NotificationEventArgs e)
        {
            // здесь мы должны были бы обработать сообщение 
            // здесь просто выводим, для использования в тестировании
 
            StringBuilder sb = new StringBuilder();
 
            foreach (string key in e.Collection.Keys)
            {
                sb.AppendFormat("{0}:{1}\n", key, e.Collection[key]);
            }
 
            string result = sb.ToString();
 
            Dispatcher.BeginInvoke(() =>
            {
                System.Diagnostics.Debug.WriteLine(result);
                MessageBox.Show(result);
            });
        }

void myToastChannel_HttpNotificationReceived(object sender, HttpNotificationEventArgs e)
        {
            //добавьте свой обработчик Raw сообщения, который будет его разбирать
            Dispatcher.BeginInvoke(() =>
            {
                System.Diagnostics.Debug.WriteLine("Получено Raw сообщение.");
                MessageBox.Show("Получено Raw сообщение");
            });
 
        }

Запустите решение (F5), откройте страницу ASP.NET проекта в браузере. В окне приложения, нажмите на кнопку «установка оповещений» и дождитесь отображения URI в окне вывода отладочной информации:

Закройте диалоговое окно приложения. Скопируйте URI из окна вывода отладочной информации и перейдите на страницу Toast оповещения, введите скопированный URI, введите Title и Subtitle и, нажав на кнопку, отошлите сообщение. Поскольку приложение запущено, оно самостоятельно обработает сообщение и выведет диалоговое окно:

Toast сообщение не отобразится.

Закройте приложение, нажав кнопку Back (отладка завершится), перейдите к веб-приложению и ещё раз отправьте Toast сообщение. Теперь оно отобразится в интерфейсе пользователя.

Перейдите к списку приложений и закрепите приложение PushNotificationExample в стартовую панель (pin to start).

Перейдите к веб-приложению на страницу отправки Tile сообщения и отправьте Tile сообщение, указав Front Title и Count (не забудьте указать URI). Обратите внимание, как изменился тайл приложения.

В качестве самостоятельно работы, запустите приложение и проверьте, что оно получает Raw сообщения.

Добавьте в приложения картинки и сформируйте Tile сообщение, чтобы они использовались в обновлённом тайле.

Итоги и следующие шаги

В этой статье мы познакомились с возможностями системы, которые позволяют нам оповещать пользователя, напоминать ему или даже предоставлять саму востребованную информацию без необходимости запускать приложения.

В следующих статьях мы познакомимся с новым API работы с камерой, и работой с дополнительными аппаратными возможностями устройства, такими как компас, гироскоп и Motion-сенсор.

Файлы для загрузки

Проект SimpleNotificationManager

Проект LiveTilesExample

Проект PushNotificationExample