Использование стандартных диалогов в Windows Phone 7

Любое мобильное устройство обладает стандартной функциональностью, которая специфична для конкретной платформы. Если говорить, например, о мобильных устройствах на базе Windows Phone 7, то такая функциональность – это и запуск камеры, и звонок по телефону, и отправка электронной почты с помощью Outlook Mobile, и ряд других задач. Для того, чтобы приложения могли использовать эту функциональность существует стандартный API, которым могут воспользоваться разработчики. О нем здесь и пойдет речь.

Вообще, стандартная функциональность операционной системы доступна приложению путем использования специальных объектов Tasks (задачи), которые входят в стандартный API. Идеология строится на том, чтобы для каждого сценария иметь свою задачу. Каждая задача представлена в виде стандартного объекта, реализующего интерфейс ITask или IChooser.

Интерфейс ITask очень прост и состоит из единственного метода для запуска задачи.

public interface ITask
{
    void Show();
}

Как видно, задачи можно просто запускать, более никакой функциональности недоступно. В некоторых ситуациях этого недостаточно. Например, если мы запускаем камеру, то хотелось бы получить результат. Поэтому для таких задач появился интерфейс IChooser, который расширяет функциональность ITask.

public abstract class IChooser : ITask
{
    protected IChooser();

    public EventArgs EventArgs { get; set; }

    public abstract void Deserialize(Stream s);
    public abstract void OnInvokeReturned(byte[] outputBuffer);
    public abstract void Serialize(Stream s);
    public abstract void Show();
}

Здесь одним из наиболее важных моментов является наличие свойства EventArgs, которое и будет содержать результат выполнения задачи. Особо внимательные читатели уже, наверное, заметили, что на самом деле IChooser – это вовсе никакой не интерфейс, IChooser — это абстрактный класс. Это необходимо для того, чтобы в нем можно было реализовать часть логики.

Все задачи в Windows Phone 7 (за маленьким исключением) реализуют тот или иной интерфейс. Поэтому с большинством из них мы можем работать, абстрагируясь от конкретной задачи.

Теперь давайте посмотрим какие типы задач существуют на данный момент в Windows Phone 7 и каким образом их можно использовать. В первую очередь для того, чтобы использовать задачи, следует подключить стандартную сборку Microsoft.Phone.Tasks.

После подключения этой сборки становятся доступными объекты, выполняющие различные задачи в Windows Phone 7. Типы задач, доступные на момент написания этого материала (April 2010 CTP):

  • CameraCaptureTask – позволяет получить изображение с камеры;
  • EmailAddressChooserTask – позволяет выбрать абонента из телефонной книги и получить его e-mail;
  • EmailComposeTask – запускает задачу написания e-mail сообщения;
  • MarketplaceLauncher – запускает приложение для Marketplace;
  • MediaPlayerLauncher – запускает проигрыватель медиа-файлов;
  • PhoneCallTask – запускает приложения набора номера телефона;
  • PhoneNumberChooserTask – позволяет выбрать абонента из телефонной книги и получить его номер телефона;
  • PhotoChooserTask – позволяет выбрать фото из существующей коллекции изображений или с камеры;
  • SaveEmailAddressTask – позволяет сохранить адрес электронной почты в телефонной книге;
  • SavePhoneNumberTask – позволяет сохранить номер телефона в телефонной книге;
  • SearchTask – запускает системное окно поиска;
  • SmsComposeTask – запускает задачу написания сообщения SMS;
  • WebBrowserTask – запускает веб-браузер.

После запуска задачи и ее выполнения в некоторых случаях требуется обработать результаты выполнения задачи. Для этого можно воспользоваться двумя способами – обратиться к экземпляру класса той задачи, которую запустил пользователь и использовать свойство EventArgs, либо использовать объект ChooserListener и подписаться на событие ChooserCompleted для обработки результата. Оба подхода по большей степени равноценны, и какой из них выбрать – дело конкретного случая. Использование объекта ChooserListener сводится к следующему коду.

