WPF - использование навигации по страницам, добавление контролов в NavigationWindow
- modified:
- reading: 4 minutes
В WPF существует возможность создавать приложения с возможность навигации, то есть с использованием Navigation объектов, таких как Page, NavigationService и NavigationWindow. Большинство примеров на MSDN отображает, как использовать такие объекты в XBAP приложениях, но никто не запрещает нам и создавать клиентские приложения с использованием навигации по страницам.
Итак, постараемся создать такое приложение. Создаем новый WPF Application проект, оттуда удаляем созданное Window1.xaml, добавляем две страницы FirstPage.xaml и SecondPage.xaml, а так же прописываем в App.xaml файл страницу, с которой наше приложение будет стартовать:
StartupUri="FirstPage.xaml"
После запуска мы должны увидеть окно нашего приложения, содержащее навигационную панель с кнопками перехода вперед и назад. Дальше, нам необходимо создать ссылки, которые помогали бы переходить с одной страницы на другую. Это можно сделать разными способами.
Добавим в проект UserControl и назовем его ApplicationToolbar. Первая возможность, при помощи которой мы сможем осуществлять переход между страницами это использование NavigationService, создадим для этого метод обработки нажатий на кнопки со следующим кодом:
private void NavigationCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
NavigationService service = NavigationService.GetNavigationService(this);
service.Navigate(new Uri(e.Parameter.ToString(), UriKind.Relative));
}
Метод осуществляет переход на страницу, которая указана в параметрах команды. В xaml файле контрола ApplicationToolbar создадим команду, привяжем к ней данный метод и укажем кнопкам данную команду на выполнение:
<UserControl x:Class="WPF_NavigationWindow.ApplicationToolbar" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="Auto" Width="Auto">
<UserControl.Resources>
<!-- Команда для обработки нажатий клавиш -->
<RoutedCommand x:Key="NavigationCommand" />
</UserControl.Resources>
<UserControl.CommandBindings>
<!-- Биндинг методов к команде -->
<CommandBinding Command="{StaticResource NavigationCommand}" Executed="NavigationCommand_Executed" />
</UserControl.CommandBindings>
<ToolBar HorizontalAlignment="Right">
<!-- Навигация по страницам осуществляется при помощи обычных кнопок и команд -->
<Button CommandParameter="FirstPage.xaml" Command="{StaticResource NavigationCommand}">First Page</Button>
<Button CommandParameter="SecondPage.xaml" Command="{StaticResource NavigationCommand}">Second Page</Button>
</ToolBar>
</UserControl>
Таким образом мы создали панель с кнопками перехода между страницами. Можно пользоваться и более простым решением - Hyperlink, давайте добавим и его на наш контрол:
<ToolBarPanel>
<TextBlock>
<Hyperlink NavigateUri="FirstPage.xaml">First Page</Hyperlink>
<Hyperlink NavigateUri="SecondPage.xaml">Second Page</Hyperlink>
</TextBlock>
</ToolBarPanel>
Итак, в результате мы должны получить, приблизительно, следующий вид окна:
Тут возникает следующее желание, положить наш контрол ApplicationToolbar на NavigationWindow (там, где располагаются навигационные кнопки). Сделать нам это поможет возможность переопределение стилей в WPF. А именно переопределение стиля для NavigationWindow.
Для того, чтобы описать новый стиль для NavigationWindow, а точнее переписать существующий, нам изначально необходимо вытащить стиль из библиотек PresentationFramework. Вообще у PresentationFramework есть несколько наборов тем, таких как Classic, Aero, Royale и Luna, каждая тема лежит в отдельной библиотеке. Я буду рассматривать библиотеку PresentationFramework.Aero с ее темой. Для того, чтобы посмотреть BAML файл (после компиляции файл xaml упаковывается в baml - Binary Application Markup) можно воспользоваться программой Reflector с плагином BamlViewer, но как показала практика - BamlViewer не очень хорошо справляется с декомпиляцией: он не верно прописывает имена ключей (Key), не верно расшифровывает данные Geometry, даже располагает описание триггеров перед описание контента стиля - из-за чего элементы в триггерах не находятся. В общем, лучше с задачей декомпиляции BAML справляется StyleExplorer. Вот небольшой скриншот сравнения данных программ:
Самое интересное, что значение ключей стилей действительно такими и являются как видно на скриншоте - x:Key="Ì". То есть в своих стилях мы можем ссылаться на стиль с именем ключа Ì (если стиль подключили). Почему сделаны такие странные имена ключей - не понятно (и видно так же, что BAML Viewer не верно их распознает). В итоге, Style Explorer предоставляет нам реально работающий XAML текст - мы можем к примеру перенести к себе в проект весь XAML текст и он будет валиден, чего не скажешь о BAML Viewer. Но весь XAML нам не нужен. Нам интересно переписать представление NavigationWindow, для этого мы изначально создадим в нашем проекте новый файл ресурсов Themes/General.xaml (имя файла и каталог можно выбрать любыми). Чтобы наш файл ресурсов был доступен в приложении - мы должны его так же подключить в файле App.xaml, это делается так:
<Application x:Class="WPF_NavigationWindow.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="FirstPage.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Путь до нашего ресурса -->
<ResourceDictionary Source="Themes/General.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
В файле General.xaml мы так же должны подключить файл ресурсов из библиотеки PresentationFramework.Aero, чтобы мы могли в стиле нашего будущего NavigationManager использовать уже существующие стили от темы Aero. Делается это таким же способом:
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/PresentationFramework.Aero;V3.0.0.0;31bf3856ad364e35;component/themes/Aero.NormalColor.xaml"/>
</ResourceDictionary.MergedDictionaries>
Далее из Style Explorer копируем стиль для NavigationWindow, а так же его ContentTemplate (который используется в стиле). ContentTemplate мы немного изменим, а точнее добавим наш контрол ApplicationToolbar в него, больше ничего делать не будем. Заметим, что ссылки на ресурсы вида ì, d, ê остаются и они будут работать. В результате у нас должен получится, приблизительно, такой набор стилей (большую часть закомментировал, там все остается то, что дал нам Style Explorer):
<ControlTemplate x:Key="d" TargetType="{x:Type NavigationWindow}">
<Border Background="{TemplateBinding Control.Background}" BorderBrush="{TemplateBinding Control.BorderBrush}" BorderThickness="{TemplateBinding Control.BorderThickness}">
<DockPanel>
<Grid Name="NavChrome" Background="{StaticResource e}" DockPanel.Dock="Top" Height="30">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="29" />
<ColumnDefinition Width="26" />
<ColumnDefinition Width="17" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- ... -->
<!-- Наш добавленный контрол -->
<WPF_NavigationWindow:ApplicationToolbar Grid.Row="0" Grid.Column="3" HorizontalAlignment="Right"/>
</Grid>
<!-- ... -->
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<!-- ... -->
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="{x:Type NavigationWindow}" TargetType="{x:Type NavigationWindow}">
<!-- ... -->
</Style>
В результате наше приложение будет выглядеть следующим образом:
Как видим, мы добились того, что наш контрол ApplicationToolbar теперь располагается на навигационной панели.