Материалы по разработке
Вам понадобится

Для разработки под Windows вам понадобиться следующее ПО:

Бесплатная версия Windows 10

После обновления вы бесплатно получаете Windows 10 на свое устройство.

Visual Studio 2015

Бесплатная версия Visual Studio, позволяющая создавать приложения для платформы Microsoft Azure.

SDKs и доп. инструменты

Инструменты разработки приложений для платформы Microsoft Azure.

Упражнение 1. Покупка

В этом упражнении мы модифицируем приложение Contoso Cookbook так, чтобы пользователь мог покупать группы рецептов и время приготовления. Мы добавим поддержку библиотеки MockIAPLib, чтобы тестировать API Windows Phone Store на эмуляторе Windows Phone и обойтись без использования физического устройства с включенным режимом разработчика.

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

  1. Продукты с неограниченным сроком действия. Элементы этого типа покупаются только один раз и становятся частью приложения. Например, вы можете купить более мощное оружие в игре или песню в музыкальном магазине. Элементами с неограниченным сроком действия в приложении Contoso Cookbook являются дополнительные группы рецептов, отсутствующие в первоначальной версии. Если пользователь купит группу рецептов, они будут доступны при активации приложения в будущем.
  2. Продукты с ограниченным сроком действия. Эти элементы расходуются во время работы приложения, и их нужно периодически докупать. Например, пользователь может приобретать боеприпасы в игре либо время просмотра в приложении для воспроизведения потокового видео. В Contoso Cookbook пользователь покупает время приготовления (минуты) для различных рецептов и использует его, чтобы задавать напоминания о завершении процесса. Когда эти минуты будут израсходованы, пользователь при желании может снова купить время приготовления.

Задание 1. Добавление поддержки библиотеки MockIAPLib

В этом задании мы добавим поддержку библиотеки MockIAPLib, чтобы тестировать приложение в эмуляторе Windows Phone. Затем мы добавим поддержку интерфейса API покупки в магазине Windows Phone Store, который будет использоваться в реальном устройстве.

Примечание. Код библиотеки MockIAPLib добавляется в блок условия «#if DEBUG». Чтобы запустить приложение с помощью API Windows Phone Store, измените конфигурацию сборки на Release (Коммерческая) или удалите определение DEBUG из конфигурации отладочной сборки. Больше информации об отладочных и коммерческих сборках, а также об условной компиляции см. в документации по Visual Studio:  http://msdn.microsoft.com/en-us/library/4y6tbswk.aspx

  1. Откройте Visual Studio 2012.
  2. Перейдите в подкаталог EX1\Begin установочной папки данного практического занятия.
  3. Откройте решение ContosoBookbook.sln.
  4. Найдите папку References (Ссылки) в основном проекте и щелкните ее правой кнопкой.
  5. Выберите Add Reference… (Добавить ссылку...) и перейдите к библиотеке MockIAPLib, нажав Browse… (Просмотреть...) из меню выбора файлов.
  6. Перейдите в подкаталог Assets папки текущего проекта и выберите библиотеку MockIAPLib.dll, добавляемую в качестве ссылки.
  7. Найдите и откройте файл App.xaml.cs.
  8. В самом начале файла добавьте следующий код:
#define SIMULATE_TRIAL
#define DONT_PRESERVE_LICENSE_INFO

Позже мы расскажем об этих определениях.

9. Найдите последнее выражение using в начале файла и добавьте после него следующий код:

using System.Xml.Linq;
#if DEBUG
using MockIAPLib;
using Store = MockIAPLib;
#else
    using Store = Windows.ApplicationModel.Store;
#endif

Примечание. Поскольку мы используем блок компиляции DEBUG, приложение будет обращаться к библиотеке MockIAPLib — при компиляции отладочной сборки и к API Windows Phone Store — в конфигурации реальной коммерческой сборки. Это осуществляется с помощью псевдонима пространства имен Store, который мы задействуем в данном приложении позже. Кроме того, API библиотеки MockIAPLib и Windows.ApplicationModel.Store используют одно и то же пространство имен классов.

10. Добавьте в класс следующее статическое свойство:

public static Store.ListingInformation IAPListingInformation {get; private set;}

