Руководство разработчика Prism — часть 8.1, навигация на основе представлений (View-Based Navigation)

в 15:05, , рубрики: .net, expression blend, microsoft, mvvm, patterns and practices, Prism, silverlight, Visual Studio, wpf, интерфейсы

Оглавление

  1. Введение
  2. Инициализация приложений Prism
  3. Управление зависимостями между компонентами
  4. Разработка модульных приложений
  5. Реализация паттерна MVVM
  6. Продвинутые сценарии MVVM
  7. Создание пользовательского интерфейса
    1. Рекомендации по разработке пользовательского интерфейса
  8. Навигация
    1. Навигация на основе представлений (View-Based Navigation)
  9. Способы коммуникации между слабосвязанными компонентами

Навигация на основе представлений (View-Based Navigation)

Несмотря на то, что навигация на основе состояний может быть полезна в сценариях, описанных ранее, тем не менее, навигация в приложении часто требует замены одного представления на другое. В Prism, такой вид навигации называется «навигация на основе представлений (view-based navigation)».

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

  • Цель навигации — контейнер для добавляемых, или удаляемых представлений — может обрабатывать навигацию разными способами при добавлении и удалении представлений, или может визуализировать процесс навигации по-разному. Во многих случаях, целью навигации является обычный Frame, или ContentControl, и представления просто отображаются внутри этих элементов управления. Однако существует множество сценариев, когда целью навигации является другой вид элементов управления, таких как TabControl, или ListBox. В таких случаях, навигация может потребовать активации или выделения уже существующего представления, или добавление нового представления.
  • Приложению часто будет требоваться, каким-либо образом идентифицировать представление, к которому должна быть выполнена навигация. Для примера, в web-приложениях, страница, к которой выполняется навигация, напрямую идентифицируется по URI. В клиентских приложениях, представление может быть идентифицировано по имени его типа, по расположению файла ресурсов, или множеством других способов. В составных приложениях, состоящих из слабо связанных модулей, представления зачастую определены в раздельных модулях. Отдельные представления, в таких случаях, должны иметь возможность быть идентифицированы без создания дополнительных зависимостей между модулями.
  • После идентификации представления, процесс его создания и инициализации должен быть тщательно скоординирован. Это особенно важно при использовании паттерна MVVM. В таком случае, представления и соответствующая модель представления должны быть созданы и ассоциированы друг с другом во время совершения навигации. В случае использования контейнера внедрения зависимостей, такого как Unity, или MEF, при создании модели представления и/или представления может потребоваться использование особого механизма конструирования.
  • MVVM паттерн позволяет отделить UI приложения от его логики взаимодействия с пользователем и бизнес-логики. Однако процесс навигации может охватывать как UI, так и логику приложения. Пользователь может начать навигацию внутри представления, в результате чего представление будет обновлено. Но часто будет требоваться возможность инициировать и скоординировать навигацию из модели представления. Важным аспектом для рассмотрения, является способность чётко разделить навигационное поведение между представлением и моделью представления.
  • Приложению часто может потребоваться передавать параметры, или контекст, представлению для его корректной инициализации. Для примера, если пользователь производит навигацию к представлению для редактирования информации о выбранном клиенте, ID этого клиента, или его данные, должны быть переданы в представление, для отображения в нем корректной информации.
  • Многим приложениям необходимо тщательно координировать навигацию для уверенности, что будут выполнены определённые бизнес-требования. К примеру, пользователю может быть показано всплывающее сообщение о некорректности введённых им данных, во время попытки навигации к другому представлению. Этот процесс требует координации между предыдущим и новым представлениями.
  • Наконец, большинство современных приложений позволяют пользователю производить навигацию к предыдущему, или к следующему представлению. Аналогично, некоторые приложения реализуют свой рабочий процесс, используя последовательность представлений, или форм и позволяют пользователю производить по ним навигацию вперёд или назад, добавляя или редактируя данные, перед тем, как завершить задачу и отправить все сделанные изменения одним пакетом. Такие сценарии требуют некоторого механизма журналирования, для того, чтобы последовательность навигации могла быть сохранена, повторена, или предопределена.

