Навигация, всплывающие меню и панели

101

В данной статье мы продолжим создание приложения AutoShop и добавим к нему интерактивные элементы навигации, которые являются стандартными для Metro-приложений: панель приложения (AppBar) и навигационная панель (NavBar), которые обеспечивают средства, с помощью которых пользователь может взаимодействовать с вашим контентом и перемещаться внутри вашего приложения. Я также покажу, как создать всплывающие меню, которые являются всплывающими мини-окнами, использующимися для сбора информации от пользователя, обычно в ответ на взаимодействие с AppBar.

AppBar

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

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

Самый простой способ создать AppBar - объявить его в файле XAML. Ниже показана дополненная разметка файла ListItem.xaml:

<Page
    x:Class="AutoShop.Pages.ListItem"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:AutoShop"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource AppBackground}">
        ...
    </Grid>
    
    <Page.BottomAppBar>
        <AppBar>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
                    <Button x:Name="AppBarDoneButton" Style="{StaticResource DeleteAppBarButtonStyle}"
                            IsEnabled="false" Click="AppBarButtonClick"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Right">
                    <Button x:Name="AppBarAddButton" Style="{StaticResource AddAppBarButtonStyle}" 
                            Click="AppBarButtonClick"/>
                    <Button x:Name="AppBarMarksButton" Style="{StaticResource BookmarksAppBarButtonStyle}"
                            Click="AppBarButtonClick"/>
                    <Button x:Name="AppBarDiscountButton" Style="{StaticResource LikeAppBarButtonStyle}"
                            Click="AppBarButtonClick" IsEnabled="False"/>
                    <Button x:Name="AppBarHomeButton" Style="{StaticResource HomeAppBarButtonStyle}"
                            Click="AppBarButtonClick"/>
                </StackPanel>
            </Grid>
        </AppBar>
    </Page.BottomAppBar>
</Page>

Для создания панели приложения, я передал элемент управления AppBar в свойстве Page.BottomAppBar. Для создания панели навигации, которая появляется сверху, а не снизу, нужно будет использовать свойство Page.TopAppBar (навигацию создадим чуть позже).

Панель приложения внутри себя содержит элемент компоновки Grid, разбивающий ее на два столбца. В левом, как правило, размещаются элементы управления имеющие специфичную функциональность для Metro-приложений, поэтому я поместил сюда кнопку удаления записи из коллекции AutoList. В правом столбце представлены элементы управления имеющие более широкую (app-wide) функциональность - добавление записи, список марок машин, добавление скидки на машину и домашний сайт компании.

Все кнопки в панели приложения ссылаются на стандартные стили Metro. Эти стили объявлены в файле StandartStyles.xaml, при этом для кнопок изначально они закомментированы. Вы можете либо раскомментировать эти стили, либо добавить определенные стили, которые нам требуются, в словарь ресурсов AutoAppStyles.xaml. Я выбрал второй подход, т.к. нам необходимо переопределить некоторые стандартные свойства:


    <Style x:Key="DeleteAppBarButtonStyle" TargetType="ButtonBase" BasedOn="{StaticResource AppBarButtonStyle}">
        <Setter Property="AutomationProperties.AutomationId" Value="DeleteAppBarButton"/>
        <Setter Property="AutomationProperties.Name" Value="Удалить"/>
        <Setter Property="Content" Value="&#xE106;"/>
    </Style>

    <Style x:Key="AddAppBarButtonStyle" TargetType="ButtonBase" BasedOn="{StaticResource AppBarButtonStyle}">
        <Setter Property="AutomationProperties.AutomationId" Value="AddAppBarButton"/>
        <Setter Property="AutomationProperties.Name" Value="Добавить"/>
        <Setter Property="Content" Value="&#xE109;"/>
    </Style>

    <Style x:Key="HomeAppBarButtonStyle" TargetType="ButtonBase" BasedOn="{StaticResource AppBarButtonStyle}">
        <Setter Property="AutomationProperties.AutomationId" Value="HomeAppBarButton"/>
        <Setter Property="AutomationProperties.Name" Value="Сайт"/>
        <Setter Property="Content" Value="&#xE10F;"/>
    </Style>

    <Style x:Key="BookmarksAppBarButtonStyle" TargetType="ButtonBase" BasedOn="{StaticResource AppBarButtonStyle}">
        <Setter Property="AutomationProperties.AutomationId" Value="BookmarksAppBarButton"/>
        <Setter Property="AutomationProperties.Name" Value="Марки машин"/>
        <Setter Property="Content" Value="&#xE12F;"/>
    </Style>

    <Style x:Key="LikeAppBarButtonStyle" TargetType="ButtonBase" BasedOn="{StaticResource AppBarButtonStyle}">
        <Setter Property="AutomationProperties.AutomationId" Value="LikeAppBarButton"/>
        <Setter Property="AutomationProperties.Name" Value="Скидка"/>
        <Setter Property="Content" Value="&#xE19F;"/>
    </Style>

