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

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

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

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

Visual Studio 2015

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

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

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

Упражнение 1. Бумажник

В этом упражнении мы добавим в приложение Contoso Cookbook поддержку функции Wallet (Бумажник), чтобы члены клуба Contoso Cooking Club могли получать купоны. Эти купоны можно будет использовать для приобретения времени приготовления — вместо кредитной карты (при покупке из интерфейса приложения).

Задание 1. Изменение манифеста приложения

Чтобы использовать API функции Wallet, приложение Contoso Cookbook должно:

  • Объявить поддержку функции Wallet.
  • Создать экземпляр класса Microsoft.Phone.Wallet.Deal, представляющий в функции Wallet специальное предложение, т. е. один купон.
  • Создать и вызвать копию класса Microsoft.Phone.Wallet.WalletTransactionItem, который представляет элемент транзакции (или хранилище транзакций, например членскую карту клуба с купонами) в Бумажнике.
  • Создать экземпляр класса Microsoft.Phone.Wallet.WalletTransaction, который представляет реальную транзакцию, в том числе экземпляр WalletTransactionitem, например покупку большего количества купонов или оплату купоном.

В данном задании мы добавим функцию Wallet в манифест приложения.

  1. Откройте Visual Studio 2012.
  2. Перейдите в папку Sources\EX1\Begin.
  3. Откройте решение ContosoBookbook.sln.
  4. Найдите файл WMAppManifest.xml и дважды щелкните, чтобы открыть его.
  5. Перейдите на вкладку Capabilities (Функции) (вторая слева) и отметьте опцию ID_CAP_WALLET (см. рисунок ниже).

    Рис. 1.
    Вкладка Capability

Примечание.

Если вы открыли манифест приложения в XML-редакторе (а не в пользовательском интерфейсе, описанном выше), найдите раздел Capabilities (Функции) и добавьте следующую строку:
<Capability Name="ID_CAP_WALLET"/>.

Задание 2. Добавление информации о купоне в модель данных приложения

Класс Microsoft.Phone.Wallet.Deal представляет отдельное специальное предложение (купон). Поскольку данный класс не имеет свойства, указывающего на то, что он является частью функции Wallet, мы преобразуем его в собственный классMembersCoupon, который хранит информацию о том, внесена ли сделка в Бумажник.

  1. Правой кнопкой мыши щелкните папку DataModel и выберите Add > New Item (Добавить новый элемент). Добавьте в папку новый класс под названием MembersCoupon.
  2. Добавьте в класс объявление, реализующее интерфейс INotifyPropertyChanged.
  3. В самом начале файла добавьте следующие выражения using:
using Microsoft.Phone.Wallet;

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

class MembersCoupon : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string property)
    {
        if (null != PropertyChanged)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }

    public string CouponID { get; set; }

    private Deal coupon;
    public Deal Coupon
    {
        get { return coupon; }
        set
        {
            coupon = value;
            NotifyPropertyChanged("Coupon");
        }
    }

    bool isInWallet;
    public bool IsInWallet
    {
        get { return isInWallet; }
        set
        {
            isInWallet = value;
            NotifyPropertyChanged("IsInWallet");
        }
    }
}
  1. Данный код реализует класс, представляющий купон. Он содержит уникальный идентификатор купона, текущее состояние купона («В Бумажнике» или «Не в Бумажнике»), и объект Deal (из пространства имен Microsoft.Phone.Wallet), который является реальным специальным предложением в Бумажнике.

Задание 3. Добавление страницы управления купонами

Чтобы пользователи могли управлять купонами в своем Бумажнике, нужно реализовать страницу управления. Она отображает список неиспользованных купонов, с помощью которых можно оплачивать покупки. Страница доступна только зарегистрированным пользователям — членам клуба Contoso Cooking Club. Если пользователь еще не вступил в клуб, на странице отображается кнопка регистрации, которую мы добавим позже.

  1. Добавьте новую страницу в книжной ориентации с названием CouponsPage.
  2. Найдите основной элемент phone.PhoneApplicationPage и задайте для свойства shell:SystemTray.IsVisible значение False (когда отображается страница, панель задач в верхней части экрана скрывается).
  3. Найдите элемент LayoutRootGrid и замените его следующим кодом:
<Grid x:Name="LayoutRoot"  
Background="{StaticResource CustomApplicationBackgroundBrush}">
    <Grid.Resources>
        <local:InWalletToStringConverter  
xmlns:local="clr-namespace:ContosoCookbook.Common" 
 x:Key="InWalletToStringConverter"/>
    </Grid.Resources>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <Image x:Name="imgLogo" Source="Assets/Title.png"
 Stretch="Uniform"  HorizontalAlignment="Left" 