public MainPage()
{
    InitializeComponent();

    SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;

    ChooserListener.ChooserCompleted += new EventHandler(ChooserListener_ChooserCompleted);
}

void ChooserListener_ChooserCompleted(object sender, EventArgs e)
{
    var data = (TaskEventArgs<PhotoResult>)e;

    var photoStream = data.Result.ChosenPhoto;

    // обработка изображения
}

Теперь, когда основные принципы работы с задачами в Windows Phone 7 стали более понятны, давайте рассмотрим небольшой пример, как мы по шагам можем вызывать задачи, а также получим изображение с камеры (в случае эмулятора – виртуальной камеры).

Работа с камерой в Windows Phone 7

Создадим новый проект Windows Phone 7 и сделаем ссылку на сборку Microsoft.Phone.Tasks.В нашем приложении мы будем использовать меню для вызова разных типов задач, поэтому сделаем также ссылку на сборку Microsoft.Phone.Shell. Добавим пространство имен для Microsoft.Phone.Shell в разметку формы и создадим объект ApplicationBar.

<phoneNavigation:PhoneApplicationPage 
    x:Class="LauncherTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phoneNavigation="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}">
    <phoneNavigation:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsMenuEnabled="True">
            <shell:ApplicationBar.MenuItems>
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phoneNavigation:PhoneApplicationPage.ApplicationBar>

    <Grid x:Name="LayoutRoot" Background="{StaticResource PhoneBackgroundBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitleGrid is the name of the application and page title-->
        <Grid x:Name="TitleGrid" Grid.Row="0">
            <TextBlock Text="MY APPLICATION" x:Name="textBlockPageTitle" Style="{StaticResource PhoneTextPageTitle1Style}"/>
            <TextBlock Text="page title" x:Name="textBlockListTitle" Style="{StaticResource PhoneTextPageTitle2Style}"/>
        </Grid>

        <!--ContentGrid is empty. Place new content here-->
        <Grid x:Name="ContentGrid" Grid.Row="1">
            <Image Height="171" HorizontalAlignment="Left" Margin="179,347,0,0" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="170" />
        </Grid>
    </Grid>
    
</phoneNavigation:PhoneApplicationPage>

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

<phoneNavigation:PhoneApplicationPage 
    x:Class="LauncherTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phoneNavigation="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}">
    <phoneNavigation:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsMenuEnabled="True">
            <shell:ApplicationBar.MenuItems>
                <shell:ApplicationBarMenuItem Text="Camera" Click="CameraMenuItem_Click"/>
                <shell:ApplicationBarMenuItem Text="Email address" Click="EmailAddressMenuItem_Click"/>
                <shell:ApplicationBarMenuItem Text="Email message" Click="EmailMessageMenuItem_Click"/>
                <shell:ApplicationBarMenuItem Text="Marketplace" Click="MarketPlaceMenuItem_Click"/>
                <shell:ApplicationBarMenuItem Text="Media player" Click="MediaPlayerMenuItem_Click"/>
                <shell:ApplicationBarMenuItem Text="Phone call" Click="PhoneCallMenuItem_Click"/>
                <shell:ApplicationBarMenuItem Text="Phone number" Click="PhoneNumberMenuItem_Click"/>
                <shell:ApplicationBarMenuItem Text="Choose photo" Click="ChoosePhotoMenuItem_Click"/>
                <shell:ApplicationBarMenuItem Text="Save email" Click="SaveEmailMenuItem_Click"/>
                <shell:ApplicationBarMenuItem Text="Save phone number" Click="SavePhoneNumberMenuItem_Click"/>
                <shell:ApplicationBarMenuItem Text="Search" Click="SearchMenuItem_Click"/>
                <shell:ApplicationBarMenuItem Text="Compse SMS" Click="ComposeSMSMenuItem_Click"/>
                <shell:ApplicationBarMenuItem Text="Web" Click="WebMenuItem_Click"/>
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phoneNavigation:PhoneApplicationPage.ApplicationBar>

    <Grid x:Name="LayoutRoot" Background="{StaticResource PhoneBackgroundBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitleGrid is the name of the application and page title-->
        <Grid x:Name="TitleGrid" Grid.Row="0">
            <TextBlock Text="MY APPLICATION" x:Name="textBlockPageTitle" Style="{StaticResource PhoneTextPageTitle1Style}"/>
            <TextBlock Text="page title" x:Name="textBlockListTitle" Style="{StaticResource PhoneTextPageTitle2Style}"/>
        </Grid>

        <!--ContentGrid is empty. Place new content here-->
        <Grid x:Name="ContentGrid" Grid.Row="1">
            <Image Height="171" HorizontalAlignment="Left" Margin="179,347,0,0" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="170" />
        </Grid>
    </Grid>
    