Это свойство содержит информацию о списке продуктов, полученную из библиотеки MockIAPLib или реального API магазина Windows Phone Store.

11. В начало конструктора, перед всеми другими вызовами, добавьте следующий вызов метода:

InitializeLicensing();

Этот вызов метода инициализирует механизм лицензирования, который мы добавим далее.

12. Теперь добавим в класс реальную логику лицензирования. Добавьте следующий метод:

private async void InitializeLicensing()
{
#if DEBUG
    //Use Mock API instead of real store
    MockIAP.Init();
    MockIAP.RunInMockMode(true);

#if DONT_PRESERVE_LICENSE_INFO
    // Clear the cache every time. Remove this to have persistence.
    MockIAP.ClearCache();
#endif

    var sri = App.GetResourceStream
(new Uri("Data\\MockupLicenseInfo.xml", UriKind.Relative));
    XDocument doc = XDocument.Load(sri.Stream);
    string xml = doc.ToString();

    MockIAP.SetListingInformation
(1, "en-us", "Contoso Cookbooks sample app", "$5.99", "Contoso Cookbooks");

    MockIAP.PopulateIAPItemsFromXml(xml);

#endif
#if SIMULATE_TRIAL
    Store.CurrentApp.LicenseInformation.IsTrial = true;
#endif

    //Load associated product listings
    IAPListingInformation = await Store.CurrentApp.LoadListingInformationAsync();
}

Примечание. Данный код использует блок условной компиляции #if DEBUG, чтобы инициализировать библиотеку MockIAPLib, если приложение скомпилировано в отладочную сборку.

Вышеуказанный код загружает эмулированную информацию из библиотеки MockIAPLib с помощью метода SetListingInformation и из XML-файла, который мы добавим позже. Если вы используете доступ к Windows Phone Store, то информация о покупке добавляется с помощью управляющего интерфейса магазина, который мы в данном материале не рассматриваем.

При использовании библиотеки MockIAPLib существует два варианта действий: мы можем указать, что покупка действительна, при каждом запуске приложения, либо выполнять сброс покупки, очищая кэш библиотеки MockIAPLib. За выполнение этих действий отвечает блок условной компиляции #if DONT_PRESERVE_LICENSE_INFO.

По завершении этапа настройки последняя строка кода наследует текущую лицензионную информацию из API Windows Phone Store с помощью метода LoadListingInformationAsync. Этот метод работает независимо от того, что мы использовали: библиотеку MockIAPLib или реальный API Windows Phone Store. Он возвращает информацию обо всех доступных покупках и покупках, сделанных пользователем ранее.

13. Найдите в Visual Studio Solution Explorer папку Data и щелкните ее правой кнопкой мыши.

14. Выберите пункт меню Add/Existing Item… (Добавить/существующий элемент...).

15. Перейдите к папке Data в средстве выбора файлов и укажите файл MockupLicenseInfo.xml.

Файл MockupLicenseInfo.xml содержит информацию о покупке: эти сведения требуются библиотеке MockIAPLib. На реальном устройстве эта информация загружается из Windows Phone Store.

Файл лицензии объявляет XML-элемент ProductListings, который содержит список продуктов, доступных для покупки. В свою очередь, каждый элемент ProductListing объявляет название и описание продукта, его уникальный идентификатор, тип продукта (с неограниченным или ограниченным сроком действия), его цену и изображение.

Кроме того, с помощью формата XML можно объявить набор ключевых слов для поиска и элемент Tag, используемый для передачи данных о продукте в приложение. В приложении Contoso Cookbook мы применяли элемент Tag для передачи имен файлов изображений группы и названия группы рецептов в приложение при покупке.

Давайте рассмотрим группу рецептов итальянской кухни. Она добавлена как подчиненный элемент элемента ProductListings:

<ProductListing Key="Italian" Purchased="false" Fulfilled="false">
    <Name>Italian Recipes</Name>
    <Description>Italian recipes pack for Contoso
 Cookbooks sample application. This recipe pack includes the following recipes:
* Tiramisu
* Lemon risotto
* Linguine with clams
* Spaghetti marinara
* Caprese Salad
* Lasagna</Description>
    <ProductId>Recipes.Durables.Italian</ProductId>
    <ProductType>Durable</ProductType>
    <FormattedPrice>$2.99</FormattedPrice>
    <ImageUri>ms-appdata:///Images/Italian/italian_group.png</ImageUri>
    <Keywords>recipes;italian</Keywords>
    <Tag>italian_group_detail.png@!!@italian_group_header.png</Tag>
</ProductListing>

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

Задание 2. Добавление возможности покупки продукта с неограниченным сроком действия

  1. Откройте файл Common\Features.cs.
  2. Найдите последнее выражение using в начале файла и добавьте после него следующий код:
#if DEBUG
using MockIAPLib;
using Store = MockIAPLib;
#else
    using Store = Windows.ApplicationModel.Store;
#endif

Этот код похож на тот, что мы добавляли в файл App.xaml.cs в предыдущем задании. Мы должны внести подобные изменения в каждый исходный файл, с которым будет использоваться библиотека MockIAPLib.

3. Добавьте новый общий класс License в качестве внутреннего в классе Features.

4. В начало класса добавьте следующие блоки условной компиляции:

#if DEBUG
    private static Store.LicenseInformation _info =
Store.CurrentApp.LicenseInformation;
#else
    private static Microsoft.Phone.Store.LicenseInformation _info = 
 new Microsoft.Phone.Store.LicenseInformation();
#endif

Как и ранее, в отладочной сборке мы будем использовать информацию о лицензировании из библиотеки MockIAPLib, а в коммерческой — информацию, полученную из API Windows Phone Store.

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

internal static bool IsGroupLicensed(string groupID)
{
    if (App.IAPListingInformation.ProductListings.ContainsKey(groupID))
    {
        var productListing = App.IAPListingInformation.ProductListings[groupID];
var liveInfo = Store.CurrentApp.LicenseInformation.ProductLicenses[productListing.ProductId];

       return liveInfo.IsActive;
    }
    else
        return true;
}

Предыдущий метод вначале проверяет, доступна ли данная группа рецептов для продажи. Если нет, то эта группа не считается продуктом и должна быть доступна всегда. Если данная группа является продуктом, доступным для продажи, то метод вызывает список продуктов из Windows Phone Store. Возвращенный результат имеет значение true, если продукт помечен в Windows Phone Store как активный, т. е. его активировали в результате предыдущей покупки.

6. Теперь изменим источник данных рецептов (загруженный при запуске приложения) так, чтобы он поддерживал механизм лицензирования, созданный нами на предыдущих этапах. Откройте файл DataModel\RecipeDataSource.cs.

7. Найдите метод LoadLocalDataAsync.

8. Найдите вызов метода group.Items.Add(recipe) в конце блока foreach и добавьте в этот вызов следующий код:

if (!Features.License.IsGroupLicensed(group.UniqueId))
    group.LicensedRequired = true;

Теперь установим для свойства группы LicensedRequired значение, которое вернул метод IsGroupLicensed, добавленный ранее. Мы создали структуру настройки покупок через пользовательский интерфейс приложения. Теперь можно реализовать процесс покупки продуктов с неограниченным и ограниченным сроками действия.

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

  1. Пользователи смогут расширять функциональность приложения, покупая дополнительные рецепты в качестве продуктов с неограниченным сроком действия. Эти группы рецептов остаются заблокированными то тех пор, пока пользователь не купит их.

    Откройте файл GroupDetailPage.xaml.

  2. Найдите второй элемент controls:PivotItem, затем перейдите к внутреннему элементу controls:PivotItem.Header и добавьте следующий код XAML в конце внутреннего элемента StackPanel, сразу же за блоком TextBlock:
<Image Source="/Assets/lock.png"        
Visibility="{Binding LicensedRequired,  Converter={StaticResource VisibilityConverter}}"  
HorizontalAlignment="Right" VerticalAlignment="Center" Margin="15, 0" />

Предыдущий код XAML объявляет значок блокировки; он отображается только для групп, свойство которых LicensedRequired имеет значение true. Предоставляя XML-файл конфигурации или настраивая Windows Phone Store, мы передаем в пользовательский интерфейс информацию о группах рецептов, которые являются бесплатными независимо от статуса покупки приложения. Некоторые группы остаются заблокированными, и их можно прибрести в качестве продуктов с ограниченным сроком действия (после того как будет куплено само приложение).

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