Как видите, все стили для кнопок переопределяют стиль AppBarButtonStyle, который определяет стандартный вид кнопок панели AppBars. Здесь используются три важных свойства: AutomationProperties.AutomationId - задает идентификатор для кнопки, с помощью которого ее можно будет идентифицировать в коде, AutomationProperties.Name - определяет текст, отображаемый под кнопкой, и Content - задает изображение, которое будет использоваться.

Значением свойства Content является код символа из символов шрифта Segoe UI. Вы можете увидеть значки, заданные этим шрифтом - Стандартные стили и ресурсы Metro. На рисунке показана созданная панель:

Панель AppBar

На данный момент кнопки управления на панели AppBar не имеют никакой функциональности. Давайте добавим простую функциональность для кнопок "Удалить" и "Сайт". Для этого добавим обработчик события клика AppBarButtonClick в файле ListItem.xaml.cs, а также модифицируем существующий обработчик события PropertyChanged (который реализован в виде делегата в конструкторе ListItem). Этот обработчик будет активировать кнопки "Удалить" и "Скидка", когда пользователь выберет что-нибудь в списке:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using AutoShop.Data;

namespace AutoShop.Pages
{
    public sealed partial class ListItem : Page
    {
        ViewModel viewModel;

        public ListItem()
        {
            viewModel = new ViewModel();

            this.InitializeComponent();
            this.DataContext = viewModel;
            AutoDetailFrame.Navigate(typeof(Pages.NoItemSelected));

            viewModel.PropertyChanged += (sender, args) =>
            {
                if (args.PropertyName == "SelectedItemIndex")
                {
                    if (viewModel.SelectedItemIndex == -1)
                    {
                        AutoDetailFrame.Navigate(typeof(NoItemSelected));
                        AppBarDoneButton.IsEnabled = false;
                        AppBarDiscountButton.IsEnabled = false;
                    }
                    else
                    {
                        AutoDetailFrame.Navigate(typeof(AutoDetail), viewModel);
                        AppBarDoneButton.IsEnabled = true;
                        AppBarDiscountButton.IsEnabled = true;
                    }
                }

                if (args.PropertyName == "SelectedModelList")
                {
                    Style style = (Style)Application.Current.Resources["AutoListitemHighlight"];

                    autoList.ItemContainerStyleSelector =
                        new HighlightStyleSelector(null, style, viewModel.SelectedModelList);
                }
            };
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

        private void autoList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            viewModel.SelectedItemIndex = autoList.SelectedIndex;
        }

        private async void AppBarButtonClick(object sender, RoutedEventArgs e)
        {
            if (e.OriginalSource == AppBarDoneButton
                && viewModel.SelectedItemIndex > -1)
            {
                viewModel.AutoList.RemoveAt(viewModel.SelectedItemIndex);
                viewModel.SelectedItemIndex = -1;
            }
            else if (e.OriginalSource == AppBarHomeButton)
            {
                await Windows.System.Launcher.LaunchUriAsync(new Uri("http://professorweb.ru"));
            }
        }
    }
}

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

