outcoldman
outcoldman Denis Gladkikh

WPF - использование навигации по страницам, добавление контролов в NavigationWindow

.NET, C#, WPF, and XAML

В 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>

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

Untitled

Тут возникает следующее желание, положить наш контрол 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. Вот небольшой скриншот сравнения данных программ:

Untitled2

Самое интересное, что значение ключей стилей действительно такими и являются как видно на скриншоте - 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>

В результате наше приложение будет выглядеть следующим образом:

Untitled3

Как видим, мы добились того, что наш контрол ApplicationToolbar теперь располагается на навигационной панели.

Скачать пример: WPF_NavigationWindow.zip (12,64 KB)

Have feedback or questions? Looking for consultation?

My expertise: MongoDB, ElasticSearch, Splunk, and other databases. Docker, Kubernetes. Logging, Metrics. Performance, memory leaks.

Send me an email to public@denis.gladkikh.email.

The content on this site represents my own personal opinions and thoughts at the time of posting.

Content licensed under the Creative Commons CC BY 4.0.