3. Далее мы изменим код программной части, чтобы добавить поддержку покупок из пользовательского интерфейса приложения. Откройте файл GroupDetailPage.xaml.cs.

4. Найдите обработчик событий lstRecipes_SelectionChanged.

5. Найдите блок else if (group.LicensedRequired) и добавьте к нему следующий вызов метода:

NavigationService.Navigate(new Uri("/ProductInfoPage.xaml?ID=" +  
group.UniqueId, UriKind.Relative));

Указанный код ведет пользователя на новую страницу, где будет совершаться покупка. Мы добавим эту страницу (ProductInfoPage.xaml) позже.

6. Щелкните правой кнопкой проект в Visual Studio Solution Explorer и добавьте новую страницу типа «Windows Phone Portrait Page» под названием ProductInfoPage.xaml.

7. Измените атрибут shell:SystemTray.IsVisible основного элемента на False (значение по умолчанию True).

8. Чтобы отобразить в фоне логотип и название приложения, в основном элементе LayoutRootGrid замените значение атрибута Background следующим:

{StaticResource CustomApplicationBackgroundBrush}

9. Замените содержимое элемента TitlePanelStackPanel (см. соответствующий комментарий в коде) следующим кодом:

<Image x:Name="imgLogo" Source="Assets/Title.png" 
Stretch="Uniform"  HorizontalAlignment="Left" 
Width="{StaticResource LogoImageWidth}"/>
<TextBlock x:Name="PageTitle" Text="Product Info" Margin="9,-7,0,0"        
Style="{StaticResource PhoneTextTitle1Style}"     
 Foreground="{StaticResource CustomGroupTitleBrush}"/>

10. Затем добавьте элементы управления основного пользовательского интерфейса, которые будут использоваться для отображения информации о продукте. Измените содержимое элемента ContentPanelGrid на следующее:

<Grid.RowDefinitions>
 <RowDefinition Height="Auto"/>
       <RowDefinition Height="*"/>
       <RowDefinition Height="Auto"/>
       <RowDefinition Height="Auto"/>
       <RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
       <Grid.ColumnDefinitions>
       <ColumnDefinition/>
       <ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" Grid.ColumnSpan="2"  
Style="{StaticResource PhoneTextTitle2Style}"  
Foreground="{StaticResource CustomGroupTitleBrush}"/>
<TextBlock Text="{Binding Description}" 
TextWrapping="Wrap"  Grid.Column="0" Grid.Row="1" 
Style="{StaticResource PhoneTextSmallStyle}"  
Foreground="{StaticResource CustomGroupTitleBrush}"/>
<StackPanel Grid.Column="0" Grid.Row="2" 
Orientation="Vertical" Margin="0, 10">
 <TextBlock Text="Product type:"  
Style="{StaticResource PhoneTextSmallStyle}" 
Foreground="{StaticResource CustomGroupTitleBrush}"/>
 <TextBlock Text="{Binding ProductType}" 
 Style="{StaticResource PhoneTextSmallStyle}"
 Foreground="{StaticResource CustomGroupTitleBrush}"/>
</StackPanel>
<StackPanel Grid.Column="0" 
Grid.Row="3" Orientation="Vertical">
 <TextBlock Text="Purchase price:"  
Style="{StaticResource PhoneTextSmallStyle}"
 Foreground="{StaticResource CustomGroupTitleBrush}"/>
       <TextBlock Text="{Binding FormattedPrice}" 
Style="{StaticResource PhoneTextLargeStyle}" 
Foreground="{StaticResource CustomGroupTitleBrush}" FontWeight="Bold"/>
</StackPanel>
<Image Source="{Binding ImageUri}" Grid.Column="1" 
Grid.Row="1" Grid.RowSpan="3" VerticalAlignment="Top"/>
<Button Grid.Row="4" Grid.Column="0" Content="Buy now" 
x:Name="btnPurchase"  Click="btnPurchase_Click_1"   
Background="{StaticResource CustomGroupTitleBrush}"  
HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
<Button Grid.Row="4" Grid.Column="1" 
Content="Cancel" x:Name="btnCancel"  Click="btnCancel_Click_1"   
Background="{StaticResource CustomGroupTitleBrush}"   
HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>