Prism предоставляет руководство по решению этих проблем, расширяя механизм регионов для поддержки навигации. Следующие разделы содержат краткую сводку о регионах Prism и рассказывают о том, как они были расширены для поддержки навигации на основе представлений.

Обзор регионов (Region) Prism

Регионы Prism были спроектированы для поддержки разработки составных приложений (приложений, состоящих из нескольких модулей), позволяя конструировать пользовательский интерфейс слабо связанным образом. Регионы позволяют отображать представления, определённые в модулях, в UI приложения, причём модули не обязаны знать о полной структуре пользовательского интерфейса. Это позволяет легко менять разметку UI приложения, без необходимости вносить изменения в сами модули.

Регионы Prism, по большей части, являются именованными заполнителями, в которых отображаются представления. Любой элемент управления может быть объявлен как регион, с помощью простого добавления присоединенного свойства RegionName, как показано ниже.

<ContentControl prism:RegionManager.RegionName="MainRegion" ... />

Для каждого элемента управления, определённого как регион, Prism создаёт объект Region (регион), представляющий сам регион, и объект RegionAdapter (адаптер региона), задачей которого является управление расположением и активацией представлений в заданном элементе управления. Prism Library предоставляет реализацию RegionAdapter для большинства элементов управления Silverlight и WPF. Вы можете создать свой собственный RegionAdapter для поддержки дополнительных элементов управления, или для реализации особого поведения. Класс RegionManager (менеджер регионов) предоставляет доступ к объектам Region приложения.

Во многих случаях, регионом может быть простой элемент управления, такой как ContentControl, который может отображать только одно представление в один момент времени. В других случаях, это может быть элемент управления, позволяющий отображать сразу несколько представлений, такой как TabControl, или ListBox.

Адаптер региона управляет списком представлений, ассоциированных с этим регионом. Одно, или несколько из этих представлений, могут быть отображены в элементе управления региона, в соответствии с его стратегией отображения содержимого. Представлениям могут быть назначены имена, которые могут быть использованы для их поиска в дальнейшем. Адаптер региона также управляет тем, какое представление является активным в данном регионе. Активным, является то представление, которое выделено в данный момент, или является самым верхним. К примеру, в TabControl, активным является то представление, которое отображено на выделенной вкладке, в ContentControl — то, которое отображается на экране в данный момент.

Заметка.
Определение того, какое представление является активным, особенно важно в процессе навигации. Часто может понадобиться, чтобы активное представление принимало участие в навигации, к примеру, чтобы оно сохраняло данные перед тем, как пользователь уйдёт с него, или, чтобы оно запросило отмену, или подтверждение операции.

Предыдущие версии Prism позволяли отображать представления в регионах двумя различными способами. Первый способ, называемый внедрение представления (view injection), позволял программно отображать представления в регионе. Данный подход полезен при отображении динамического содержания, когда представление, которое необходимо отобразить в регионе, часто изменяется, отображая логику приложения.

Внедрение представлений поддерживается через предоставление метода Add в классе Region. Следующий код показывает, как вы можете получить ссылку на объект Region через класс RegionManager, и программно добавить в него представление. В примере, представление создаётся с помощью DI контейнера.

IRegionManager regionManager = ...;
IRegion mainRegion = regionManager.Regions["MainRegion"];
InboxView view = this.container.Resolve<InboxView>();
mainRegion.Add(view);

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

Обнаружение представления поддерживается через метод RegisterViewWithRegion в классе RegionManager. Этот метод позволяет задать метод обратного вызова, который будет вызван при отображении региона с заданным именем. Следующий пример показывает, как можно создать представление (использую контейнер внедрения зависимостей) при отображении региона с именем «MainRegion».

IRegionManager regionManager = ...;
regionManager.RegisterViewWithRegion("MainRegion", () => container.Resolve<InboxView>());

Для более подробного обзора регионов Prism, смотрите главу "Создание пользовательского интерфейса". Далее в статье будут рассмотрено, как регионы были расширены для поддержки навигации, и как преодолеть описанные ранее проблемы.

Базовая навигация в регионах

Оба описанных выше способа отображения представлений в регионах, могут быть расценены, как некоторая ограниченная форма навигации — внедрение представления является формой явной, программной навигации, а обнаружение представления — неявной, или отложенной навигацией. Однако в Prism 4.0 регионы были расширены для поддержки более общего понятий навигации, основанного на URI и расширяемом механизме навигации.