Width="{StaticResource LogoImageWidth}"/>
        <TextBlock x:Name="PageTitle" Text="Coupons" 
Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"  
Foreground="{StaticResource CustomGroupTitleBrush}"/>
    </StackPanel>

    <Grid x:Name="ContentPanel" Grid.Row="1" 
Margin="12,0,12,0">
        <StackPanel Orientation="Vertical" 
VerticalAlignment="Center" 
 HorizontalAlignment="Center" 
x:Name="stkMembershipOnly" >
            <TextBlock Text="To view coupons please 
become join the Contoso Cooking Club."  
TextWrapping="Wrap" 
HorizontalAlignment="Center"  
VerticalAlignment="Center" 
 Foreground="{StaticResource CustomGroupTitleBrush}"/>
            <Button Content="Join the club" 
Click="btnBuyMembership_Click"   
Margin="0,30" x:Name="btnBuyMembership" 
 Foreground="{StaticResource CustomGroupTitleBrush}"   
BorderBrush="{StaticResource CustomGroupTitleBrush}"/>
        </StackPanel>
        <TextBlock Text="Coupons for you" x:Name="txtDeals" 
 VerticalAlignment="Top" Margin="10" 
Foreground="{StaticResource CustomGroupTitleBrush}"/>
        <ListBox x:Name="lstCoupons" ItemsSource="{Binding}" 
Margin="10,50,10,10" 
SelectionChanged="lstCoupons_SelectionChanged">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="3*"/>
                            <ColumnDefinition Width="2*"/>
                        </Grid.ColumnDefinitions>
                        <TextBlock 
Text="{Binding Coupon.DisplayName}" 
 Style="{StaticResource PhoneTextTitle2Style}" 
 Foreground="{StaticResource
 CustomGroupTitleBrush}"/>
                        <TextBlock Text="{Binding Coupon.Description}"  
Grid.Row="1" TextWrapping="Wrap" 
Style="{StaticResource PhoneTextSmallStyle}" 
 Foreground="{StaticResource CustomGroupTitleBrush}"/>
                        <TextBlock Text="{Binding Coupon.ExpirationDate, 
StringFormat=Expiration Date: \{0:d\}}"  
Style="{StaticResource PhoneTextSmallStyle}"   
FontWeight="Bold"
 Foreground="{StaticResource CustomGroupTitleBrush}"
                    Grid.Row="2" HorizontalAlignment="Center"
 VerticalAlignment="Stretch"/>
                        <TextBlock Text="{Binding IsInWallet, 
Converter={StaticResource InWalletToStringConverter}}"
                    Grid.Row="2" Grid.Column="1" TextWrapping="Wrap"
 Style="{StaticResource PhoneTextSmallStyle}" 
 Foreground="{StaticResource CustomGroupTitleBrush}"/>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Grid>

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

4. Обратите внимание на объявление InWalletToStringConverter в элементе Grid.Resources и его последующее использование на странице. Данный преобразователь преобразует логическое значение IsInWallet в текстовое представление. Добавьте новый класс под названием InWalletToStringConverter в папку Common.

5. Убедитесь, что этот новый класс реализует интерфейс IValueConverter и является общим. Не забудьте добавить объявление using System.Windows.Data; в начало файла.

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

public object Convert(object value, Type targetType, object parameter,   
System.Globalization.CultureInfo culture)
{
    bool val = bool.Parse(value.ToString());
    string retVal = "Not in wallet";

    if (val)
        retVal = "In wallet";

    return retVal;
}

public object ConvertBack(object value, Type targetType, object parameter,      
 System.Globalization.CultureInfo culture)
{
    throw new NotImplementedException();
}

Приведенный выше код преобразует логическое значение в текст In wallet (В Бумажнике) или Not in wallet (Не в Бумажнике).

7. Далее переходим к файлу кода программной части. Откройте файл CouponsPage.xaml.cs.

8. В начало файла добавьте следующие объявления пространств имен:

using Microsoft.Phone.Wallet;
using System.Threading.Tasks;
using ContosoCookbook.DataModel;
using System.Windows.Media.Imaging;using System.Windows.Navigation;

9. Добавьте следующий член класса (он будет содержать список доступных купонов):

List<MembersCoupon> coupons = new List<MembersCoupon>();

10. Замените метод OnNavigatedTo. Этот метод вызывает список купонов и отображает (скрывает) кнопку Join the club! (Вступить в клуб!):

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    WalletTransactionItem membershipCard = 
Wallet.FindItem("membershipCard")  as WalletTransactionItem;

    if (null != membershipCard)
    {
        //Show coupons list
        txtDeals.Visibility = Visibility.Visible;
        lstCoupons.Visibility = Visibility.Visible;
        stkMembershipOnly.Visibility = Visibility.Collapsed;

        //Init coupons
        LoadCoupons();
    }
    else
    {
        //Hide coupons list
        txtDeals.Visibility = Visibility.Collapsed;
        lstCoupons.Visibility = Visibility.Collapsed;
        stkMembershipOnly.Visibility = Visibility.Visible;
    }
}