Во-вторых здесь становиться видно преимущество использования паттерна MVVM для проектирования приложения - при удалении записи, представление автоматически обновляется, т.к. обновляется модель-представление, поэтому нам не нужно беспокоиться о деталях компоновки вспомогательных страниц NoItemSelected.xaml и AutoDetail.xaml.

Всплывающие меню

В приведенном выше примере, кнопки "Удалить" и "Сайт" имеют простейшую функциональность. Однако, как правило, большинство кнопок на панели приложения AppBar предназначены для взаимодействия с пользователем - вводом какой-то определенной информации. Для ввода этой информации мы добавим различные всплывающие меню.

На JavaScript для всплывающих меню есть специальный элемент управления Flyout, для разработчиков, использующих C#/C++/XAML придется создавать собственный элемент управления, реализующий эту функциональность. Для этого создадим специальную папку Flyouts в приложении и добавим новый пользовательский элемент управления ModelListFlyout (Project --> Add New Item --> User Control), который будет просто отображать список моделей, представленных в магазине:

<UserControl
    x:Class="AutoShop.Flyouts.ModelListFlyout"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:AutoShop.Flyouts"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="200"
    d:DesignWidth="150">

    <Popup x:Name="AutoModelPopup"
           IsLightDismissEnabled="True" Width="150" Height="200" >
        <StackPanel Background="Black">
            <Border Background="#85C54C" BorderThickness="4">
                <StackPanel>
                    <TextBlock Style="{StaticResource PopupTextStyle}"
                               Text="Автодилеры:" VerticalAlignment="Center"
                               Margin="20,12,20,4" />
                    <ListBox Height="150" ScrollViewer.VerticalScrollBarVisibility="Visible"
                             ItemsSource="{Binding ModelList}" Margin="10,2,10,10" 
                             ItemContainerStyle="{StaticResource ListBoxItemAutoListStyle}"
                             Background="Transparent"/>
                    <Button Click="OKButtonClick" HorizontalAlignment="Center"
                            Margin="20" Content="OK"/>
                </StackPanel>
            </Border>
        </StackPanel>
    </Popup>
</UserControl>

Здесь мы использовали элемент Popup, предоставляющий собой всплывающие окошко. Он содержит список всех марок машин, представленных в магазине. При объявлении Popup используются три важных атрибута: IsLightDismissEnabled - задает, будет ли исчезать элемент Popup если пользователь щелкнет в другом месте за пределами этого элемента; Width и Height - соответственно задают ширину и высоту, это необходимо для правильного позиционирования элемента.

Теперь добавьте необходимые стили для всплывающих окон в словарь ресурсов AutoAppStyles.xaml:

 <!-- Стили для всплывающих меню Flayouts -->
    <Style x:Key="PopupTextStyle" TargetType="TextBlock" BasedOn="{StaticResource BasicTextStyle}">
        <Setter Property="FontSize" Value="22" />
    </Style>
    
    <Style x:Key="ListBoxItemAutoListStyle" TargetType="ListBoxItem">
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Foreground" Value="#DDD"/>
    </Style>
    
    <Style x:Key="AddItemText" TargetType="TextBlock" 
           BasedOn="{StaticResource TextBlockListItem}" >
        <Setter Property="FontSize" Value="22"/>
        <Setter Property="HorizontalAlignment" Value="Right"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
    </Style>

    <Style x:Key="AddItemTextBox" TargetType="TextBox" BasedOn="{StaticResource AutoDetailTextBox}">
        <Setter Property="FontSize" Value="16"/>
    </Style>