Навигация в пределах региона означает отображение нового представления в нём. Отображаемое представление идентифицируется с помощью URI, который, по умолчанию, ссылается на имя представления, к которому осуществляется навигация. Вы можете инициировать навигацию программно, используя метод RequestNavigate, определённый в интерфейсе INavigateAsync.

Заметка.
Несмотря на название, интерфейс INavigateAsync не подразумевает асинхронную навигацию, выполняемую в отдельном потоке. Наоборот, INavigateAsync подразумевает проведение псевдо-асинхронной навигации. Метод RequestNavigate может завершиться синхронно, после окончания навигации, или он может завершиться до окончания навигации, например, когда пользователю нужно подтвердить навигацию. Позволяя вам задавать метод обратного вызова во время навигации, Prism даёт возможность поддержки таких сценариев без сложностей обращения с фоновыми потоками.

Интерфейс INavigateAsync реализует класс Region, позволяя инициировать навигацию в этом регионе.

IRegion mainRegion = ...;
mainRegion.RequestNavigate(new Uri("InboxView", UriKind.Relative));

Вы также можете вызвать метод RequestNavigate на объекте RegionManager, задав имя региона, на котором производится навигация. Этот метод находит ссылку на соответствующий регион и вызывает на нём метод RequestNavigate. Это показано на примере ниже.

IRegionManager regionManager = ...;
regionManager.RequestNavigate("MainRegion", new Uri("InboxView", UriKind.Relative));

По умолчанию, навигационный URI задаёт имя представления, по которому оно зарегистрировано в контейнере. Код ниже иллюстрирует взаимоотношения между именем регистрации представления в контейнере Unity и использования этого имени во время навигации.

container.RegisterType<object, InboxView>("InboxView");
regionManager.Regions[Constants.MainRegion].RequestNavigate(new Uri("InboxView", UriKind.Relative));

Заметка.
При создании представления навигационным сервисом, он запрашивает объект тип Object из контейнера, с именем, предоставленном в навигационном URI. Различные контейнеры используют различные механизмы регистрации для поддержки этого. К примеру, в Unity, вам нужно зарегистрировать представления, сопоставив тип Object с этим представлением, и предоставив имя регистрации, соответствующее имени в навигационном URI. В MEF нужно всего лишь задать имя контракту в атрибуте ExportAttribute.

Пример. При использовании Unity для регистрации представления:

Не используйте:

container.RegisterType<InboxView>("InboxView");

Используйте вместо этого:

container.RegisterType<object,InboxView>("InboxView");

Заметка.
Имя, используемое для регистрации и навигации, не обязательно должно быть связано с именем типа представления, подойдёт любая строка. Для примера, вместо строки, вы можете явно использовать полное имя типа:

typeof(InboxView).FullName

В MEF, вы просто можете экспортировать представление с требуемым именем.

[Export("InboxView")]
public partial class InboxView : UserControl { ... }

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

Заметка.
Предшествующее описание иллюстрирует view-first навигацию, когда URI ссылается на имя представления, с которым оно было экспортировано или зарегистрировано в контейнере. С view-first навигацией, зависимая модель представления создаётся как зависимость представления. Альтернативным подходом является использование view model–first навигацию, когда навигационный URI ссылается на имя модели представления, с которым она была зарегистрирована в контейнере. Такой подход может быть полезен, когда представления определены, как шаблоны данных, или когда вы хотите, чтобы схема навигации была определена независимо от представлений.

Метод RequestNavigate также позволяет задать метод обратного вызова, который будет вызван при завершении навигации.

private void SelectedEmployeeChanged(object sender, EventArgs e) {
    ...
    regionManager.RequestNavigate(RegionNames.TabRegion, "EmployeeDetails", NavigationCompleted);
}

private void NavigationCompleted(NavigationResult result) {
    ...
}

Класс NavigationResult имеет свойства, через которые можно получить информацию о навигационной операции. Свойство Result показывает, завершилась ли навигация. Если навигация завершилась с ошибкой, то свойство Error будет содержать ссылку на любые исключения, вброшенные во время навигации. Через свойство Context можно получить доступ к навигационному URI и любым параметрам, которые он содержит, а также к ссылке на сервис, координирующий навигационные операции.

