Управление журналом навигации

35

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

Линейная навигация

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

При запуске пользователем задачи, связанной с переходом на страницу, приложение может просто создать новый объект NavigationWindow, который позволяет сделать это. По завершении задачи этот объект может быть уничтожен. Однако если все приложение основано на навигации, тогда ситуация усложняется. В таком случае необходимо придумать способ для очистки списка ранее посещенных страниц при отмене или завершении задачи, чтобы пользователь не мог вернуться назад на один из промежуточных шагов.

К сожалению, в WPF не позволяет получить высокий контроль над процессом навигации. Все, что имеется — это два метода в классе NavigationService: AddBackEntry() и RemoveBackEntry(). В рассматриваемом примере необходим метод RemoveBackEntry(), который берет самый недавний элемент в списке предыдущих страниц и удаляет его.

Кроме того, RemoveBackEntry() возвращает объект JournalEntry, описывающий этот элемент. Он сообщает URI-адрес (через свойство Source) и имя, которое тот имеет в журнале навигации (через свойство Name). Не забывайте, что имя устанавливается на основе свойства Page.Title.

Если по завершении задачи должно удаляться сразу несколько записей, потребуется вызывать метод RemoveBackEntry() несколько раз. Здесь возможны два варианта. При удалении всего списка предыдущих шагов достижение его конца определяется с помощью свойства CanGoBack:

while (nav.CanGoBack)
{
   nav.RemoveBackEntry();
}

В качестве альтернативы можно удалять элементы до тех пор, пока не будет удалена начальная точка выполнения задачи. Например, если выполнение задачи начинается на странице ConfigureAppWizard.xaml, по его завершении можно использовать такой код:

string pageName;
while (pageName != "ConfigureAppWizard.xaml")
{
   JournalEntry entry = nav.RemoveBackEntry();
   pageName = System.IO.Path.GetFileName(entry.Source.ToStnng());
}

Этот код берет полный URI, который хранится в свойстве JournalEntry.Source, и усекает его до имени страницы с помощью статического метода GetFileName() класса Path (который также эффективно работает и с URI). Использование свойства Title сделало бы кодирование более удобным, но не таким надежным. Поскольку заголовок страницы отображается в хронологии навигации и является видимым для пользователя, он представляет собой фрагмент информации, который в случае локализации приложения потребуется переводить на другие языки. А это чревато нарушением кода, который ожидает жестко закодированного заголовка страницы. И даже если приложение не планируется локализовать, нетрудно представить другой сценарий с изменением заголовка страницы, например, для того, чтобы тот был более понятным или более описательным.

Кстати, все элементы в списке предыдущих и следующих страниц можно просматривать с помощью свойств BackStack и ForwardStack навигационного контейнера (вроде NavigationWindow или Frame). Однако получать эту информацию через класс NavigationService нельзя. В любом случае эти свойства предоставляют простые и доступные только для чтения объекты JournalEntry. Вносить изменения в списки они не позволяют, и поэтому реальная необходимость в них возникает крайне редко.

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