Теперь добавим код C# пользовательского элемента управления ModelListFlyout. Он включает вспомогательный метод Show(), который будет открывать всплывающее окно и обработчик клика по кнопке ОК, который будет закрывать всплывающее окно:

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace AutoShop.Flyouts
{
    public sealed partial class ModelListFlyout : UserControl
    {
        public ModelListFlyout()
        {
            this.InitializeComponent();
        }

        public void Show()
        {
            AutoModelPopup.IsOpen = true;
        }

        private void OKButtonClick(object sender, RoutedEventArgs e)
        {
            AutoModelPopup.IsOpen = false;
        }
    }
}

Теперь, чтобы добавить вызов всплывающего окна ModelListFlyout в нашем приложении, нам нужно добавить этот элемент на страницу ListItem.xaml и модифицировать обработчик событий AppBarButtonClick:

<Page
    x:Class="AutoShop.Pages.ListItem"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:AutoShop"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:flyouts="using:AutoShop.Flyouts"
    mc:Ignorable="d">

    <Grid Background="{StaticResource AppBackground}">
        ...
        
        <flyouts:ModelListFlyout x:Name="modelListFlyout" Grid.ColumnSpan="2" Grid.RowSpan="3"/>
    </Grid>
    
    <Page.BottomAppBar>
        ...
    </Page.BottomAppBar>
</Page>
private async void AppBarButtonClick(object sender, RoutedEventArgs e)
{
            if (e.OriginalSource == AppBarDoneButton
                && viewModel.SelectedItemIndex > -1)
            {
                viewModel.AutoList.RemoveAt(viewModel.SelectedItemIndex);
                viewModel.SelectedItemIndex = -1;
            }
            else if (e.OriginalSource == AppBarHomeButton)
            {
                await Windows.System.Launcher.LaunchUriAsync(new Uri("http://professorweb.ru"));
            }
            else if (e.OriginalSource == AppBarMarksButton)
            {
                modelListFlyout.Show();
            }
}

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

xmlns:flyouts="using:AutoShop.Flyouts"

Это позволяет использовать префикс flyouts для вызова элемента управления из пространства имен AutoShop.Flyouts в XAML-разметке.

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

Всплывающе окно в Metro-приложении

Давайте теперь создадим всплывающие окна для кнопок "Добавить" и "Скидка", но перед этим я предлагаю добавить в папку Flyouts вспомогательный класс FlyoutHelper, который будет отвечать за позиционирование элемента Popup - было бы логично, если бы всплывающие окна отображались возле кнопок, на которых они были вызваны. Для ModelListFlyout мы этим не озадачивались, т.к. это окно не предполагает взаимодействия с пользователем.

Добавьте в папку Flyouts новый файл класса FlyoutHelper.cs (Project --> Add Class) со следующим кодом:

using System;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Shapes;
using Windows.UI.Xaml.Controls.Primitives;

namespace AutoShop.Flyouts
{
    public class FlyoutHelper
    {
        // Вспомогательный метод, получающий координаты элемента
        public static Point getOffset(UIElement control1, UIElement control2)
        {
            return control1.TransformToVisual(control2)
                .TransformPoint(new Point(0, 0));
        }

        // Выравниваем всплывающее окно по положению AppBar
        // и положению кнопки, вызвавшей это окно
        public static void ShowRelativeToAppBar(Popup popup, Page page,
            AppBar appbar, Button button)
        {
            Point popupOffset = getOffset(popup, page);
            Point buttonOffset = getOffset(button, page);

            popup.HorizontalOffset = buttonOffset.X - popupOffset.X
                - (popup.ActualWidth / 2) + (button.ActualWidth / 2);
            popup.VerticalOffset = getOffset(appbar, page).Y
                - popupOffset.Y - popup.ActualHeight;

            if (popupOffset.X + popup.HorizontalOffset
                + popup.ActualWidth > page.ActualWidth)
            {
                popup.HorizontalOffset = page.ActualWidth
                    - popupOffset.X - popup.ActualWidth;
            }
            else if (popup.HorizontalOffset + popupOffset.X < 0)
            {
                popup.HorizontalOffset = -popupOffset.X;
            }
        }
    }
}