</phoneNavigation:PhoneApplicationPage>

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Tasks;
using System.Windows.Media.Imaging;

namespace LauncherTest
{
    public partial class MainPage : PhoneApplicationPage
    {
        private CameraCaptureTask _task = new CameraCaptureTask();
        public MainPage()
        {
            InitializeComponent();

            SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;
        }

        private void CameraMenuItem_Click(object sender, EventArgs e)
        {
            (new CameraCaptureTask()).Show();
        }

        private void EmailAddressMenuItem_Click(object sender, EventArgs e)
        {
            (new EmailAddressChooserTask()).Show();
        }

        private void EmailMessageMenuItem_Click(object sender, EventArgs e)
        {
            (new EmailComposeTask()).Show();
        }

        private void MarketPlaceMenuItem_Click(object sender, EventArgs e)
        {
            MarketplaceLauncher.Show(MarketplaceContent.Applications, MarketplaceOperation.Open);
        }

        private void MediaPlayerMenuItem_Click(object sender, EventArgs e)
        {
            (new MediaPlayerLauncher()).Show();
        }

        private void PhoneCallMenuItem_Click(object sender, EventArgs e)
        {
            (new PhoneCallTask()).Show();
        }

        private void PhoneNumberMenuItem_Click(object sender, EventArgs e)
        {
            (new PhoneNumberChooserTask()).Show();
        }

        private void ChoosePhotoMenuItem_Click(object sender, EventArgs e)
        {
            (new PhotoChooserTask()).Show();
        }

        private void SaveEmailMenuItem_Click(object sender, EventArgs e)
        {
            (new SaveEmailAddressTask()).Show();
        }

        private void SavePhoneNumberMenuItem_Click(object sender, EventArgs e)
        {
            (new SavePhoneNumberTask()).Show();
        }

        private void SearchMenuItem_Click(object sender, EventArgs e)
        {
            (new SearchTask()).Show();
        }

        private void ComposeSMSMenuItem_Click(object sender, EventArgs e)
        {
            (new SmsComposeTask()).Show();
        }

        private void WebMenuItem_Click(object sender, EventArgs e)
        {
            (new WebBrowserTask()).Show();
        }
    }
}

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

Теперь осталось задать обработчик результата. Воспользуемся для этого объектом ChooserListener, и будем обрабатывать получение изображения от камеры. После выполнения задачи просто отобразим изображение на форме.

public MainPage()
{
    InitializeComponent();

    SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;

    ChooserListener.ChooserCompleted += new EventHandler(ChooserListener_ChooserCompleted);
}

void ChooserListener_ChooserCompleted(object sender, EventArgs e)
{
    if (e is TaskEventArgs<PhotoResult>)
    {
        var taskEventArgs = (TaskEventArgs<PhotoResult>)e;
        var photoStream = taskEventArgs.Result.ChosenPhoto;

        var bitmapImage = new BitmapImage();
        bitmapImage.SetSource(photoStream);

        image1.Source = bitmapImage;
    }
}

Событие ChooserCompleted вызывается после завершения каждой задачи. Однако, в каждом случае тип аргумента будет разным. Например, для задачи CameraCaptureTask аргумент будет иметь тип TaskEventArgs<PhotoResult>. Поскольку камера отсутствует в эмуляторе, то вместо реального изображения там будут сгенерирована случайная картинка.

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