Участие представления и модели представления в навигации

Часто бывает, что и представление, и модель представления в вашем приложении захотят принять участие в навигации. Сделать это позволяет интерфейс INavigationAware. Вы можете реализовать этот интерфейс в представлении, или (более часто) в модели представления. Реализовав его, ваше представление, или модель представления могут принять непосредственное участие в процессе навигации.

Заметка.
В последующем описании делается предположение, что навигация происходит между представлениями. Но нужно отметить, что интерфейс INavigationAware во время навигации будет вызываться вне зависимости от того, реализован ли он в представлении, или в модели представления. Во время навигации, Prism проверяет, реализует ли представление INavigationAware, если да, то на нём вызываются необходимые методы. Также, Prism делает проверку реализации этого интерфейса на объекте, в который установлено свойство DataContext представления, и, в случае успеха, вызывает необходимые методы.

Этот интерфейс позволяет представлению, или модели представления принять участие в операции навигации. В интерфейсе INavigationAware определены три метода:

public interface INavigationAware {
    bool IsNavigationTarget(NavigationContext navigationContext);
    void OnNavigatedTo(NavigationContext navigationContext);
    void OnNavigatedFrom(NavigationContext navigationContext);
}

Метод IsNavigationTarget позволяет существующему в регионе представлению, или модели представления, показать, может ли оно обработать запрос навигации. Это может быть полезным в случаях, когда вы хотите повторно использовать уже существующее представление для обработки навигации, или хотите совершить навигацию к уже существующему представлению. Для примера, представление, отображающее информацию о клиенте, может обновляться при выборе различных клиентов. Для получения более подробной информации, смотрите раздел «Навигация к существующим представлениям» далее в статье.

Методы OnNavigatedFrom и OnNavigatedTo вызываются во время операции навигации. Если текущее активное представление в регионе (или его модель представления) реализует этот интерфейс, метод OnNavigatedFrom вызывается до начала навигации. Метод OnNavigatedFrom позволяет предыдущему представлению сохранить своё состояние, или подготовиться к деактивации или удалению из пользовательского интерфейса. Для примера, представление может сохранить сделанные пользователем изменения в базу данные, или отослать их web-сервису.

Если только что созданное представление (или модель представления) реализует этот интерфейс, его метод OnNavigatedTo вызывается после совершения навигации. Метод OnNavigatedTo позволяет только что отображённому представлению провести инициализацию, возможно, используя параметры, переданные вместе с навигационным URI. Для получения дополнительной информации, смотрите следующий раздел «Передача параметров во время навигации».

После создания, инициализации и добавления в целевой регион нового представления, оно становится активным, а предыдущее представление деактивируется. Иногда, может понадобиться удалить деактивированное представление из региона. В Prism существует интерфейс IRegionMemberLifetime, позволяющий контролировать время жизни представлений в регионах, задавая, следует ли сразу же удалять деактивированные представления из региона, или просто помечать их как деактивированные.

public class EmployeeDetailsViewModel : IRegionMemberLifetime {
    public bool KeepAlive { get { return true; } }
}

Интерфейс IRegionMemberLifetime определяет единственное свойство KeepAlive, доступное только для чтения. Если оно установлено в false, представление будет удалено из региона при его деактивации. Так как регион после этого перестаёт хранить ссылку на представление, оно будет достижимо для сборщика мусора (если, конечно, на него не ссылаются другие части вашего приложения). Вы можете реализовать этот интерфейс, как в представлении, так и в модели представления. Хотя интерфейс IRegionMemberLifetime предназначен, по большей части, для управления временем жизни представлений в регионах во время активации и деактивации, свойство KeepAlive также используется во время навигации после создания и активации представления в целевом регионе.

Заметка.
Регионы, способные отображать несколько представлений, такие как ItemsControl, или TabControl, будут показывать как активные, так и неактивные представления. Удаление неактивного представления из региона, для этих элементов управления, приведёт к удалению его из пользовательского интерфейса.

Передача параметров во время навигации