Этот код создает страницу ProductInfoPage.xaml, на которой отображается информация о продукте: пользователь может купить продукт или отказаться от приобретения. Перед совершением покупки пользователь должен ознакомится с подробными сведения о продукте, его типом и стоимостью.

12. Найдите и откройте созданный файл ProductInfoPage.xaml.cs.

13. Найдите последнее выражение using в начале файла и добавьте после него следующий код:

using ContosoCookbook.Common;
#if DEBUG
using MockIAPLib;
using Store = MockIAPLib;
#else
    using Store = Windows.ApplicationModel.Store;
#endif

13. Замените содержимое класса следующим кодом:

ProductListing productInfo = null;
string groupId = "";

public ProductInfoPage()
{
    InitializeComponent();
}

14. Замените метод OnNavigatedTo следующим кодом:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    groupId = NavigationContext.QueryString["ID"];
    productInfo = App.IAPListingInformation.ProductListings[groupId];

#if DEBUG
    if (null != productInfo.ImageUri)
    {
        //Tweak image URI
        string s = productInfo.ImageUri.OriginalString;
        s = s.Replace("ms-appdata://", "");
        productInfo.ImageUri = new Uri(s, UriKind.Relative);
    }
#endif

    this.DataContext = productInfo;

    base.OnNavigatedTo(e);
}

Вышеуказанный код получает текущий идентификатор группы из строки запроса навигатора и загружает информацию о продукте данной группы. С помощью условной компиляции код отображает фиктивное изображение продукта в отладочной сборке (он не работает непосредственно с API Windows Phone Store).

15. Далее мы включим обработчик событий для кнопок покупки и отмены. Добавьте следующие методы:

private async void btnPurchase_Click_1
(object sender, RoutedEventArgs e)
{
var receipt = await Store.CurrentApp.RequestProductPurchaseAsync
(productInfo.ProductId, true);

    if (productInfo.ProductType == 
Windows.ApplicationModel.Store.ProductType.Durable)
    {
        var group = App.Recipes.FindGroup(groupId);
        group.LicensedRequired =  !Store.CurrentApp.LicenseInformation.
ProductLicenses[productInfo.ProductId].IsActive;

        NavigationService.GoBack();
    }
}

private void btnCancel_Click_1(object sender, RoutedEventArgs e)
{
    NavigationService.GoBack();
}

Когда пользователь нажимает кнопку Purchase (Купить), используется API магазина (библиотека MockIAPLib или реальный магазин Windows Phone Store). API может отображать дополнительные страницы, например этапы верификации, перед тем как вывести сообщение о совершении покупки.

Примечание. Метод RequestProductPurchaseAsync принимает два параметра: идентификатор продукта и логическую переменную. Последняя определяет, должна ли операция возвращать XML-документ о совершении покупки. Этот документ используется приложением, чтобы проверить, успешно ли завершилась операция покупки. Приложение Contoso Cookbook не использует эту функцию.

  1. Если продукт (в нашем случае — группа рецептов) с неограниченным сроком действия, то нужно найти эту группу и изменить свойство LicensedRequired на противоположное. Другими словами, если покупка состоялась, этой группе больше не нужна лицензия и рецепты отображаются в приложении.

    Наконец, код возвращает страницу рецептов.

