Служба навигации

70

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

Программная навигация

Можно сделать так, чтобы значения свойств Hyperlink.NavigateUri и Frame.Source устанавливались динамически. Однако самый гибкий и мощный подход предполагает использование службы навигации WPF. Получать доступ к этой службе навигации можно через контейнер, в котором размещена страница (например, объект Frame или NavigationWindow), но такой подход ограничивает страницы использованием в контейнере только этого типа. Поэтому лучше всего получать доступ к службе навигации через статический метод NavigationService.GetNavigationService(). Этому методу передается ссылка на страницу, а он возвращает действующий объект NavigationService, который позволяет реализовать навигацию программно:

NavigationService nav;
nav = NavigationService.GetNavigationService(this);

Этот код работает независимо от того, какой контейнер выбран для обслуживания страниц.

Объект NavigationService нельзя использовать ни в конструкторе страницы, ни на этапе срабатывания события Page.Initialized. Больше всего для этого подходит событие Page.Loaded.

В классе NavigationService определен набор методов для работы с навигацией. Наиболее часто используется метод Navigate(), который позволяет переходить на страницу по ее URI:

nav.Navigate(new System.Uri("Page1.xaml", UriKind.RelativeOrAbsolute));

или за счет создания соответствующего объекта страницы:

Page1 nextPage = new Page1();
nav.Navigate(nextPage);

По возможности лучше всегда применять URI, поскольку это позволяет системе журнализации WPF сохранять данные страницы, не удерживая в памяти все дерево ее объектов. Когда методу Navigate() передается объект страницы, в памяти сохраняется весь объект.

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

Решив использовать программную навигацию, вы сами определяете, что применять: навигационные кнопки, гиперссылки или что-нибудь еще. Как правило, для принятия решения о том, на какую страницу следует переходить, в обработчике событий используется условный код.

Навигация в WPF осуществляется асинхронным образом. Это значит, что запрос на навигацию можно отменять до того, как он будет выполнен, посредством вызова метода NavigationService.StopLoading(). Вдобавок можно использовать метод Refresh() для повторной загрузки страницы.

И, наконец, класс NavigationService также предоставляет методы GoBack() и GoForward(), которые позволяют перемещаться по спискам предыдущих и следующих страниц. Это удобно в ситуации, когда разработчик создает собственные навигационные элементы управления. Оба эти метода генерируют исключение InvalidOperationException при попытке перехода на страницу, которой не существует (например, при попытке вернуться назад, находясь на первой странице). Во избежание таких ошибок перед вызовом соответствующих методов следует проверять значения булевских свойств CanGoBack и CanGoForward.

События навигации

Класс NavigationService также предоставляет полезный набор событий, которые можно использовать для реагирования на навигацию. Наиболее распространенной причиной реагирования на навигацию является необходимость выполнения по ее завершении какой-то задачи. Например, если страница размещается внутри рамки в обычном окне, может понадобиться, чтобы по завершении навигации в окне обновлялся текст строки состояния.

Поскольку навигация осуществляется асинхронным образом, возврат из метода Navigate() происходит до появления целевой страницы. В некоторых случаях разница во времени может быть значительной, например, как в случае перехода на несвязанную XAML-страницу, расположенную на веб-сайте (или на XAML-страницу, находящуюся в другой сборке, которая инициирует веб-загрузку), или в случае, когда страница включает длительно выполняющийся код в обработчике событий Initialized или Loaded.

Процесс навигации в WPF описан ниже:

Ниже перечислены события, генерируемые классом NavigationService в течение этого процесса. Эти события навигации также предоставляются классом Application и навигационными контейнерами (такими как NavigationWindow и Frame). При наличии более одного навигационного контейнера это дает возможность обрабатывать процесс навигации в разных контейнерах по отдельности. Однако встроенного способа для обработки навигационных событий единственной страницы не существует:

Navigating

Процесс навигации начинается. Это событие можно отменить и тем самым предотвратить выполнение навигации

Navigated

Процесс навигации начался, но целевая страница еще не извлечена

LoadCompleted

Страница прошла синтаксический анализ. Однако события Initialized и Loaded еще не были сгенерированы

FragmentNavigation

Страница готовится к прокручиванию до целевого элемента. Это событие срабатывает только в случае, если используется URI с информацией о фрагменте

NavigationStopped

Процесс навигации был отменен с помощью метода StopLoading()

NavigationFailed

Процесс навигации не удался из-за того, что не получилось обнаружить или загрузить целевую страницу. Это событие можно использовать для нейтрализации исключения до того, как оно появится и превратится в необработанное исключение приложения. Для этого необходимо просто установить NavigationFailedEventArgs.Handled в true

NavigationProgress

Процесс навигации идет полным ходом, и часть данных страницы уже загружена. Это событие вызывается периодически для предоставления информации о ходе навигации. Оно предоставляет информацию об объеме данных, которые уже загружены (NavigationProgressEventArgs.BytesRead), и общем объеме данных, которые требуется загрузить (NavigationProgressEventArgs.MaxBytes). Это событие запускается после каждого извлечения данных объемом 1 Кбайт

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

События навигации нельзя подавлять с помощью свойства RoutedEventArgs.Handled. Причина в том, что они являются не маршрутизируемыми, а обычными событиями .NET.

Навигационным событиям можно передавать данные из метода Navigate(). Нужно просто использовать ту из перегруженных версий метода Navigate(), которая в качестве параметра принимает дополнительный объект. Этот объект сделан доступным в событиях Navigated, NavigationStopped и LoadCompleted через свойство NavigationEventArgs.ExtraData. Данное свойство можно использовать, например, для отслеживания времени поступления запроса на навигацию.

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