Этот код позиционирует элемент Popup, который передается в методе ShowRelativeToAppBar, непосредственно над кнопкой, находящейся на панели приложения AppBar. Теперь добавьте новый элемент управления AddItemFlyout.xaml:

<UserControl
    x:Class="AutoShop.Flyouts.AddItemFlyout"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:AutoShop.Flyouts"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="485"
    d:DesignWidth="435">

    <Popup x:Name="AddItemPopup" IsLightDismissEnabled="True" Width="435" Height="485" Opened="AddItemPopup_Opened">
        <StackPanel Background="Black">
            <Border Background="#85C54C" BorderThickness="4">
                <Grid Margin="10">
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                        <RowDefinition/>
                        <RowDefinition/>
                        <RowDefinition Height="150"/>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="300"/>
                    </Grid.ColumnDefinitions>

                    <TextBlock Text="ID:" Style="{StaticResource AddItemText}" />
                    <TextBlock Text="Название:" Grid.Row="1" Style="{StaticResource AddItemText}" />
                    <TextBlock Text="Модель:" Grid.Row="2" Style="{StaticResource AddItemText}" />
                    <TextBlock Text="Стоимость:" Grid.Row="3" Style="{StaticResource AddItemText}" />
                    <TextBlock Text="Описание:" Grid.Row="4" Style="{StaticResource AddItemText}" 
                               VerticalAlignment="Top" />
                    <TextBlock Text="Картинка:" Grid.Row="5" Style="{StaticResource AddItemText}" />

                    <TextBox x:Name="ItemId" Grid.Column="1" Style="{StaticResource AddItemTextBox}" IsReadOnly="True" />
                    <TextBox x:Name="ItemName" Grid.Column="1" Grid.Row="1" Style="{StaticResource AddItemTextBox}"/>
                    <TextBox x:Name="ItemModel" Grid.Column="1" Grid.Row="2" Style="{StaticResource AddItemTextBox}"/>
                    <TextBox x:Name="ItemCost" Grid.Column="1" Grid.Row="3" Style="{StaticResource AddItemTextBox}"/>
                    <TextBox x:Name="ItemDescription" Grid.Column="1" Grid.Row="4" Style="{StaticResource AddItemTextBox}"
                             TextWrapping="Wrap"/>
                    <TextBox x:Name="ItemPicture" Grid.Column="1" Grid.Row="5" Style="{StaticResource AddItemTextBox}"/>

                    <StackPanel Orientation="Horizontal" Grid.Row="6" HorizontalAlignment="Center" Grid.ColumnSpan="2">
                        <Button Click="AddButtonClick">Добавить</Button>
                    </StackPanel>
                </Grid>
            </Border>
        </StackPanel>
    </Popup>
</UserControl>

Здесь нет ничего нового - простой элемент управления с несколькими текстовыми полями для ввода информации, которая используется для добавления новой машины в общий список. Куда интереснее код этого элемента, в котором мы используем модифицированный метод Show(), а также добавляем обработчик для событий Popup.Opened и Button.Click:

using System;
using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using AutoShop.Data;

namespace AutoShop.Flyouts
{
    public sealed partial class AddItemFlyout : UserControl
    {
        ViewModel viewModel;
        int id;

        public AddItemFlyout()
        {
            this.InitializeComponent();
        }

        public void Show(Page page, AppBar appbar, Button button)
        {
            AddItemPopup.IsOpen = true;
            FlyoutHelper.ShowRelativeToAppBar(AddItemPopup, page, appbar, button);
        }