Задание 3. Тестирование покупки продукта с неограниченным сроком действия

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

  1. Скомпилируйте, разверните и запустите приложение.

    Рис. 1.

    Главная страница приложения

  2. Рядом с некоторыми группами рецептов имеется значок блокировки. Чтобы разблокировать их содержимое, группы нужно купить.

    Коснитесь заблокированной группы рецептов.

    Рис. 2.
    Заблокированная группа рецептов

  3. Прокрутите совмещенный элемент управления вверх страницы, чтобы появился элемент Recipes (Рецепты).

    Рис. 3.
    Страница группы рецептов

  4. Выберите рецепт.

    Рис. 4.
    Страница информации о продукте

  5. На странице информации о продукте приведено описание группы рецептов, поскольку она еще не куплена. Коснитесь кнопки Buy now (Купить) в левом нижнем углу экрана.
  6. Проверьте эмулированное сообщение (Mock UI) пользовательского интерфейса.
     
    Примечание. В коммерческой сборке продукта это сообщение будет заменено пользовательским интерфейсом Windows Phone Store.

    Рис. 5.
    Сообщение Mock UI

  7. Коснитесь кнопки ОК, чтобы подтвердить эмулированную покупку. Появится страница рецептов.

    Рис. 6.
    Страница рецептов

  8. Коснитесь аппаратной кнопки «Назад» и перейдите на главную страницу. Для группы рецептов больше не отображается значок блокировки.

    Рис. 7.
    Главная страница

  9. Снова коснитесь той же группы и перейдите к одному из рецептов. Убедитесь, что приложение больше не предлагает купить эти рецепты.

Задание 4. Добавление возможности покупки продукта с ограниченным сроком действия

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

  1. Откройте файл App.xaml.cs.
  2. Добавьте в класс следующее статическое свойство:
public static int RemainingCookingTime { get; set; }

3. Перейдите в конструктор и добавьте в конец следующий вызов метода:

InitReadLocalStore();

4. Теперь добавьте метод InitReadLocalStore:

private void InitReadLocalStore()
{
    RemainingCookingTime = Features.Settings.GetRemainingCookingTime();
}

Этот метод инициализирует свойство RemainingCookingTime с информацией, полученной из статического класса Settings, который мы определим позже в этом упражнении.

Чтобы реализовать поддержку лицензирования продуктов, требуется внести в файл Common\Features.cs несколько изменений. Мы добавим несколько новых методов и их вызов из основного кода приложения.

5. Продуктами с ограниченным сроком действия в приложении Contoso Cookbook является время (пакеты минут) приготовления. Добавим код, который будет управлять приобретенными продуктами с ограниченным сроком действия и отслеживать потраченные минуты.

6. Найдите файл Common\Features.cs и откройте его.

7. Добавьте в класс Features следующий внутренний класс. Этот класс содержит всю информацию о покупке продуктов с ограниченным сроком действия.

public static class Settings
{
    public static int GetRemainingCookingTime()
    {
        int remainingCookingTime = 0;

        var appSettings = IsolatedStorageSettings.ApplicationSettings;
        if (appSettings.Contains("RemainingCookingTime"))
            remainingCookingTime =  (int)appSettings["RemainingCookingTime"];
        else
        {
            remainingCookingTime = 10;
            appSettings.Add("RemainingCookingTime", 10);
        }

        return remainingCookingTime;
    }

    public static void SetRemainingCookingTime(int remainingCookingTime)
    {
        var appSettings = IsolatedStorageSettings.ApplicationSettings;

        if (appSettings["RemainingCookingTime"] != null)
            appSettings["RemainingCookingTime"] = remainingCookingTime;
        else
            appSettings.Add("RemainingCookingTime", remainingCookingTime);
    }
}

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

8. Далее изменим метод, который устанавливает в приложении напоминание о времени приготовления. Это напоминание использует созданную нами инфраструктуру управления временем приготовления.

9. Найдите метод SetReminder в классе Notifications.

10. Найдите блок if (!IsScheduled(item.UniqueId)).

11. В самом начале блока if добавьте следующий код:

var schedule = ScheduledActionService.Find(item.UniqueId);
if (null != schedule)
    ScheduledActionService.Remove(schedule.Name);

Если вышеуказанный код определяет, что таймер активен, он удаляет его и создает новый.

12. В конце блока if добавьте следующий код:

//Consume time
App.RemainingCookingTime -= item.PrepTime;
Features.Settings.SetRemainingCookingTime(App.RemainingCookingTime);

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

13. Найдите блок else над блоком if и в конце добавьте следующий код:

//Redeem time as user cancelled
App.RemainingCookingTime += item.PrepTime;
Features.Settings.SetRemainingCookingTime(App.RemainingCookingTime);

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

14. Далее мы реализуем в приложении покупку времени приготовления. Откройте файл RecipeDetailPage.xaml.cs.

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

