Навигация, всплывающие меню и панели
101WPF --- Периферия WPF --- Windows 8 Metro - Навигация, всплывающие меню и панели
В данной статье мы продолжим создание приложения 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=""/>
</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=""/>
</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=""/>
</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=""/>
</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=""/>
</Style>
Как видите, все стили для кнопок переопределяют стиль AppBarButtonStyle, который определяет стандартный вид кнопок панели AppBars. Здесь используются три важных свойства: AutomationProperties.AutomationId - задает идентификатор для кнопки, с помощью которого ее можно будет идентифицировать в коде, AutomationProperties.Name - определяет текст, отображаемый под кнопкой, и Content - задает изображение, которое будет использоваться.
Значением свойства Content является код символа из символов шрифта Segoe UI. Вы можете увидеть значки, заданные этим шрифтом - Стандартные стили и ресурсы Metro. На рисунке показана созданная панель:
На данный момент кнопки управления на панели 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-разметке.
Итак, мы создали очень простое всплывающее окно, отображающее список моделей, которые представлены в магазине:
Давайте теперь создадим всплывающие окна для кнопок "Добавить" и "Скидка", но перед этим я предлагаю добавить в папку 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);
}
}
Чтобы убедиться в работоспособности этого кода, прокрутим коллекцию 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="" Click="NavBarButtonPress"/>
<Button x:Name="DetailViewButton" Style="{StaticResource AppBarButtonStyle}"
AutomationProperties.Name="No Item Selected"
Content="" 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");
}
}
...
Теперь в приложении будет доступна навигация:
На этом я заканчиваю рассмотрение приложения AutoShop. Оно еще далеко от полноценного Metro-приложения, в нем нет, например, проверки достоверности введенных данных, нет сохранения данных путем их сериализации в XML-файле и многого другого. Я описал создание этого приложения в качестве введения в Metro, но ни как в качестве полного описания его возможностей.