        private void AddButtonClick(object sender, RoutedEventArgs e)
        {
            // Ссылка на экземпляр ViewModel, используемый в приложении
            viewModel = ((ViewModel)DataContext);

            // Добавляем в коллекцию AutoList новый элемент
            viewModel.AutoList.Add(new AutoItem
            {
                ID = id,
                Name = ItemName.Text,
                Model = ItemModel.Text,
                Cost = Int32.Parse(ItemCost.Text),
                Description = ItemDescription.Text,
                PictureSource = ItemPicture.Text
            });
            AddItemPopup.IsOpen = false;

            // Обновляем коллекцию ModelList
            viewModel.ModelList.Add(ItemName.Text);
            viewModel.SelectedModelList = ItemName.Text;
        }

        private void AddItemPopup_Opened(object sender, object e)
        {
            // Устанавливаем в поле ID номер, выше последнего в коллекции AutoList
            id = ((ViewModel)DataContext).AutoList.Last().ID + 1;
            ItemId.Text = id.ToString();
        }
    }
}

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

При этом нужно проявлять осторожность, потому что я делаю предположение, что значением этого свойства будет объект ViewModel, который я создаю в конструкторе класса ListItem (в ListItem.xaml.cs) и задаю в качестве контекста. В своих приложениях вам нужно четко следить за типом контекста данных, т.к. в разных частях приложения могут использоваться сразу несколько блоков данных.

Как только я получаю ссылку на ViewModel, я просто добавляю новый элемент в коллекцию AutoList. Теперь добавьте вызов этого окна в коде главного окна ListItem.xaml.cs:

private async void AppBarButtonClick(object sender, RoutedEventArgs e)
{
      ...
            else if (e.OriginalSource == AppBarAddButton)
            {
                addItemFlyout.Show(this, this.BottomAppBar, (Button)e.OriginalSource);
            }
}
Сложное всплывающее окно в Metro-приложении

Чтобы убедиться в работоспособности этого кода, прокрутим коллекцию AutoList вниз и убедимся в добавлении новой записи:

Добавление нового элемента из всплывающего окна

Теперь давайте добавим аналогичное окно DiscountFlyout, в котором мы будем задавать скидку для определенной машины, при этом автоматически изменяя представление, в частности поле Cost:

<UserControl
    x:Class="AutoShop.Flyouts.DiscountFlyout"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:AutoShop.Flyouts"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="130"
    d:DesignWidth="350">

    <Popup x:Name="DiscountPopup" IsLightDismissEnabled="True" Width="350" Height="130" >
        <StackPanel Background="Black">
            <Border Background="#85C54C" BorderThickness="4">
                <StackPanel>
                    <StackPanel Orientation="Horizontal" Margin="10">
                        <TextBlock Style="{StaticResource PopupTextStyle}"
                                   Text="Скидка (%):" VerticalAlignment="Center"
                                   Margin="0,0,10,0" />
                        <TextBox x:Name="txtDiscount" Height="40" Width="150" FontSize="20" />
                    </StackPanel>
                    <Button Click="OKButtonClick" HorizontalAlignment="Center"
                            Margin="10">OK</Button>
                </StackPanel>
            </Border>
        </StackPanel>
    </Popup>
</UserControl>
using System;
using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using AutoShop.Data;

namespace AutoShop.Flyouts
{
    public sealed partial class DiscountFlyout : UserControl
    {
        ViewModel viewModel;

        public DiscountFlyout()
        {
            this.InitializeComponent();
        }

        public void Show(Page page, AppBar appbar, Button button)
        {
            DiscountPopup.IsOpen = true;
            FlyoutHelper.ShowRelativeToAppBar(DiscountPopup, page, appbar, button);
        }