private bool HasEnoughCookingMinutes(int duration)
{
    if (App.RemainingCookingTime >= duration)
        return true;
    else
        return false;
}

16. Найдите обработчик событий btnStartCooking_Click.

17. Добавьте следующий код:

if (HasEnoughCookingMinutes(item.PrepTime) ||  
Features.Notifications.IsScheduled(item.UniqueId))
{
    Features.Notifications.SetReminder(item);
    SetScheduleBar(item.UniqueId);
}
else
    NavigationService.Navigate( new Uri
("/ProductSelectionPage.xaml?Keyword=minutes",  UriKind.Relative));

Указанный код проверяет наличие достаточного количества времени, вызывая метод HasEnoughCookingMinutes, добавленный ранее. Если нет, то мы перенаправляем пользователя на страницу ProductSelectionPage.xaml, где можно купить дополнительное время. Мы добавим эту страницу позже.

18. Щелкните правой кнопкой проект в Visual Studio Solution Explorer и добавьте новую страницу типа «Windows Phone Portrait Page» под названием ProductSelectionPage.xaml.

19. Измените атрибут shell:SystemTray.IsVisible основного элемента phone:PhoneApplicationPage на False (значение по умолчанию True).

20. Чтобы отобразить логотип и название приложения, измените значение элемента Background под основным элементом LayoutRootGrid на:

{StaticResource CustomApplicationBackgroundBrush}

21. Замените содержимое элемента TitlePanelStackPanel следующим кодом:

<Image x:Name="imgLogo" Source="Assets/Title.png"
Stretch="Uniform"  HorizontalAlignment="Left"
Width="{StaticResource LogoImageWidth}"/>

<TextBlock x:Name="PageTitle" Text="Products" Margin="9,-7,0,0"     
Style="{StaticResource PhoneTextTitle1Style}"       
Foreground="{StaticResource CustomGroupTitleBrush}"/>

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

<Grid.RowDefinitions>
 <RowDefinition Height="Auto"/>
       <RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
 <TextBlock Text="You don't have enough cooking time left!"     
 Style="{StaticResource PhoneTextSmallStyle}" 
Foreground="{StaticResource CustomGroupTitleBrush}"/>
 <TextBlock x:Name="txtRemainingCookingTime"      
Text="{Binding StringFormat='Your remaining time is \{0:D\} minutes'}"
 Style="{StaticResource PhoneTextSmallStyle}"      
Foreground="{StaticResource CustomGroupTitleBrush}"/>
 <TextBlock Text="Please purchase additional time from the products below:"          
Style="{StaticResource PhoneTextSmallStyle}"      
Foreground="{StaticResource CustomGroupTitleBrush}"/>
</StackPanel>
<ListBox Grid.Row="1"
 x:Name="lstProducts" ItemsSource="{Binding}"   
SelectionChanged="lstProducts_SelectionChanged_1" Margin="10">
 <ListBox.ItemTemplate>
       <DataTemplate>
              <Border BorderBrush="{StaticResource CustomGroupTitleBrush}" 
Margin="0, 5" BorderThickness="2">
 <Grid>
                            <Grid.RowDefinitions>
                                  <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="Auto"/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="3*"/>
                                    <ColumnDefinition Width="2*"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Text="{Binding Value.Name}"     
 Style="{StaticResource PhoneTextTitle2Style}"     
Foreground="{StaticResource CustomGroupTitleBrush}"/>
                                <TextBlock Text="{Binding Value.Description}"     
Grid.Row="1" TextWrapping="Wrap"      
Style="{StaticResource PhoneTextSmallStyle}"     
Foreground="{StaticResource CustomGroupTitleBrush}"/>
                                <TextBlock Text="{Binding Value.FormattedPrice}"     
Style="{StaticResource PhoneTextLargeStyle}"      
Foreground="{StaticResource CustomGroupTitleBrush}"      
FontWeight="Bold" Grid.Column="1"      
Grid.ColumnSpan="2"      HorizontalAlignment="Center"      
VerticalAlignment="Stretch"/>
                            </Grid>
 </Border>
 </DataTemplate>
 </ListBox.ItemTemplate>
</ListBox>

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