Для реализации необходимого поведения при навигации, вам зачастую может понадобиться задавать дополнительные данные, передаваемые во время запроса навигации, кроме имени целевого представления. Объект NavigationContext предоставляет доступ к навигационному URI и ко всем параметрам, которые были переданы вместе с ним. Вы можете получить доступ к NavigationContext в методах IsNavigationTarget, OnNavigatedFrom, и OnNavigatedTo.

Для помощи в задании и получении параметров навигации, в Prism существует класс UriQuery. Вы можете использовать его при необходимости добавить навигационные параметры к URI перед началом навигации и для доступа к этим параметрам во время навигации. UriQuery создаёт список с парами имя-значение для каждого параметра.

Следующий пример кода показывает, как добавить параметры к UriQuery и присоединить их к навигационному URI.

Employee employee = Employees.CurrentItem as Employee;
if (employee != null) {
    UriQuery query = new UriQuery();
    query.Add("ID", employee.Id);
    _regionManager.RequestNavigate(RegionNames.TabRegion,
        new Uri("EmployeeDetailsView" + query.ToString(), UriKind.Relative));
}

Вы можете получить навигационные параметры, используя свойство Parameters объекта NavigationContext. Это свойство возвращает экземпляр класса UriQuery, у которого есть свойство-индексатор для упрощения доступа к индивидуальным параметрам.

public void OnNavigatedTo(NavigationContext navigationContext) {
    string id = navigationContext.Parameters["ID"];
}

Навигация к существующим представлениям

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

Примером к первому сценарию может служить ситуация, когда приложению позволяет пользователю изменять информацию о клиенте, используя представление EditCustomer, и пользователь уже использует это представление для редактирования клиента с ID 123. Если пользователь решит отредактировать запись о клиенте с ID 456, он может просто совершить навигацию к представлению EditCustomer и ввести новый ID. После этого, представление EditCustomer запросит данные о новом клиенте и обновит свой UI соответствующим образом.

Для примера использования второго сценария, допустим, что приложение позволяет редактировать информацию сразу о нескольких клиентах одновременно. В этом случае, приложение отображает несколько представлений EditCustomer в TabControl, к примеру, клиентов с ID 123 и ID 456. При навигации к EditCustomer и вводе ID 456, соответствующее представление будет активировано (то есть, соответствующая вкладка будет выделена). Если пользователь совершит навигацию к представлению EditCustomer и введёт ID 789, то новый экземпляр будет создан и отображён в UI.

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

Prism поддерживает оба описанных выше сценария через метод INavigationAware.IsNavigationTarget. Этот метода вызывается во время навигации на представлениях в регионе, имеющих тот же тип, что и целевое представление. В предыдущем примере, целевое представление было типа EditCustomer, поэтому метод IsNavigationTarget будет вызван на всех существующих представлениях типа EditCustomer, находящихся в регионе. Prism определяет целевой тип представления с помощью навигационного URI, предполагая, что он является коротким именем типа целевого представления.

Заметка.
Для того, чтобы Prism определила тип целевого представления, имя представления в навигационном URI должно соответствовать короткому имени его типа. К примеру, если представление имеет класс MyApp.Views.EmployeeDetailsView, то имя представления, заданное в навигационном URI должно быть EmployeeDetailsView. Это является стандартным поведением Prism. Вы можете изменить его, создав свой класс загрузчика контента. Сделать это можно реализовав интерфейс IRegionNavigationContentLoader, или унаследовав свой класс от RegionNavigationContentLoader.

Реализация метода IsNavigationTarget может использовать параметр NavigationContext для определения, может ли представление обработать навигационный запрос. NavigationContext даёт доступ к навигационному URI и навигационным параметрам. В предыдущем примере, реализация этого метода в модели представления EditCustomer, сравнивала текущий ID клиента с ID, заданном в навигационном запросе, возвращая true при совпадении.

public bool IsNavigationTarget(NavigationContext navigationContext) {
    string id = navigationContext.Parameters["ID"];
    return _currentCustomer.Id.Equals(id);
}

Если метод IsNavigationTarget всегда возвращает true, вне зависимости от навигационных параметров, это представление всегда будет использоваться повторно. Такой подход может гарантировать, что в определённом регионе будет существовать только одно представление определённого типа.

Подтверждение, или отмена навигации