        private void OKButtonClick(object sender, RoutedEventArgs e)
        {
            viewModel = (ViewModel)DataContext;
            if (viewModel.SelectedItemIndex != -1)
            {
                ((AutoItem)viewModel.AutoList.ElementAt(viewModel.SelectedItemIndex)).Cost =
                    (Int32)(((AutoItem)viewModel.AutoList.ElementAt(viewModel.SelectedItemIndex))
                    .Cost * (1 - Double.Parse(txtDiscount.Text)/100));
            }

            // Обновить значение свойства SelectedItemIndex, чтобы 
            // представление автоматически обновилось (AutoDetail.xaml)
            int index = viewModel.SelectedItemIndex;
            viewModel.SelectedItemIndex = index;

            DiscountPopup.IsOpen = false;
        }
    }
}
// ListItem.xaml.cs
private async void AppBarButtonClick(object sender, RoutedEventArgs e)
{
      ...
            else if (e.OriginalSource == AppBarDiscountButton)
            {
                discountFlyout.Show(this, this.BottomAppBar, (Button)e.OriginalSource);
            }
}
Ещё одно всплываюшее окно

Панель навигации

Если ваше приложение содержит отдельные станицы с разной функциональностью, то необходимо предоставить панель навигации (NavBar), так чтобы пользователь мог легко перемещаться между этими страницами. Самый простой способ обеспечить последовательную навигацию в Метро-приложении - добавить элемент Frame внутри оболочки главной страницы, который будет подключать необходимые окна при навигации по приложению.

Я создал новое окно MainPage.xaml в подпапке Pages нашего приложения. Это окно выступает в роли обертки для других окон. Вы можете увидеть содержимое этого файла, который я создал с помощью шаблона пустой страницы:

<Page
    x:Class="AutoShop.Pages.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:AutoShop.Pages"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.TopAppBar>
        <AppBar>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                <Button x:Name="ListViewButton" Style="{StaticResource AppBarButtonStyle}"
                              AutomationProperties.Name="List View"
                              Content="&#xE14C;" Click="NavBarButtonPress"/>
                <Button x:Name="DetailViewButton" Style="{StaticResource AppBarButtonStyle}"
                              AutomationProperties.Name="No Item Selected"
                              Content="&#xE1A3;" Click="NavBarButtonPress"/>
            </StackPanel>
        </AppBar>
    </Page.TopAppBar>
    <Grid>
        <Frame x:Name="MainFrame" />
    </Grid>
</Page>

Как видите, чтобы добавить панель навигации я использовал элемент управления AppBar в свойстве Page.TopAppBar (панель навигации будет появляться в верхней части экрана). Она состоит из двух кнопок, которые будут переключаться между страницами ListItem.xaml и NoItemSelected.xaml. Это не представляет никакой полезной функциональности для нашего приложения, я просто создал эту панель для демонстрации возможности навигации в Metro-приложениях.

В коде этой страницы я создаю новый экземпляр ViewModel и загружаю в фрейм страницу по умолчанию - ListItem:

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using AutoShop.Data;

namespace AutoShop.Pages
{
    public sealed partial class MainPage : Page
    {
        private ViewModel viewModel;

        public MainPage()
        {
            this.InitializeComponent();
            viewModel = new ViewModel();

            this.DataContext = viewModel;
            MainFrame.Navigate(typeof(ListItem), viewModel);
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

        private void NavBarButtonPress(object sender, RoutedEventArgs e) 
        {
            Boolean isListView = (Button)sender == ListViewButton;
            MainFrame.Navigate(isListView ? typeof(ListView) : typeof(NoItemSelected), viewModel);
        }
    }
}

Все что осталось, это указать страницу MainPage в качестве стартовой в App.xaml.cs:

...
if (rootFrame.Content == null)
{
      if (!rootFrame.Navigate(typeof(Pages.MainPage), args.Arguments))
      {
            throw new Exception("Failed to create initial page");
      }
}
...

Теперь в приложении будет доступна навигация:

Навигация Metro

На этом я заканчиваю рассмотрение приложения AutoShop. Оно еще далеко от полноценного Metro-приложения, в нем нет, например, проверки достоверности введенных данных, нет сохранения данных путем их сериализации в XML-файле и многого другого. Я описал создание этого приложения в качестве введения в Metro, но ни как в качестве полного описания его возможностей.

Пройди тесты
Лучший чат для C# программистов