Данный код пытается получить экземпляр WalletTransactionItem, который представляет собой клубную карту в Бумажнике. Если этот экземпляр найден, то загружается и отображается список купонов (с помощью метода LoadCoupons, который мы добавим далее). Если экземпляр не найден, то список скрывается и отображается кнопка «Join the club!».

11. Код, добавленный на предыдущем этапе, вызывает метод LoadCoupons, который используется для загрузки купонов из Бумажника. Добавьте этот метод:

private void LoadCoupons()
{
    AddFakeCoupons();

    foreach (var coupon in coupons)
    {
        var walletDeal = Wallet.FindItem(coupon.CouponID);

        if (null != walletDeal)
            coupon.IsInWallet = true;
    }

    lstCoupons.DataContext = coupons;
}

Вышеуказанный метод вызывает метод AddFakeCoupons (мы его добавим далее) и создает набор купонов-заполнителей, а затем нумерует их. Код устанавливает свойство IsInWallet в зависимости от того, размещен ли купон в Бумажнике. На последнем этапе ListBox связывается со списком coupons для отображения.

12. Приложение Contoso Cookbook не генерирует купоны, поэтому метод LoadCoupons вызывает метод AddFakeCoupons, чтобы добавить купоны в Бумажник.

Примечание.

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

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

 

private void AddFakeCoupons()
{
    Random rnd = new Random();

    for (int i = 5; i < 50; i += 5)
    {
        Deal theDeal = new Deal("Deals." + i);
        theDeal.DisplayName = i + " minutes free";
        theDeal.Description = "Get " + i + " minutes of cooking time free";
        theDeal.ExpirationDate = DateTime.Now.AddDays(rnd.Next(1, 14));
        theDeal.Code = "DEAL_" + i;
        theDeal.IssuerName = "Contoso Cookbooks";
        theDeal.MerchantName = "Contoso Cookbooks";
        theDeal.IssuerWebsite = new Uri("http://www.contoso.com");
        theDeal.StartDate = DateTime.Now;

        CustomWalletProperty couponValueProperty =  
new CustomWalletProperty(i.ToString());
        theDeal.CustomProperties.Add("RedemptionValue", couponValueProperty);

        BitmapImage bmp = new BitmapImage();
        Uri barcodeUri = new Uri("Assets/Barcode.png", UriKind.Relative);
        bmp.SetSource(Application.GetResourceStream(barcodeUri).Stream);
        theDeal.BarcodeImage = bmp;

        bmp = new BitmapImage();
        Uri logoUri = new Uri("Assets/SmallLogo.png", UriKind.Relative);
        bmp.SetSource(Application.GetResourceStream(logoUri).Stream);
        theDeal.Logo99x99 = bmp;
        theDeal.Logo159x159 = bmp;
        theDeal.Logo336x336 = bmp;

        MembersCoupon coupon = new MembersCoupon();
        coupon.CouponID = "Deals." + i;
        coupon.Coupon = theDeal;
        coupon.IsInWallet = false;
        coupons.Add(coupon);
    }
}

Вышеуказанный код создает объекты Deal. Объект Deal объявляет специальное предложение с именем, описанием и датой окончания; специфический код приложения; название организации, выпустившей купон, а также дату вступления в силу специального предложения. Мы также можем добавить пользовательские свойства с информацией, специфической для данного предложения. В нашем случае количество минут, предоставляемое по купону, хранится как настраиваемое свойство объекта Deal.

13. Теперь, когда в список загружены купоны, пользователь может выбрать один из них и использовать. Добавьте обработчик событий SelectionChanged:

private async void lstCoupons_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (lstCoupons.SelectedIndex > -1)
    {
        var selectedCoupon = lstCoupons.SelectedItem as MembersCoupon;

        // Check if the selected coupon is already in the wallet
        if (selectedCoupon.IsInWallet)
        {
            // Show the coupon details
            NavigationService.Navigate(new Uri("/CouponView.xaml?ID=
" + selectedCoupon.CouponID, UriKind.Relative));
        }
        else
        {
            // Offer the user to purchase the coupon
            int couponPrice = int.Parse(selectedCoupon.
Coupon.CustomProperties["RedemptionValue"].Value);
            if (MessageBox.Show("This coupon is not in you wallet. 
Do you want to purchase it for "  + couponPrice  + 
" membership credits and save to your wallet before proceeding?", 
"Save Coupon", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
            {
                WalletTransactionItem membershipCard =  
Wallet.FindItem("membershipCard")  as WalletTransactionItem;
                // Extract the current balance from the WalletTransactionItem
                int currentBalance = int.Parse(membershipCard.DisplayBalance);

                if (currentBalance >= couponPrice)
                {
                    //Add purchase transaction on membership card
                    WalletTransaction transaction =  new WalletTransaction();
                    transaction.Description =  "Coupon purchase";
                    transaction.DisplayAmount =  (-couponPrice).ToString("C");
                    transaction.TransactionDate = DateTime.Now;
membershipCard.TransactionHistory.Add
("CouponPurchase_" + transaction.TransactionDate, transaction);

                    //Update card balance 
                    membershipCard.DisplayBalance = (currentBalance - couponPrice).ToString();
                    membershipCard
.DisplayAvailableBalance = membershipCard .DisplayBalance;
                    await membershipCard.SaveAsync();

                    await selectedCoupon.Coupon .SaveAsync();
                    NavigationService.Navigate(new Uri
("/CouponView.xaml?ID=" +  selectedCoupon.CouponID,  UriKind.Relative));
                }
                else
                    MessageBox.Show("Insufficient funds of membership credits. 
The coupon cannot be purchased.\nPurchase more membership credits and try again.");
            }
            else
                MessageBox.Show("Cannot show unsaved coupon details");
        }
    }

    lstCoupons.SelectedIndex = -1;
}

Данный код проверяет, добавлен ли в Бумажник выбранный купон. Если купон добавлен, то код ведет на страницу CouponView.xaml с информацией о купоне (страница будет добавлена позже).

Если купона в Бумажнике нет, то пользователю предлагается купить купон и добавить его в Бумажник. После утверждения метод ищет элемент WalletTransactionItem, который предоставляет клубную карту в Бумажнике. Этот элемент будет создан, когда пользователь вступит в клуб Contoso Cooking Club на странице MembershipPage.xaml (она будет добавлена позже).

Если у пользователя достаточно средств на карте, чтобы купить купон, в клубную карту добавляется новая транзакция покупки. Это делается путем создания нового объекта WalletTransaction и его добавления в свойствоTransactionHistory клубной карты. Затем код обновляет баланс на карте и вновь сохраняет карту в Бумажнике, а затем выводит страницу CouponView.xaml с информацией о выбранном купоне.

14. Наконец, осуществляется обработка нажатия кнопки «Join the club!» — пользователь перенаправляется на страницу MembershipPage.xaml (мы добавим ее далее):

private void btnBuyMembership_Click(object sender, RoutedEventArgs e)
{
    NavigationService.Navigate(new Uri("/MembershipPage.xaml", UriKind.Relative));
}

Задание 4. Поддержка членства в клубе

Если пользователь решит вступить в клуб Contoso Cooking Club, чтобы получать купоны, он перейдет на страницу членства. Здесь вводятся данные, необходимые для вступления в клуб. Затем приложение создает соответствующий элемент WalletTransactionItem, который предоставляет членскую карту пользователя в Бумажнике.

Примечание.

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

  1. Добавьте новую страницу в книжной ориентации с названием MembershipPage.
  2. Найдите основной элемент phone.PhoneApplicationPage и установите для свойства shell:SystemTray.IsVisible значение False.
  3. Найдите элемент LayoutRootGrid и замените его следующим кодом:
<Grid x:Name="LayoutRoot"  
Background="{StaticResource CustomApplicationBackgroundBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <StackPanel x:Name="TitlePanel" 
Grid.Row="0" Margin="12,17,0,28">
        <Image x:Name="imgLogo" Source="Assets/Title.png" 
Stretch="Uniform"  HorizontalAlignment="Left" 
Width="{StaticResource LogoImageWidth}"/>
        <TextBlock x:Name="PageTitle" 
Text="new member" Margin="9,-7,0,0" 
Style="{StaticResource PhoneTextTitle1Style}"
Foreground="{StaticResource CustomGroupTitleBrush}"/>
    </StackPanel>

    <StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <StackPanel.Resources>
            <Style TargetType="TextBlock">
                <Setter Property="Foreground" 
 Value="{StaticResource CustomGroupTitleBrush}" />
                <Setter Property="FontSize"  
Value="{StaticResource PhoneFontSizeSmall}" />
                <Setter Property="Margin" Value="20,0,0,0"/>
            </Style>
            <Style TargetType="TextBox">
                <Setter Property="Width" Value="460"/>
            </Style>
        </StackPanel.Resources>

        <TextBlock x:Name="FirstNameLabel" 
Text="First Name"/>
        <TextBox x:Name="FirstNameInput"/>
        <TextBlock x:Name="LastNameLabel" 
Text="Last Name"/>
        <TextBox x:Name="LastNameInput"/>
        <TextBlock x:Name="PhoneNumberLabel" 
Text="Phone Number"/>
        <TextBox x:Name="PhoneNumberInput"
 InputScope="TelephoneNumber"/>
        <TextBlock x:Name="EmailLabel" Text="Email"/>
        <TextBox x:Name="EmailInput" InputScope="EmailUserName"/>
        <Button x:Name="btnSignUp" Click="btnSignUp_Click" Content="Join"  
Foreground="{StaticResource CustomGroupTitleBrush}"  
BorderBrush="{StaticResource CustomGroupTitleBrush}"/>
    </StackPanel>
</Grid>

Вышеуказанный код определяет страницу с формой, в поля которой вводится имя пользователя, телефон и адрес электронной почты: данные, необходимые для вступления в клуб Contoso Cooking Club.

4. Далее переходим к файлу кода программной части. Откройте файл MembershipPage.xaml.cs.

5. В начало файла добавьте следующие объявления пространств имен:

using Microsoft.Phone.Wallet;
using System.Windows.Media.Imaging;
using System.Reflection;
using Microsoft.Phone.Tasks;

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

AddWalletItemTask addWalletItemTask = new AddWalletItemTask();

Чтобы при работе в фоновом режиме пользовательский интерфейс приложения сохранял производительность, мы будем использовать задачи Windows Phone. Задача AddWalletItemTask выполняется в фоновом режиме и по окончании создает событие Completed. В результате скорость отклика пользовательского интерфейса остается высокой.

7. В конце конструктора добавьте следующее событие:

addWalletItemTask.Completed += addWalletItemTask_Completed;

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

void addWalletItemTask_Completed(object sender, AddWalletItemResult e)
{
    if (e.TaskResult == Microsoft.Phone.Tasks.TaskResult.OK)
    {
        MessageBox.Show(e.Item.DisplayName + " was added to your wallet!");

        NavigationService.GoBack();
    }
    else if (e.TaskResult == Microsoft.Phone.Tasks.TaskResult.Cancel)
    {
        MessageBox.Show("Save operation was cancelled");
    }
    else if (e.TaskResult == Microsoft.Phone.Tasks.TaskResult.None)
    {
        MessageBox.Show("None");
    }
}

Данный обработчик событий оповещает пользователя о результате транзакции в Бумажнике: успехе или неудаче.

9. Задача AddWalletItemTask, которая используется в вышеуказанных методах, запускается, когда пользователь коснется кнопки Join. Добавьте обработчик событий для данной кнопки:

private void btnSignUp_Click(object sender, RoutedEventArgs e)
{
    try
    {
        WalletTransactionItem membershipItem;
        membershipItem = new WalletTransactionItem("membershipCard");
        membershipItem.IssuerName = "Contoso Cookbooks";
        membershipItem.DisplayName = "Contoso Cooking Club Membership";
        membershipItem.IssuerPhone.Business = "+1 (425) 555 1234";
        membershipItem.CustomerName = 
FirstNameInput.Text + " " + LastNameInput.Text;
        membershipItem.AccountNumber = Guid.NewGuid().ToString();
        membershipItem.BillingPhone = PhoneNumberInput.Text;
        membershipItem.IssuerWebsite = new Uri("http://www.contoso.com");
        membershipItem.CustomProperties.Add("email", 
new CustomWalletProperty(EmailInput.Text));
        membershipItem.DisplayAvailableBalance = "100";
        membershipItem.DisplayBalance = "100";

        BitmapImage bmp = new BitmapImage();
        Uri logoUri = new Uri("Assets/SmallLogo.png", UriKind.Relative);
        bmp.SetSource(Application.GetResourceStream(logoUri).Stream);

        membershipItem.Logo99x99 = bmp;
        membershipItem.Logo159x159 = bmp;
        membershipItem.Logo336x336 = bmp;
        addWalletItemTask.Item = membershipItem;
        addWalletItemTask.Show();
    }
    catch (Exception ex)
    {
        MessageBox.Show("The following error occurred 
when saving your membership to the wallet: " + ex.Message);
    }
}

Данный код создает экземпляр элемента WalletTransactionItem с именем membershipCard, который используется в методе LoadCoupons. Эмитент Contoso Cookbook создает клубную карту: она содержит информацию о покупателе, начальный баланс и адаптированные изображения. Затем создайте свойство Item задачи AddWalletItemTask для вновь созданного элемента WalletTransactionItem и вызовите метод Show, чтобы завершить транзакцию в пользовательском интерфейсе Wallet UI.

Задание 5. Отображение купонов

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

  1. Добавьте новую страницу в книжной ориентации с названием CouponView.
  2. Найдите основной элемент phone.PhoneApplicationPage и установите для свойства shell:SystemTray.IsVisible значение False.
  3. Найдите элемент LayoutRootGrid и замените его следующим кодом:
<Grid x:Name="LayoutRoot"  
Background="{StaticResource CustomApplicationBackgroundBrush}">
    <Grid.Resources>
        <local:IsUsedToVisibilityConverter  
xmlns:local="clr-namespace:ContosoCookbook.Common"  
x:Key="IsUsedToVisibilityConverter"/>
    </Grid.Resources>

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <Image x:Name="imgLogo" Source="Assets/Title.png" 
Stretch="Uniform" HorizontalAlignment="Left" 
 Width="{StaticResource LogoImageWidth}"/>
        <TextBlock x:Name="PageTitle" Text="Coupon" 
Margin="9,-7,0,0"  Style="{StaticResource PhoneTextTitle1Style}"
 Foreground="{StaticResource CustomGroupTitleBrush}"/>
    </StackPanel>

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <Grid.RowDefinitions>
            <!-- Coupon Title -->
            <RowDefinition Height="Auto"/>
            <!-- Description -->
            <RowDefinition Height="Auto"/>
            <!-- Expiration -->
            <RowDefinition Height="Auto"/>
            <!-- Padding -->
            <RowDefinition Height="30"/>
            <!-- Barcode -->
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBlock Grid.Row="0" x:Name="DealTitle"  
Text="{Binding DisplayName}"  
Style="{StaticResource PhoneTextTitle1Style}"  
Foreground="{StaticResource CustomGroupTitleBrush}"/>
        <TextBlock Grid.Row="1" x:Name="DealDescription"  
Text="{Binding Description}"  Style="{StaticResource PhoneTextNormalStyle}" 
 Foreground="{StaticResource CustomGroupTitleBrush}"/>
        <TextBlock Grid.Row="2" x:Name="DealExpiration" Text=""  
Foreground="{StaticResource PhoneAccentBrush}"  Margin="12,0,0,0"/>
        <Image Grid.Row="4" x:Name="DealBarcode"  
Source="{Binding BarcodeImage}" 
 Visibility="{Binding IsUsed, Converter={StaticResource IsUsedToVisibilityConverter}}"  
Stretch="None" VerticalAlignment="Center"  HorizontalAlignment="Center" />
    </Grid>
</Grid>

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

4. Класс IsUsedToVisibilityConverter преобразует свойство логического выражения IsUsed в свойство Visibility путем привязки. Добавьте новый класс IsUsedToVisibilityConverter в папку Common.

5. Убедитесь, что этот новый класс реализует интерфейс IValueConverter и является общим.

6. В начало файла добавьте следующие объявления пространств имен:

using System.Windows;
using System.Windows.Data;

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

public object Convert(object value, Type targetType, 
object parameter,   
System.Globalization.CultureInfo culture)
{
    bool val = bool.Parse(value.ToString());
    return val ? Visibility.Visible : Visibility.Collapsed;
}

public object ConvertBack(object value, 
Type targetType, object parameter,       
System.Globalization.CultureInfo culture)
{
    throw new NotImplementedException();
}

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

8. Далее переходим к файлу кода программной части. Откройте файл CouponView.xaml.cs.

9. В начало файла добавьте следующие объявления пространств имен:

using Microsoft.Phone.Wallet;

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

Deal currentCoupon;

11. Замените метод OnNavigatedTo на:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    string passedID = "";
    if (NavigationContext.QueryString.
TryGetValue("ID", out passedID))
    {
        // Set the current deal.
        currentCoupon = Wallet.FindItem(passedID) as Deal;

        // Set the data context to current deal.
        DataContext = currentCoupon;

        // Set the expiration text
        DealExpiration.Text = "Expires " + 
currentCoupon.ExpirationDate.Value.ToShortDateString();

        BuildApplicationBar();
    }
}

Вышеуказанный метод извлекает идентификатор купона из строки запроса, загружает информацию о купоне из Бумажника и соответствующим образом настраивает текстовые блоки. Затем он вызывает метод BuildApplicationBar, который мы добавим далее.

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

private void BuildApplicationBar()
{
    // Set the page's ApplicationBar to a
 new instance of ApplicationBar.
    ApplicationBar = new ApplicationBar();

    if (!currentCoupon.IsUsed)
    {
        ApplicationBarIconButton appBarButton = 
new ApplicationBarIconButton(new Uri("/Assets/UseCoupon.png", 
  UriKind.Relative));
        appBarButton.Text = "Use";
        appBarButton.Click += appBarButton_Click;
        ApplicationBar.Buttons.Add(appBarButton);
    }
    else
    {
        ApplicationBarIconButton appBarButton =    
 new ApplicationBarIconButton(new Uri("/Assets/RemoveCoupon.png",      
UriKind.Relative));
        appBarButton.Text = "Remove";
        appBarButton.Click += appBarButtonRemoveUsed_Click;
        ApplicationBar.Buttons.Add(appBarButton);
    }
}

Вышеуказанный метод добавляет кнопку Use (Использовать) или Remove (Удалить) в строку меню приложения в соответствии с текущим состоянием купона: использован или не использован.

13. Далее добавьте обработчик событий для кнопки Remove:

private void appBarButtonRemoveUsed_Click
(object sender, EventArgs e)
{
    if (null != currentCoupon)
    {
        if (MessageBox.Show("Remove used coupon?", 
"My Coupons",  MessageBoxButton.OKCancel) == MessageBoxResult.OK)
        {
            Wallet.Remove(currentCoupon.Id);
            NavigationService.GoBack();
        }
    }
}

14. Теперь добавьте обработчик событий для кнопки Use:

private async void appBarButton_Click
(object sender, EventArgs e)
{
   if (null != currentCoupon)
    {
        if (MessageBox.Show("Use the coupon?", 
"My Coupons", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
        {
            //Redeem coupon value
            int value =  int.Parse(currentCoupon.CustomProperties["RedemptionValue"].Value);
            App.RemainingCookingTime += value;

            currentCoupon.IsUsed = true;
            await currentCoupon.SaveAsync();

            MessageBox.Show("Coupon used. Feel free to remove it from your wallet");
            NavigationService.GoBack();
        }
    }
}
  1. Вышеуказанные обработчики событий изменяют время приготовления и при необходимости удаляют купон из Бумажника.

  2. Добавленные методы используют изображения Barcode.pngUseCoupon.png и RemoveCoupon.png. Давайте добавим их в качестве актива. В основном проекте найдите папку Assets.
  3. Щелкните проект правой кнопкой и выберите пункт Add &gt; Existing (Добавить — Существующий).
  4. Добавьте из папки Assets следующие файлы изображений:
    1. Barcode.png
    2. UseCoupon.png
    3. RemoveCoupon.png

Задание 6. Поддержка главной страницы купона

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

  1. Найдите страницу MainPage.xaml.

    Найдите элемент phone:PhoneApplicationPage.ApplicationBar и установите для свойства IsMenuEnabled значение True, чтобы отобразилось меню.

  2. В элемент shell:ApplicationBar добавьте следующий код:
<shell:ApplicationBar.MenuItems>
    <shell:ApplicationBarMenuItem x:Name="mnuCoupons" 
Text="Coupons" Click="mnuCoupons_Click"/>
</shell:ApplicationBar.MenuItems>

Вышеуказанный код добавляет в меню кнопку Coupons (Купоны).

3. Далее обратимся к файлу кода программной части. Откройте файл MainPage.xaml.cs.

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

private void mnuCoupons_Click(object sender, EventArgs e)
{
    NavigationService.Navigate(new Uri
("/CouponsPage.xaml", UriKind.Relative));
}

При нажатии на эту кнопку отображается страница CouponsPage.xaml (мы добавили ее в начале этого упражнения). Она запускает процесс работы с купоном.

Задание 7. Добавление фонового агента Бумажника

В коммерческой версии приложения Contoso Cookbook информация на клубной карте должна синхронизироваться с данными на сервере, отвечающем за управление пользователями и их купонами. Чтобы это сделать, мы создадим фоновый агент, запускаемый в случае, если инфраструктура Бумажника определит, что необходимо обновление данных. Фоновый агент — это класс, производный от класса WalletAgent и реализованный в проекте запланированных заданий агента в том же решении.

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

  1. Создайте новый проект Windows Phone Scheduled Task Agent (Агент запланированных заданий Windows Phone) в решении ContosoCookbook.sln. Назовите его ContosoCookbookWalletAgent. В качестве целевой версии операционной системы выберите Windows Phone OS 8.0.
  2. В созданном проекте откройте файл ScheduledAgent.cs.

    В самом начале файла добавьте следующее выражение using:

using Microsoft.Phone.Wallet;

3. Выберите объявление класса, производное от WalletAgent:

public class ScheduledAgent : WalletAgent

4. Удалите метод OnInvoke, который принадлежит к предыдущему базовому классу, и добавьте следующий метод переопределения OnRefreshData (он выполняется, когда происходит вызов агента для обновления информации в Бумажнике):

protected override async void OnRefreshData(RefreshDataEventArgs args)
{
    foreach (var item in args.Items)
    {
        WalletTransactionItem membershipCard = item    as WalletTransactionItem;
        if (null != membershipCard) //membership card
        {
            if (int.Parse(membershipCard.DisplayBalance) < 5)
            {
                //Simulate purchasing more credits
                WalletTransaction transaction =  new WalletTransaction();
                transaction.Description = "Credits purchase";
                transaction.DisplayAmount = 100.ToString("C");
                transaction.TransactionDate = DateTime.Now;

                membershipCard.Message = "Membership credits balance filled";
                membershipCard.MessageNavigationUri =  new Uri("/MainPage.xaml", UriKind.Relative);
                membershipCard.DisplayBalance = "100";
                membershipCard.DisplayAvailableBalance = 
membershipCard.DisplayBalance;
                membershipCard.TransactionHistory.Add
( "CreditsPurchase_" +   transaction.TransactionDate, transaction);
            }
            else
            {
                membershipCard.Message = "You might be 
interested in special deals for you. Click here to check it out!";
                membershipCard.MessageNavigationUri =  
new Uri("/CouponsPage.xaml", UriKind.Relative);

                //Simulate updating balance as result of usage
                membershipCard.DisplayBalance = 
 (int.Parse(membershipCard.DisplayBalance) -  5).ToString();
                membershipCard.DisplayAvailableBalance =  
membershipCard.DisplayBalance;
            }

            await membershipCard.SaveAsync();
        }

        Deal coupon = item as Deal; //coupon
        if (null != coupon)
        {
            if (coupon.ExpirationDate.Value.Date <= DateTime.Now.Date)
            {
                //Remove coupon
                Wallet.Remove(coupon.Id);
            }
            else
            {
                coupon.Message = "You might be interested in 
special deals for you. Click here to check it out!";
                coupon.MessageNavigationUri =  
new Uri("/CouponsPage.xaml",  UriKind.Relative);
                await coupon.SaveAsync();
            }
        }
    }

    NotifyComplete();
}
  1. Вышеуказанный метод вызывается операционной системой для обновления данных в Бумажнике. Он эмулирует получение информации о клубной карте и обновление этих сведений, зачисляя средства на текущий купон в Бумажнике, как если бы он был куплен пользователем. Данный метод также эмулирует загрузку новых специальных предложений с сервера, которые пользователь может оплатить позже.

Задание 8. Тестирование приложения

Теперь приложение готово, и мы можем приступить к его тестированию.

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

    Рис. 2.
    Главный экран

  2. Коснитесь одной из незаблокированных групп рецептов и прокрутите совмещенный элемент управления, чтобы открыть элемент Recipes (Рецепты).

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

  3. Выполните прокрутку экрана и выберите рецепт, на приготовление которого потребуется минимум 40 минут. Коснитесь кнопки меню в нижней части экрана.

    Рис. 4.
    Рецепт с длительным временем приготовления

  4. Коснитесь кнопки Set Alarm (Установить таймер). Вы должны увидеть, что у вас не хватает купленных минут для приготовления данного блюда.

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

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

    Рис. 6.
    Главная страница с меню AppBar

  6. Коснитесь страницы меню Coupons.

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

  7. Поскольку вы еще не вступили в клуб (в вашем Бумажнике нет клубной карты), коснитесь кнопки Sign-in for membership (Вступить в клуб).

    Рис. 8.
    Форма регистрации членства

  8. Заполните форму и коснитесь кнопки Join (Зарегистрироваться).

    Рис. 9.
    Добавление в Бумажник

  9. Коснитесь кнопки Save (Сохранить), чтобы завершить транзакцию и сохранить новую клубную карту в Бумажнике.

    Рис. 10.
    Страница сохранения в Бумажнике

  10. Коснитесь кнопки ОК, чтобы подтвердить операцию и перейти к странице купонов.

    Рис. 11.
    Купоны

    11. Прокрутите страницу и найдите купон номиналом 40 минут. Коснитесь его. Коснитесь кнопки Save (Сохранить), чтобы сохранить купон в Бумажнике.

    Рис. 12.
    Сохранение купона

    12. На странице купонов коснитесь кнопки Use (Использовать).

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

    13. Коснитесь кнопки ОК в нижней части страницы.

  11. Рис. 14.
    Использование купона

    14. Коснитесь кнопки ОК, чтобы закрыть сообщение о подтверждении.
  12. 15. Перейдите к рецепту, который вас заинтересовал, и коснитесь кнопки Set Alarm (Установить таймер) в меню еще раз. На этот раз запустится таймер оповещения.
    Рис. 15.
    Страница рецепта с запущенным таймеромВместо того чтобы покупать время приготовления за деньги через пользовательский интерфейс приложения, вы использовали купон.

  13. Далее проверим Бумажник. Перейдите в начальный экран и прокрутите его влево, чтобы отобразить приложение. Прокрутите экран вниз, найдите приложение Wallet и коснитесь его значка.

    Рис. 16.
    Приложение Wallet (Бумажник)

  14. Проверьте свой баланс. В Бумажнике коснитесь элемента Contoso Cookbook.

    Рис. 17.
    Элемент Бумажника

  15. Изучите другие элементы в Бумажнике.
  16. На этом практическое занятие окончено.

Краткая информация:

На этом практическом занятии мы добавим в приложение поддержку функции Wallet (Бумажник). Она позволяет реализовать хранение кредитов и купонов, которые можно использовать в приложении в различных целях.

Назад