Часто может возникнуть необходимость взаимодействия с пользователем во время навигации, позволяя пользователю подтвердить, или отменить её. Во многих приложениях, пользователь, к примеру, может начать навигацию во время ввода или редактирования данных. В таких ситуациях, может потребоваться спросить у пользователя, хочет ли он сохранить, или отменить сделанные изменения, перед тем, как уйти со страницы, или не хочет ли он вообще отменить навигацию. Prism поддерживает такие сценарии через интерфейс IConfirmNavigationRequest.

Интерфейс IConfirmNavigationRequest унаследован от интерфейса INavigationAware и добавляет метод ConfirmNavigationRequest. Реализуя этот интерфейс в вашем представлении, или модели представления, вы позволяете им участвовать в процессе навигации, позволяя взаимодействовать с пользователем так, чтобы он мог отменить, или подтвердить навигацию. Вам также может понадобиться использовать объект Interaction Request, о котором было рассказано в части 6 "Продвинутые сценарии MVVM", для показа всплывающего окна с подтверждением.

Заметка.
Метод ConfirmNavigationRequest вызывается на активном представлении (или модели представления), аналогично методу OnNavigatedFrom, описанному ранее.

Метод ConfirmNavigationRequest принимает два параметра, ссылку на текущий навигационный контекст, описанный выше, и делегат, который необходимо будет вызвать для продолжения навигации. По этой причине, данный делегат часто называют делегатом-продолжением (continuation callback). Вы можете сохранить ссылку на делегат-продолжение, для его вызова после окончания взаимодействия с пользователем. Если приложение взаимодействует с пользователем через объекты Interaction Request, вы можете использовать этот делегат в качестве метода обратного вызова запроса взаимодействия. Следующая диаграмма иллюстрирует полный процесс.

Подтверждение навигации, используя объект запроса взаимодействия

Следующие шаги описывают процесс подтверждения навигации при использовании объекта InteractionRequest:

  1. Навигация инициируется через вызов метода RequestNavigate.
  2. Если представление, или модель представления реализуют интерфейс IConfirmNavigation, вызывается метода ConfirmNavigationRequest.
  3. Модель представления вызывает событие начала взаимодействия (interaction request event).
  4. Представление отображает всплывающее окно с подтверждением и ждёт ответа от пользователя.
  5. Метод обратного вызова запроса взаимодействия вызывается после того, как пользователь закрывает всплывающее окно.
  6. Делегат-продолжение вызывается для продолжения, или отмены прерванной операции навигации.
  7. Операция навигации завершается, или отменяется.

Чтобы увидеть иллюстрацию этому, посмотрите View-Switching Navigation Quick Start. Это приложение позволяет пользователю создавать сообщения электронной почты, используя классы ComposeEmailView, и ComposeEmailViewModel. Класс модели представления реализует интерфейс IConfirmNavigation. Если пользователь совершает навигацию, к примеру, нажимая кнопку Calendar, в время создания сообщения, будет вызван метод ConfirmNavigationRequest для того, чтобы модель представления могла запросить подтверждение у пользователя. Для поддержки этого, модель представления задаёт объект запроса взаимодействия, как показано в примере ниже.

public class ComposeEmailViewModel : NotificationObject, IConfirmNavigationRequest {
    private readonly InteractionRequest<Confirmation> confirmExitInteractionRequest;

    public ComposeEmailViewModel(IEmailService emailService) {
        this.confirmExitInteractionRequest = new InteractionRequest<Confirmation>();
    }

    public IInteractionRequest ConfirmExitInteractionRequest { get { return this.confirmExitInteractionRequest; } }
}

В классе ComposeEmailVew задан триггер InteractionRequestTrigger, привязанный к свойству ConfirmExitInteractionRequest модели представления. При запросе взаимодействия, пользователю показывается простое всплывающее окно.

<UserControl.Resources>
    <DataTemplate x:Name="ConfirmExitDialogTemplate">
        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding}"/>
    </DataTemplate>
</UserControl.Resources>

<Grid x:Name="LayoutRoot" Background="White">
    <ei:Interaction.Triggers>
        <prism:InteractionRequestTrigger SourceObject="{Binding ConfirmExitInteractionRequest}">
            <prism:PopupChildWindowAction ContentTemplate="{StaticResource ConfirmExitDialogTemplate}"/>
        </prism:InteractionRequestTrigger>
    </ei:Interaction.Triggers>