23. Найдите и откройте созданный файл ProductSelectionPage.xaml.cs.

24. Найдите последнее выражение using в начале файла и добавьте после него следующий код:

#if DEBUG
using MockIAPLib;
using Store = MockIAPLib;
#else
    using Store = Windows.ApplicationModel.Store;
#endif

25. Замените содержимое класса следующим кодом:

public ProductSelectionPage()
{
    InitializeComponent();
}

protected override async void OnNavigatedTo(NavigationEventArgs e)
{
    string searchTerm = NavigationContext.QueryString["Keyword"];
    var results = await Store.CurrentApp.
LoadListingInformationByKeywordsAsync(new string[] { searchTerm });

    lstProducts.DataContext = results.ProductListings;
    txtRemainingCookingTime.DataContext = App.RemainingCookingTime;

    await Store.CurrentApp.LoadListingInformationAsync();

    base.OnNavigatedTo(e);
}

private void lstProducts_SelectionChanged_1(object sender,   
SelectionChangedEventArgs e)
{
    if (lstProducts.SelectedIndex > -1)
    {
        var selection = (KeyValuePair<string,   
 ProductListing>)lstProducts.SelectedItem;
        NavigationService.Navigate(new Uri
("/ProductInfoPage.xaml?ID=" + selection.Key, UriKind.Relative));
    }
}

При переходе на страницу вышеуказанный код загружает список продуктов, которые соответствуют поисковым словам, введенным в строке запроса. Давайте свяжем этот список с элементом управления lstProducts на странице, что позволит пользователям выбирать продукт для покупки. После того как пользователь выберет продукт, данный код выполняет переход на страницу ProductInfoPage.xaml, добавленную ранее, где можно завершить покупку.

26. Теперь добавим на страницу ProductInfoPage.xaml поддержку покупки продуктов с ограниченным сроком действия. Откройте файл ProductInfoPage.xaml.cs.

27. Найдите обработчик событий btnPurchase_Click_1.

28. В конце блока if добавьте следующий код:

else if (productInfo.ProductType ==   
 Windows.ApplicationModel.Store.ProductType.Consumable)
{
     if (Store.CurrentApp.LicenseInformation.
ProductLicenses[productInfo.ProductId].IsActive)
    {
        //Fulfill -- app logic
        App.RemainingCookingTime += int.Parse(productInfo.Tag);
        Features.Settings.SetRemainingCookingTime(App.RemainingCookingTime);

        //Report fulfillment
        Store.CurrentApp.ReportProductFulfillment(productInfo.ProductId);
    }

    NavigationService.RemoveBackEntry();
    NavigationService.GoBack();
}
  1. При покупке продукта с ограниченным временем действия вышеуказанный код добавляет купленное время в свойство App.RemainingCookingTime и сообщает о завершении покупки продукта в Windows Phone Store. Завершение покупки означает, что приобретенный продукт с ограниченным временем действия предоставлен покупателю и транзакция завершена.

    Когда это будет выполнено, код пропускает предыдущую страницу покупки и возвращает страницу рецептов.

Задание 5. Тестирование покупки продукта с ограниченным сроком действия

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

  1. Скомпилируйте, разверните и запустите приложение.

    Рис. 8.
    Главная страница приложения

  2. Коснитесь разблокированной группы рецептов.

    Рис. 9.
    Страница группы рецептов

  3. Прокрутите совмещенный элемент управления вверх страницы, чтобы появился элемент Recipes (Рецепты).

    Рис. 10.
    Страница группы рецептов

  4. Коснитесь одного из рецептов.
  5. Коснитесь кнопки будильника в нижней части экрана.

    Рис. 11.
    Страница рецепта

  6. Откроется страница Products (Продукты), где можно купить время приготовления. На данном этапе мы этого еще не сделали.

    Рис. 12.
    Страница покупки продуктов

  7. Коснитесь продукта «10 Minutes» («10 минут»), а затем — кнопки Buy now (Купить).

    Рис. 13.
    Страница подтверждения покупки

  8. На странице рецептов выберите рецепт, для которого требуется более длительное время приготовления, и коснитесь кнопки с будильником. Должно появиться предложение купить дополнительное время.

Предыдущая |  Следующая