...

Метод ConfirmNavigationRequest класса ComposeEmailVewMode вызывается при попытке пользователя совершить навигацию во время написания сообщения. Реализация этого метода вызывает запрос взаимодействия, определённый ранее, так, чтобы пользователь мог подтвердить, или изменить операцию навигации.

void IConfirmNavigationRequest.ConfirmNavigationRequest(
          NavigationContext navigationContext, Action<bool> continuationCallback) {
    this.confirmExitInteractionRequest.Raise(
            new Confirmation {Content = "...", Title = "..."},
            c => {continuationCallback(c.Confirmed);});
}

Метод обратного вызова для запроса взаимодействия вызывается, когда пользователь нажимает кнопку на всплывающем окне подтверждения. Он просто вызывает делегат-продолжение, передавая в него значение флага Confirmed, что заставляет подтвердить, или отменить навигацию.

Заметка.
Нужно заметить, что после вызова метода Rise объекта запроса взаимодействия, метод ConfirmNavigationRequest сразу же возвращается, чтобы позволить пользователю продолжить взаимодействие с UI приложения. При нажатии кнопки OK, или Cancel на всплывающем окне, вызывается метод обратного вызова на объекте запроса взаимодействия, который, в свою очередь, вызывает делегат-продолжение для завершения навигации. Все методы вызываются в потоке UI. При использовании такой техники не требуется создания фоновых потоков.

Используя этот механизм, вы можете контролировать, будет ли запрос навигации осуществлён сразу же, или с задержкой, ожидая реакции пользователя, или другой асинхронной операции, такой как запрос к web-серверу. Для продолжения и подтверждения навигации, необходимо просто вызвать делегат-продолжение, передав в него true, для отмены навигации, необходимо передать false.

void IConfirmNavigationRequest.ConfirmNavigationRequest(
          NavigationContext navigationContext, Action<bool> continuationCallback) {
    continuationCallback(true);
}

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

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

Использование журнала навигации (Navigation Journal)

Класс NavigationContext предоставляет доступ к сервису навигации региона, который ответственен за координирование последовательности операций во время навигации в регионе. Он предоставляет доступ к региону, в котором производится навигация, и к журналу навигации, ассоциированным с этим регионом. Сервис навигации реализует интерфейс IRegionNavigationService, показанный ниже.

public interface IRegionNavigationService : INavigateAsync {
    IRegion Region { get; set; }
    IRegionNavigationJournal Journal { get; }
    event EventHandler<RegionNavigationEventArgs> Navigating;
    event EventHandler<RegionNavigationEventArgs> Navigated;
    event EventHandler<RegionNavigationFailedEventArgs> NavigationFailed;
}

Так как этот интерфейс унаследован от интерфейса INavigateAsync, вы можете инициировать навигацию в родительском регионе, вызывая метод RequestNavigate. Событие Navigating вызывается при инициации операции навигации. Событие Navigated вызывается при завершении навигации в регионе. NavigationFailed вызывается при возникновении ошибки во время навигации.

Свойство Journal даёт доступ к навигационному журналу, ассоциированному с регионом. Навигационный журнал реализует интерфейс IRegionNavigationJournal, показанный ниже.

public interface IRegionNavigationJournal {
    bool CanGoBack { get; }
    bool CanGoForward { get; }
    IRegionNavigationJournalEntry CurrentEntry { get; }
    INavigateAsync NavigationTarget { get; set; }
    void Clear();
    void GoBack();
    void GoForward();
    void RecordNavigation(IRegionNavigationJournalEntry entry);
}

Вы можете получить и сохранить ссылку на навигационный сервис региона в представлении во время навигации через вызов метода OnNavigatedTo. По умолчанию, Prism предоставляет простой стековый журнал, который позволяет совершать навигацию вперёд и назад в пределах региона.

Вы можете использовать навигационный журнал для того, чтобы позволить пользователю совершать навигацию внутри самого представления. В следующем примере, модель представления реализует команду GoBack, которая использует навигационный журнал родительского региона. Следовательно, представление может отобразить кнопку Back, которая позволяет пользователю переместиться к предыдущему представлению в регионе. Аналогично, вы можете реализовать команду GoForward для создания рабочего процесса в стиле мастера (wizard style workflow).

public class EmployeeDetailsViewModel : INavigationAware {
    ...
    private IRegionNavigationService navigationService;

    public void OnNavigatedTo(NavigationContext navigationContext) {
        navigationService = navigationContext.NavigationService;
    }

    public DelegateCommand<object> GoBackCommand { get; private set; }

    private void GoBack(object commandArg) {
        if (navigationService.Journal.CanGoBack) {
           navigationService.Journal.GoBack();
        }
    }

    private bool CanGoBack(object commandArg) {
        return navigationService.Journal.CanGoBack;
    }
}

Вы можете создать свой собственный журнал для региона для реализации специфичного рабочего процесса для этого региона.

Заметка.
Навигационный журнал может использоваться только в операциях навигации в регионе, которые координируются навигационным сервисом региона. Если вы используете технику обнаружения или внедрения представления для реализации навигации в регионе, навигационный журнал не будет обновляться во время навигации и его нельзя использовать для навигации назад, или вперёд в регионе.

Использование WPF и Silverlight Navigation Frameworks

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

Поддерживая навигацию для регионов, Prism предоставляет возможность совершения навигации внутри множества элементов управления, и даёт возможность изменять разметку пользовательского интерфейса приложения, не нарушая структуры навигации. Также поддерживается псевдо-синхронная навигация, что позволяет производить расширенное взаимодействие с пользователем во время навигации.

Однако Prism навигация не была спроектирована для замены Silverlight navigation framework (представленного в Silverlight 3.0), или WPF's navigation framework. Вместо этого, навигация в регионах Prism была спроектирована для работы вместе с Silverlight и WPF navigation frameworks.

Silverlight navigation framework предоставляет поддержку глубоких ссылок, интеграция с браузером и проекцию навигационных URI. Навигация возможно внутри элемента управления Frame. Frame может при необходимости отображать строку навигации, которая позволяет пользователю совершать навигацию назад, или вперёд между представлениями, отображаемыми в Frame. Обычным подходом является использование Silverlight navigation framework для реализации высокоуровневой навигации в оболочке приложения и использование навигации Prism для всех остальных частей приложения. В таком случае, ваше приложение будет поддерживать глубокие ссылки и будет интегрировано с журналом браузера и его адресной строкой, а также будет пользоваться всеми преимуществами навигации в регионах Prism.

WPF navigation framework не является таким расширяемым, как в Silverlight. Соответственно, поддержка внедрения зависимостей и шаблона MVVM существенно затрудняется. Он также основывается на элементе управления Frame, имеющем схожую функциональность, с точки зрения журналирования и навигационного UI. Вы можете использовать WPF navigation framework вместе с навигацией Prism, хотя, реализация навигации, используя только регионы Prism, возможно, будет более простым и гибким решением.

Последовательность навигации в регионе

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

Последовательность навигации в регионе

Дополнительная информация

Для получения дополнительной информации о Visual State Manager, смотрите «VisualStateManager Class» на MSDN: http://msdn.microsoft.com/en-us/library/cc626338(v=VS.95).aspx.

Для получения дополнительной информации об использовании поведений Microsoft Expression Blend, смотрите «Working with built-in behaviors» на MSDN: http://msdn.microsoft.com/en-us/library/ff724013(v=Expression.40).aspx.

Для получения дополнительной информации о создании поведений Microsoft Expression Blend, смотрите «Creating Custom Behaviors» на MSDN: http://msdn.microsoft.com/en-us/library/ff724708(v=Expression.40).aspx.

Для получения дополнительной информации о Silverlight Navigation Framework, смотрите «Navigation Overview» на MSDN: http://msdn.microsoft.com/en-us/library/cc838245(VS.95).aspx.

Для получения дополнительной информации об интеграции Silverlight's Navigation Framework с Prism, смотрите «Integrating Prism v4 Region Navigation with Silverlight Frame Navigation» в блоге Karl Schifflett's: http://blogs.msdn.com/b/kashiffl/archive/2010/10/05/integrating-prism-v4-region-navigation-with-silverlight-frame-navigation.aspx.

Автор: Unrul

Источник

Поделиться

* - обязательные к заполнению поля