Оглавление
- Введение
- Инициализация приложений Prism
- Управление зависимостями между компонентами
- Разработка модульных приложений
- Реализация паттерна MVVM
- Продвинутые сценарии MVVM
- Создание пользовательского интерфейса
- Рекомендации по разработке пользовательского интерфейса
- Навигация
- Способы коммуникации между слабосвязанными компонентами
Существует несколько парадигм создания пользовательского интерфейса:
- Все элементы управления содержатся в одном XAML файле и объединяются во время проектирования формы.
- Форма разделяется на логические части, обычно — пользовательские элементы управления. Форма создаётся во время проектирования, ссылаясь на эти части.
- Форма, как и в предыдущем варианте, разделяется на логические части, но не ссылается на них напрямую. Части добавляются на форму во время выполнения. Такие приложения известны, как составные.
Интерфейс составного приложения обычно состоит из разрозненных компонент, называемых представлениями. Представления обычно находятся в модулях приложения, хотя это и не обязательное требование. Если вы разбиваете приложение на модули, то вам необходим способ объединения слабо связанных представлений в пользовательский интерфейс. Однако, этот подход можно использовать даже если представления не находятся в различных модулях.
Для сборки пользовательского интерфейса необходима архитектура, позволяющая создавать разметку, состоящую из слабо связанных визуальных элементов, генерируемых во время выполнения. Кроме того, эта архитектура должна позволять представлениям обмениваться сообщениями друг с другом, оставаясь при этом слабо связанной.
Приложение Stock Trader Reference Implementation (Stock Trader RI) производит композицию пользовательского интерфейса во время выполнения, загружая представления из различных модулей в регионы, объявленные в оболочке (Shell
), как показано на иллюстрации ниже.
Принципы компоновки пользовательского интерфейса
Корневой элемент составного приложения называется оболочкой (Shell
). Оболочка работает как мастер-страница для приложения и состоит из нескольких регионов (Region
). Регионы представляют собой элементы-заполнители, в которые во время выполнения загружается содержимое. Регионы могут быть ассоциированы с различными UI элементами, такими как ContentControl
, ItemsControl
, TabControl
, или пользовательский элемент управления, и могут управлять их контентом. Регионы могут загружать контент как автоматически, так и по запросу, в зависимости от требований приложения.
Обычно, содержимым региона является представление. Оно инкапсулирует часть пользовательского интерфейса приложения и держит его отделённым от остального приложения. Представлением может быть как пользовательский элемент управления, так и шаблон данных.
Регионы управляют компоновкой и отображением представлений. К регионам можно обращаться по их имени. Также они поддерживают динамическое добавление и удаление представлений. Можно думать о регионах, как о контейнерах, в которые динамически загружаются представления.
Следующие разделы дают представление о высокоуровневых концепциях создания составных приложений.
Оболочка (Shell)
Оболочка является корневым объектом приложения и содержит основные части пользовательского интерфейса. В WPF приложении, в роли оболочки выступает объект Windows
. В приложении Silverlight — RootVisualUserControl
.
Оболочка играет роль мастер-страницы, предоставляя основную структуру разметки интерфейса. Она содержит один или несколько именованных регионов, в которые модули могут подставлять необходимые представления. Также, в ней могут содержаться определённые высокоуровневые компоненты пользовательского интерфейса, такие как задний фон, главное меню, или панель инструментов.
Оболочка определяет общий вид приложения. В ней могут быть определены стили и границы, видимые в самой оболочке, а также стили, шаблоны и темы, которые могут быть применены к загружаемым представлениям.
Обычно оболочка является частью первичного WPF или Silverlight проекта. Сборка, содержащая оболочку, может ссылаться на сборки с загружаемыми представлениями, а может и не ссылаться, если производится поиск и загрузка сборок во время выполнения.
Представления (Views)
Представления являются основными элементами при построении пользовательского интерфейса приложения. Вы можете использовать в качестве представления пользовательский элемент управления, страницу (Page
), или шаблон данных. Представление инкапсулирует те части пользовательского интерфейса, которые вы хотите держать в отдалении от всего остального приложения. Выбрать то, что будет содержаться в представлении можно как на основе разделение функциональности, так и на основе повторного использования частей интерфейса в пределах приложения.
Благодаря модели содержимого, используемой в WPF и Silverlight, для создания представления не требуется ничего относящегося к Prism,. Наиболее простой способ создания представления заключается в создании пользовательского элемента управления (User Control
). Для добавления представления в UI требуется только способ создать это представление и добавить его в контейнер. WPF и Silverlight предоставляют механизмы для осуществления этого. Prism добавляет возможность задания регионов, в которые представление может быть динамически добавлено во время выполнения.
Составные представления
Представление, при добавлении в него некоторой функциональности, может стать достаточно сложным. В этом случае, вы можете разбить его на несколько дочерних представлений и использовать родительское представление для создания и размещения дочерних представлений. Это может происходить как статически, во время проектирования, так и динамически, позволяя модулям подставлять представления в регионы во время выполнения. Если представление не является полностью определённым в одном классе, то на него можно ссылаться, как на составное представление. Во многих случаях, родительское представление ответственно как за создание дочерних представлений, так и за координирование их взаимодействия. Вы можете спроектировать дочерние представления, которые будут менее связаны друг с другом, используя такие возможности Prism, как агрегатор событий и объекты команд.
Представления и шаблоны проектирования
Хотя Prism и не настаивает на этом, вы должны рассмотреть возможность использования некоторых шаблонов проектирования при создании представлений. Stock Trader RI и QuickStarts демонстрируют применение MVVM паттерна для разделения ответственности между интерфейсом и логикой.
Шаблон проектирования MVVM рекомендован по большей части из-за того, что он естественно сочетается с Microsoft XAML платформами, такими как WPF, Silverlight, Windows Phone, и Windows RT. Механизм зависимых свойств и богатая поддержка привязки данных, дают возможность модели представления и представлению взаимодействовать в слабо связанной манере.
Отделение логики представления от самого представления, является важным шагом, который облегчит тестирование и удобство сопровождения, а также улучшит процесс взаимодействия дизайнеров с программистами.
Если вы создаёте представление, как пользовательский элемент управления и помещаете логику в файл отделённого кода, вам будет сложно его протестировать, так как для этого необходимо будет создать экземпляр представления и через него тестировать логику. Это представляет некоторую сложность по той причине, что необходимо будет создать заглушки для огромного объёма инфраструктуры. Соответственно, для удаления зависимостей от инфраструктуры, необходимо отделить логику представления от самого представления.
Если вы создаёте представление, как шаблон данных. То код, ассоциированный с этим представлением, отсутствует. По этой причине, вы должны поместить сопутствующую логику где-то ещё. Тот же самый подход, который обеспечивал тестируемость, теперь сможет помочь с развёртыванием представления.
Заметка.
Модульное тестирование и тестирование автоматизации UI являются двумя абсолютно разными подходами к тестированию с совершенно разным покрытием. Лучшие практики модульного тестирования рекомендуют, чтобы объект тестировался в полной изоляции. Для достижения этого, необходимо создавать подделки и заглушки для всех зависимостей. После чего, модульный тест работает только с одним объектом. Тесты UI автоматизации запускают приложение, после чего применяют жесты к UI и проверяют результат. Тесты такого типа необходимы для проверки правильности присоединения элементов управления к логике приложения.
Отделение логики от представления способствует разделению ответственности. В качестве дополнения к повышению способности к тестированию, это даёт возможность дизайнеру UI работать независимо от разработчика. Для получения дополнительной информации о паттерне MVVM смотрите часть 5: «Реализация паттерна MVVM».
Команды (Commands), UI триггеры (UI Triggers), действия (Actions) и поведения (Behaviors)
Если логика представления расположена в отделённом коде, то для взаимодействия с UI используются обработчики событий. Однако, при использовании MVVM, модель представления не может непосредственно обрабатывать события, генерируемые в UI. Для перенаправления событий из UI в модель представления, можно использовать команды, UI триггеры, действия, или поведения.
Команды отделяют семантику и объект, на котором вызывают это команду, от логики самой команды. Также, объект команды даёт возможность определить, может ли эта команда быть выполнена в данный момент. Команды в UI привязаны к свойствам типа ICommand модели представления. Для получения дополнительной информации, смотрите раздел «Команды» в части 5.
UI триггеры, действия, и поведения являются частью пространства имён Microsoft.Expression.Interactivity
и поставляются вместе с Expression Blend. Они также являются частью Expression SDK. Эти компоненты предоставляют обширный API для перехвата UI событий или команд и последующей их маршрутизации в ICommand
свойства объекта текущего контекста данных. Для получения дополнительной информации про них, смотрите соответствующие разделы в части 5.
Привязка данных (Data Binding)
Привязка данных является самой важной особенностью XAML платформ. Для успешного создания приложений, использующих XAML, вы должны хорошо разбираться в привязке данных.
Привязка данных использует встроенную систему уведомлений об изменениях, предоставляемую системой зависимых свойств. При комбинировании с CLR интерфейсом INotifyPropertyChanged
, эти уведомления позволяют организовывать взаимодействие между приёмником и источником привязки данных, без использования какого-либо дополнительного кода.
Привязка данных позволяет связывать несовместимые по типам данных источники и приёмник с помощью конвертеров величин. Также она предоставляет несколько механизмов осуществления валидации данных.
Регионы (Regions)
Регионы доступны в Prism через менеджера регионов, регионы и адаптеры регионов. Следующий раздел описывает, как они работают и взаимодействуют.
Менеджер регионов (Region Managers)
Класс RegionManager
ответственен за создание и развёртывание коллекции регионов в элементы управления. RegionManager
использует адаптеры, специфичных для каждого типа элементов управления, для того, чтобы ассоциировать новый регион с соответствующим элементом управления. Следующая иллюстрация показывает отношения между регионом, элементом управления и адаптером.
RegionManager может создавать регионы в коде, или в XAML. Присоединённое свойство RegionManager.RegionName используется для создания региона в XAML с помощью его применения к элементу управления, который будет выступать в роли хоста для этого региона.
Приложение может содержать один, или несколько экземпляров RegionManager
. Есть возможность выбирать, в каком экземпляре менеджера регионов необходимо зарегистрировать регион. Это может оказаться полезным, если вы хотите передвинуть элемент управления в визуальном дереве, но не хотите, чтобы регион очистился после очищение присоединённого свойства.
RegionManager предоставляет присоединённое свойство RegionContext
, с помощью которого можно обмениваться данными между регионами.
Создание региона
Регионом является класс, который реализует интерфейс IRegion
. Термин «регион» обозначает собой контейнер, который может удерживать динамическое содержимое пользовательского интерфейса. Регионы позволяют Prism размещать динамический контент, содержащийся в модулях в предопределённые заполнители в пользовательском интерфейсе.
Регионы могут удерживать любой тип UI контента. Модуль может содержать UI контент, представленный в виде пользовательских элементов управления, данные с ассоциированными шаблонами данных, специализированные элементы управления, или их комбинацию. Это позволяет вам задавать расположение областей в UI, после чего позволить модулям загрузить контент в эти области.
Регион может содержать ноль или больше элементов. В зависимости от того, какой элемент управление используется в качестве хоста для данного региона, может быть видно один или несколько элементов. Для примера, ContentControl может отображать только единственный объект, тогда как ItemsControl
может отобразить сразу несколько элементов.
На следующей иллюстрации, оболочка Stock Trader RI состоит из четырёх регионов: MainRegion
, MainToolbarRegion
, ResearchRegion
, и ActionRegion
. Эти регионы наполняются различными модулями приложения — контент может измениться в любое время.
Отображение пользовательского элемента управления из модуля в регионе
Для демонстрации того, как модули и контент связаны с регионами, посмотрите следующую иллюстрацию. Она показывает ассоциацию WatchModule
и NewsModule
с соответствующим регионом в оболочке.
Регион MainRegion
содержит элемент управления WatchListView
, который содержится в модуле WatchModule
. Регион ResearchRegion
также содержит пользовательский элемент управления ArticleView
, который находится в модуле NewsModule
.
В приложении, созданном с помощью Prism, отображения вроде этого будут естественной частью процесса разработки, так как дизайнер и разработчик используют их для определения того, какой контент будет содержаться в определённом регионе. Это позволяет дизайнеру определить, какое понадобится пространство, и что необходимо добавить, чтобы содержимое было видно в предоставленном пространстве.
Стандартная функциональность региона
Хотя для использования регионов не нужно знать подробностей их реализации, будет полезно понимать, как элементы управления и регионы ассоциируются друг с другом. А также об их стандартной функциональности: для примера, как регионы находят и создают представления, как представления могут быть уведомлены, когда они становятся активными, или как жизненный цикл представления может быть связан с их активацией.
Следующие разделы описывают адаптеры регионов и поведения регионов.
Адаптеры регионов (Region Adapters)
Для подстановки элемента управления в регион, он должен иметь необходимый адаптер. Адаптеры регионов ответственны за создание региона и ассоциации его с элементом управления. Это позволяет использовать интерфейс IRegion
для управления контентом элемента управления согласованным образом. Каждый адаптер регионов адаптирует определённый тип элементов управления. Prism предоставляет следующие адаптеры:
- ContentControlRegionAdapter. Он адаптирует
System.Windows.Controls.ContentControl
и его наследников. - SelectorRegionAdapter адаптирует
System.Windows.Controls.Primitives.Selector
и его наследников, таких какSystem.Windows.Controls.TabControl
. - ItemsControlRegionAdapter адаптирует
System.Windows.Controls.ItemsControl
и его наследников.
Заметка.
Silverlight версия библиотеки Prism содержит ещё одни адаптер под именемTabControlRegionAdapter
. Это вызвано тем, чтоTabControl
в Silverlight не является потомком классаSelector
и имеет поведение, отличное от аналога в WPF.
Поведения регионов (Region Behaviors)
Prism вводит концепт поведений регионов. Это подключаемые компоненты, которые ответственны за большую часть функциональности регионов. Поведения были представлены для поддержки обнаружения представлений и контекст региона, а также для создания однородного API как для WPF, так и для Silverlight. Дополнительно, благодаря поведениям, появляется эффективный способ расширения функциональности регионов.
Поведение — это класс, который присоединён к региону и даёт ему дополнительную функциональность. Поведение присоединяется к региону и остаётся активным на всё время его существования. Для примера, AutoPopulateRegionBehavior
присоединяясь к региону, автоматически создаёт и добавляет типы представлений, зарегистрированные для региона с данным именем. На протяжении жизни региона, оно продолжает мониторить RegionViewRegistry
на наличие новых регистраций. Легко добавить новое поведение, или заменить существующее, как для всего приложения, так и для отдельного региона.
Следующие несколько разделов расскажут о стандартных поведениях, добавляемых к регионам автоматически. Исключением является поведение SelectorItemsSourceSyncBehavior
, которое присоединяется только к элементам управления, являющимися потомками класса Selector
.
Registration Behavior
RegionManagerRegistrationBehavior
ответственен за то, что регион будет зарегистрирован в нужном менеджере регионов. Когда представление, или элемент управления добавляется в визуальное дерево, как потомок другого элемента, или региона, любой регион, ассоциированный с элементом управления, должен быть зарегистрирован в RegionManager родительского элемента управления. При удалении элемента управления, регистрация региона должна быть удалена.
Auto-Population Behavior
За обнаружение представлений ответственны два класса. Один из них — это AutoPopulateRegionBehavior
. При присоединении к региону, он получает все типы представлений, которые зарегистрированы под именем этого региона. После этого, он создаёт экземпляры этих представлений и добавляет их в регион. После создания региона, AutoPopulateRegionBehavior
наблюдает за регистрацией новых представлений в RegionViewRegistry
для данного имени региона.
При необходимости большего контроля над процессом обнаружения представлений, есть возможность создания собственной реализации IRegionViewRegistry
и AutoPopulateRegionBehavior
.
Region Context Behaviors
Функциональность контекста региона заключена в SyncRegionContextWithHostBehavior
и BindRegionContextToDependencyObjectBehavior
. Эти поведения ответственны за наблюдение за изменениями в контексте, созданном для региона, и синхронизацию этого контекста с соответствующим свойством зависимости, присоединённом к представлению.
Activation Behavior
RegionActiveAwareBehavior
ответственен за уведомление представления о том, активно оно, или нет. Для получения этих уведомлений, представление должно реализовать интерфейс IActiveAware
. Эти уведомления однонаправленные и передаются от поведения к представлению. Представление не может сделать себя активным, влияя на соответствующее свойство интерфейса IActiveAware
.
Region Lifetime Behavior
RegionMemberLifetimeBehavior
определяет то, должен ли элемент быть удалён из региона, при его деактивации. RegionMemberLifetimeBehavior
следит за коллекцией ActiveViews
региона для определения, какие элементы были переведены в неактивное состояние. Поведение проверяет наличие у удаляемого элемента IRegionMemberLifetime
, или RegionMemberLifetimeAttribute
(именно в таком порядке) для определения, должен ли он быть сохранён при удалении.
Если элемент является System.Windows.FrameworkElement
, то его DataContext
также проверяется на наличие IRegionMemberLifetime
, или RegionMemberLifetimeAttribute
.
Элементы региона проверяются в следующем порядке:
- Значение
IRegionMemberLifetime.KeepAlive
- Значение
IRegionMemberLifetime.KeepAlive свойства DataContext
- Значение
RegionMemberLifetimeAttribute.KeepAlive
- Значение
RegionMemberLifetimeAttribute.KeepAlive свойства DataContext
Поведения, специфичные для элемента управления
SelectorItemsSourceSyncBehavior
используется только в том случае, если элемент управления унаследован от Selector
, к примеру, TabControl
в WPF. Оно ответственно за соответствие выделенных элементов в селекторе с активными представлениями в регионе.
TabControlRegionSyncBehavior
аналогичен SelectorItemsSourceSyncBehavior
, и используется вместо него в Silverlight.
Расширение функциональности регионов
Prism имеет точки для расширения, которые позволяют расширять или переопределять стандартное поведение предоставляемых API. К примеру, можно написать собственный адаптер регионов, поведение региона, изменить то, как Navigation API разбирает URI, или расширить Navigation API для работы с Silverlight Frame Navigation. Для получения дополнительной информации, смотрите раздел Extending Prism.
Композиция представлений
Композицией представлений является то, как представления создаются. В составных приложениях, представления из многочисленных модулей должны быть отображены во время выполнения в определённом месте пользовательского интерфейса. Для достижения этого, необходимо задать регионы, в которых представления будут расположены, и то, как представления будут созданы и отображены в этих регионах.
Представления могут быть созданы и отображены как автоматически, через обнаружение представлений, так и программно, через внедрение представлений. Эти две техники определяют, как представления будут отображены на именованные регионы в пользовательском интерфейсе приложения.
Обнаружение представлений (View Discovery)
При этом подходе, необходимо создать отношение между именем региона и типом представления в RegionViewRegistry
. Когда регион создаётся, он ищет все ViewTypes
, ассоциированные с этим регионом и автоматически создает и загружает соответствующие представления. Соответственно, в этом подходе, у вас нет непосредственного контроля над тем, как представления, соответствующие данному региону, создаются и отображаются.
Внедрение представлений (View Injection)
В этом случае, ваш код получает ссылку на регион, и затем программно добавляет представление в него. Обычно, это делается во время загрузки модуля, или в ответ на действие пользователя. Ваш код должен запросить RegionManager
для необходимого региона по его имени, после чего внедрить в него представление. Так вы имеете гораздо больший контроль над тем, как представления создаются и подставляются в регионы. Также имеется возможность удалять представления из регионов. Однако вы не можете добавлять представления в ещё не созданные регионы.
Навигация
В Prism Library 4.0 содержится Navigation API. Navigation API облегчает процесс внедрения представлений благодаря возможности навигации по URI. Navigation API создает представление, добавляет его в регион, и активирует его. Дополнительно, Navigation API позволяет перемещаться к созданным ранее представлениям, содержащимися в регионе. Для получения дополнительной информации смотрите часть 8 «Навигация».
Когда использовать обнаружение представлений, а когда внедрение представлений
Выбор стратегии загрузки региона зависит от требований приложения и функции региона.
Обнаружение представления может использоваться в следующих ситуациях:
- Автоматическая загрузка представлений желаема, или обязательна.
- В регион загружается единственный экземпляр представления.
Внедрение представления:
- Приложение использует Navigation API.
- Вам нужен явный контроль над тем, как представление создаётся и отображается, или вы должны удалять представления из регионов, к примеру, в ответ на действие пользователя или в результате навигации.
- Вы должны отобразить несколько экземпляров одного представления в регионе, привязав каждое представление к своим данным.
- Вы должны контролировать, в какой экземпляр региона добавляется представление. К примеру, вы хотите добавить представление с информацией о покупателе в соответствующий информационный регион. Этот подход требует создание региона с ограниченной областью действия, что будет показано далее.
Сценарии компоновки UI
В составных приложениях, представления из нескольких модулей отображаются во время выполнения в определённых местах в пользовательском интерфейсе. Для этого, необходимо задать регионы, где эти представления будут отображаться, а также то, как эти представления будут создаваться и ассоциироваться с этими регионами.
Разделение представлений и мест их отображения в интерфейсе, позволяет внешнему виду и компоновке приложения развиваться независимо от представлений, загружаемых в регионы.
Следующие разделы опишут основные сценарии, с которыми вы столкнётесь при разработке составных приложений. При необходимости, примеры из Stock Trader RI будут демонстрировать решения для этих сценариев.
Создание оболочки
Оболочка является корневым объектом приложения, в котором содержится первичный UI контент. В WPF приложении оболочкой является экземпляр Windows
. В Silverlight — RootVisualUserControl
.
Оболочка может содержать именованные регионы, в которые модули могут подставлять необходимые представления. Она также может задавать определённые высокоуровневые элементы UI, такие как главное меню и панель инструментов. Оболочка задаёт основную структуру и представление приложения, аналогично ASP.NET мастер-страницам. В ней могут задаваться стили, границы, которые видимы в самой оболочке, также в ней могут быть заданы стили, шаблоны и темы, применяемые к представлениям, загружаемым в оболочку.
Оболочка не является обязательной, при создании приложений, использующих Prism. Если вы создаёт новое составное приложение, то создание оболочки даст вам хорошо определённый корень приложения, а также сделает доступными шаблоны инициализации основного интерфейса приложения. Однако если вы добавляете поддержку Prism к уже существующему приложению, вы не обязаны менять его основную архитектуру, чтобы добавить оболочку. Вместо этого, вы можете изменить существующие определения окон или элементов управления, чтобы добавить регионы для отображения представлений.
У вас может быть больше, чем одна оболочка в приложении. Если приложение спроектировано таким образом, что позволяет пользователям открывать больше чем одно высокоуровневое окно, каждое такое окно может вести себя как оболочка для своего содержимого.
Оболочка Stock Trader RI
WPF приложение Stock Trader RI в качестве оболочки имеет главное окно. В следующей иллюстрации выделены оболочка и представления. Оболочка является главным окном, которое создаётся при запуске приложения и содержит все представления. В ней заданы регионы, в которые модули подставляют необходимые представления, и несколько высокоуровневых элементов пользовательского интерфейса, таких как заголовок и Watch List баннер.
Реализация оболочки Stock Trader RI представлена в файле Shell.xaml
, его файле отделённого кода Shell.xaml.cs
, а также в модели представления ShellViewModel.cs
. Shell.xaml
содержит разметку и UI элементы, являющиеся частью оболочки, включая определения регионов, в которые модули добавляют представления.
Следующий XAML показывает структуру и основные элементы, заданные в оболочке. Присоединяемое свойство RegionName
служит для определения регионов, а фоновое изображение окна является фоновым изображением оболочки.
XAML Shell.xaml (WPF)
<Window x:Class="StockTraderRI.Shell">
<!—shell background -->
<Window.Background>
<ImageBrush ImageSource="Resources/background.png" Stretch="UniformToFill"/>
</Window.Background>
<Grid>
<!-- Логотип -->
<Canvas x:Name="Logo">
<TextBlock Text="CFI" ... />
<TextBlock Text="STOCKTRADER" .../>
</Canvas>
<!-- Основная панель инструментов -->
<ItemsControl
x:Name="MainToolbar"
cal:RegionManager.RegionName="{x:Static inf:RegionNames.MainToolBarRegion}">
</ItemsControl>
<!-- Контент -->
<Grid>
<Controls:AnimatedTabControl
x:Name="PositionBuySellTab"
cal:RegionManager.RegionName="{x:Static inf:RegionNames.MainRegion}"/>
</Grid>
<!-- Детали -->
<Grid>
<ContentControl
x:Name="ActionContent"
cal:RegionManager.RegionName="{x:Static inf:RegionNames.ActionRegion}">
</ContentControl>
</Grid>
<!-- Боковая панель -->
<Grid x:Name="SideGrid">
<Controls:ResearchControl
cal:RegionManager.RegionName="{x:Static inf:RegionNames.ResearchRegion}">
</Controls:ResearchControl>
</Grid>
</Grid>
</Window>
Реализация Shell.xaml.cs
очень проста. Shell
является экспортируемой, что позволяет ей быть созданной загрузчиком. Её зависимости разрешаются MEF. Оболочка имеет единственную зависимость от ShellViewModel
, которая внедряется во время создания с помощью MEF.
C# Shell.xaml.cs
[Export]
public partial class Shell : Window {
public Shell() {
InitializeComponent();
}
[Import]
ShellViewModel ViewModel {
set {
this.DataContext = value;
}
}
}
C# ShellViewModel.cs
[Export]
public class ShellViewModel : NotificationObject {
// Здесь располагается любая логика модели представления для оболочки.
}
По количеству кода в code-behind файле, можно оценить мощь и простоту архитектуры составных приложений, и слабость связи межу оболочкой и представлениями.
Задание регионов
Регионы играют роль зон, в которых отображается одно, или несколько представлений во время выполнения. Модули могут обнаруживать регионы и загружать в них представления, не зная, как и где они расположены. Это позволяет менять изменять регионы, не затрагивая модули, которые предоставляют для них содержимое.
Регионы можно задать с помощью ассоциации имени региона с элементом управления в XAML, или в коде. Доступ к региону может быть получен по его имени. Во время выполнения, представления добавляются в регионы и отображаются в соответствии с особенностями ассоциированного с регионом элемента управления. К примеру, регион, ассоциированный с TabControl
, будет отображать представления в виде вкладок. Регионы поддерживают добавления и удаление представлений. Представления могут быть созданы и отображены как программно, так и автоматически. В Prism это достигается через использование внедрения представлений и обнаружение представлений. Эти две техники определяют то, как индивидуальные представления будут отображены в именованные регионы в пользовательском интерфейсе.
Оболочка приложения задаёт разметку самого высокого уровня. Для примера, задавая расположение основного контента и навигационного контента, как показано на иллюстрации ниже. Разметка внутри этих высокоуровневых представлений может быть задана аналогично, позволяя пользовательскому интерфейсу составляться рекурсивно.
Регионы иногда используются для задания расположения нескольких логически связанных представлений. В этом случае, регионом обычно является ItemsControl или его наследник, который отображает представления в соответствии со своей стратегией разметки, такой как расположение элементов в виде стопки, или в виде вкладок.
Регионы могут также использоваться для задания расположение единственного представления, используя, к примеру, ContenControl
. В этом случае элемент управления, ассоциированный с регионом, отображает только одно представление за раз, даже если в этот регион добавлено несколько представлений.
Регионы в оболочке Stock Trader RI
Stock Trader RI даёт примеры подходов, как с использованием единственного представления, так и нескольких представлений, добавляемых в регион. Вы можете видеть оба подхода в оболочке приложения. Следующая иллюстрация показывает регионы, заданные в оболочке Stock Trader RI.
Подход с использованием множественных представлений можно увидеть в Stock Trader RI при покупке, или продаже акций. Buy/Sell область является спископодобным регионом, в котором показаны OrderCompositeView
, как элементы списка, что показано ниже.
Регион оболочки ActionRegion
содержит OrdersView
. OrdersView
содержит кнопки Submit All и Cancel All, а также регион OrdersRegion
. OrdersRegion
присоединён к элементу управления ListBox
, в котором отображаются несколько представлений OrderCompositeViews
.
Интерфейс IRegion
Регионом является класс, реализующий интерфейс IRegion
. Регион представляет собой контейнер, содержащий контент для показа в элементе управления. Следующий код показывает, что содержится в этом интерфейсе.
public interface IRegion : INavigateAsync, INotifyPropertyChanged {
IViewsCollection Views { get; }
IViewsCollection ActiveViews { get; }
object Context { get; set; }
string Name { get; set; }
Comparison<object> SortComparison { get; set; }
IRegionManager Add(object view);
IRegionManager Add(object view, string viewName);
IRegionManager Add(object view, string viewName, bool createRegionManagerScope);
void Remove(object view);
void Deactivate(object view);
object GetView(string viewName);
IRegionManager RegionManager { get; set; }
IRegionBehaviorCollection Behaviors { get; }
IRegionNavigationService NavigationService { get; set; }
}
Добавление регионов в XAML
RegionManager
предоставляет присоединённое свойство, которое может быть использовано для простого создания региона в XAML. Для использования этого подхода, необходимо добавить пространство имён Prism в XAML и воспользоваться присоединённым свойством RegionName
. Следующий пример показывает, как ассоциировать регион с элементом AnimatedTabControl
.
Для того, чтобы получить строку с именем региона MainRegion
, используется расширение разметки x:Static
. Это позволяет избежать использования магических строк в XAML.
<Controls:AnimatedTabControl
x:Name="PositionBuySellTab"
cal:RegionManager.RegionName="{x:Static inf:RegionNames.MainRegion}"/>
Silverlight 4 не позволяет использовать x:Static
. Следовательно, вам придётся использовать строковые значения для задания регионов, или, при желании, определить строковые ресурсы, доступные всему приложению, в которых будут содержаться эти имена. Присоединённое свойство RegionName
, при использовании такого подхода, можно связать с таким ресурсом для получения имени региона.
<Controls:AnimatedTabControl
Regions:RegionManager.RegionName="MainRegion" />
Добавление регионов в коде
Используя RegionManager
можно регистрировать регионы непосредственно в коде, без использования XAML. Следующий пример кода показывает, как ассоциировать регион с элементом управления в файле отделённого кода. Для начала, необходимо получить ссылку на менеджер регионов. Затем, используя статический метод RegionManager.SetRegionManager
и RegionManager.SetRegionName
, регион ассоциируется с элементом управления ActionContent
, после чего устанавливается его имя в «ActionRegion».
IRegionManager regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
RegionManager.SetRegionManager(this.ActionContent, regionManager);
RegionManager.SetRegionName(this.ActionContent, "ActionRegion");
Отображение представлений в регионе, при его загрузке
При подходе с обнаружением представлений, модули могут регистрировать представления (модели представления, модели-презентеры) в определённых именованных регионах. После того, как регион будет отображён во время выполнения, любое представление, которое было зарегистрировано в данном регионе, будет создано и выведено на экран автоматически.
Модули регистрирую представления с помощью регистра. Родительское представление может запросить у регистра представления, которые были зарегистрированы в регионе с заданным именем. После обнаружения, родительское представление помещает эти представления на экран, добавляя их в элемент управления, ассоциированный с регионом.
После загрузки приложения, составное представление получает уведомления о том, что ему необходимо расположить в пользовательском интерфейсе новые представления, добавленные в регистр.
Следующая иллюстрация показывает подход с обнаружением представлений.
Prims предоставляет стандартный класс регистра RegionViewRegistry
, служащий для регистрации представлений в именованных регионах.
Для того, чтобы отобразить представление в регионе, необходимо зарегистрировать его в менеджере регионов. Как показано в примере ниже. Вы можете напрямую задать тип представления, после чего оно будет разрешено через контейнер внедрения зависимостей, при загрузке региона.
this.regionManager.RegisterViewWithRegion("MainRegion", typeof(EmployeeView));
Также, можно предоставить делегат, возвращающий необходимое представление, как показано ниже. Менеджер регионов отобразит представление при создании региона.
this.regionManager.RegisterViewWithRegion("MainRegion", () => this.container.Resolve<EmployeeView>());
В UI Composition QuickStar есть пошаговое руководство в EmployeeModule ModuleInit.cs
файле, которое демонстрирует, как использовать метод RegisterViewWithRegion
.
Отображение представление в регионе программно
В подходе с внедрением представлений, они программно добавляются, или удаляются из регионов, ответственными за них модулями. Для того, чтобы это стало возможным, приложение поддерживает регистр именованных регионов, объявленных в пользовательском интерфейсе. Модуль может использовать этот регистр для того, чтобы получить из него регион, и программно внедрить в него представление. Для того, чтобы сделать доступ к регионам единообразным по всему приложению, каждый именованный регион реализует интерфейс IRegion
. Следующая иллюстрация показывает подход с внедрением представлений.
Для внедрения представления в регион, необходимо получить этот регион через менеджера регионов и вызвать метод Add
, как показано ниже. При данном подходе, представление становится видным только после добавления его в регион, что может случиться после загрузки модуля, или в ответ на действие пользователя.
IRegion region = regionManager.Regions["MainRegion"];
var ordersView = container.Resolve<OrdersView>();
region.Add(ordersView, "OrdersView");
region.Activate(ordersView);
Stock Trader RI и UI Composition QuickStart содержат пошаговое руководство по применению этого подхода.
Навигация
Prism Library 4.0 включает Navigation API, даёт богатый и единообразный подход к осуществлению навигации в WPF, или Silverlight приложениях.
Навигация регионов является случаем внедрения представлений. Во время обработки запроса навигации, предпринимается попытка найти такое представление в регионе, которое может выполнить запрос. При невозможности найти подходящее представление, необходимое представление создаётся через DI контейнер, после чего внедряется в целевой регион и активируется.
Следующий код показывает, как на примере Stock Trader RI ArticleViewModel
инициировать запрос навигации.
this.regionManager.RequestNavigate(RegionNames.SecondaryRegion,
new Uri("/NewsReaderView", UriKind.Relative));
Для получения дополнительной информации, смотрите часть 8, раздел «Навигация». View-Switching Navigation QuickStart и State-Based Navigation QuickStart также являются образцами применения данного подхода.
Упорядочение представлений в регионе
Вне зависимости от того, какой подход используется, приложению часто бывает необходимо расположить представления в регионе в определённом подряде, в случае, если регион ассоциирован с элементом управления, поддерживающем отображение нескольких представлений. По умолчанию, представления располагаются в регионе в том порядке, в котором они были зарегистрированы и добавлены в этот регион.
При создании составного приложения, представления зачастую регистрируются разными модулями. Создание зависимостей между модулями может решить эту проблему, но это не является оптимальным подходом.
Для управления тем, как представления будут расположены в регионе, Prism предоставляет атрибут ViewSortHint
. Он содержит строковое свойство Hint
, которое позволяет представлению дать подсказку о том, как он хочет быть расположен в регионе.
При отображении представлений, класс Region
использует стандартную процедуру сортировки, принимающую во внимание эту подсказку, которая является простой, чувствительной к регистру, сортировкой строк. Представления, имеющие это атрибут, сортируются и располагаются впереди не имеющих его представлений. Представления без атрибута, располагаются в том порядке, в котором они были добавлены в регион.
Если вы захотите изменить способ сортировки представлений регионом, то класс Region
имеет свойство SortComparison
, через которое вы можете задать свой собственный делегат Comparison<object>
. Важно заметить, что сортировка свойств региона Views
и ActiveViews
отражается на UI благодаря тому, что такие адаптеры, как ItemsControlRegionAdapter
производят привязку непосредственно к этим свойствам. Нестандартный адаптер может реализовать свой собственный метод сортировки, который переопределит сортировку представлений, производимую в регионе.
View Switching QuickStart демонстрирует простую схему, использующую нумерацию, для упорядочения представлений в навигационном регионе. В следующем примере, ViewSortHint
применяется к каждому представлению, ответственному за навигацию.
[Export]
[ViewSortHint("01")]
public partial class EmailNavigationItemView { … }
[Export]
[ViewSortHint("02")]
public partial class CalendarNavigationItemView { … }
[Export]
[ViewSortHint("03")]
public partial class ContactsDetailNavigationItemView { … }
[Export]
[ViewSortHint("04")]
public partial class ContactsAvatarNavigationItemView { … }
Обмен данными между несколькими регионами
Prims предоставляет несколько подходов для взаимодействия между представлениями, в зависимости от вашего сценария. Менеджер регионов предоставляет свойство RegionContext
, как один из таких подходов.
RegionContext
полезен, когда вы хотите, чтобы родительское представление и дочерние представления использовали общий контекст данных. RegionContext
является присоединённым свойством. Вы задаёте необходимый контекст через это свойство, и он становится доступным дочерним представлениям, добавленным в соответствующий регион. Это может быть как простой, так и сложный объект, к которому может быть осуществлена привязка данных. RegionContext
может быть использован как при обнаружении, так и при внедрении представлений.
Заметка.
СвойствоDataContext
в Silverlight и в WPF используется для установки локального контекста данных для представления. Это позволяет представлению использовать привязку данных для взаимодействия с моделью представления, локальным презентером, или с моделью.RegionContext
используется для совместного использования контекста несколькими представлениями и не является локальным для единственного представления. Это предоставляет простой механизм совместного использования контекста между несколькими представлениями.
Следующий код показывает, как присоединённое свойство RegionContext
используется в XAML.
<TabControl AutomationProperties.AutomationId="DetailsTabControl"
cal:RegionManager.RegionName="{x:Static local:RegionNames.TabRegion}"
cal:RegionManager.RegionContext="{Binding Path=SelectedEmployee.EmployeeId}"
... >
Вы также можете установить RegionContext
в коде, как показано ниже.
RegionManager.Regions["Region1"].Context = employeeId;
Для получения контекста в представлении, можно использовать статический метод RegionContext.GetObservableContext
. Он принимает представление, как параметр, после чего контекст может быть доступен через свойство Value
, как показано ниже.
private void GetRegionContext() {
this.Model.EmployeeId = (int)RegionContext.GetObservableContext(this).Value;
}
Значение RegionContext
может быть изменено простым заданием нового значения свойству Value
. Представления могут подписаться на уведомления об изменениях контекста, с помощью события PropertyChanged
объекта типа ObservableObject
, который можно получить, вызвав метод GetObservableContext
. Это позволяет нескольким представлениям быть синхронизированными при изменении контекста. Следующий пример показывает, как подписаться на изменения контекста.
ObservableObject<object> viewRegionContext =
RegionContext.GetObservableContext(this);
viewRegionContext.PropertyChanged += this.ViewRegionContext_OnPropertyChangedEvent;
private void ViewRegionContext_OnPropertyChangedEvent(object sender,
PropertyChangedEventArgs args) {
if (args.PropertyName == "Value") {
var context = (ObservableObject<object>) sender;
int newValue = (int)context.Value;
}
}
Заметка.
RegionContext
устанавливается как присоединённое свойство на объекте, расположенном в регионе, из чего следует, что этот объект должен быть наследникомDependencyObject
. В предыдущем примере, представлением был элемент управления, который по умолчанию унаследован отDependencyObject
. Если вы используете шаблоны данных в качестве представления, то этим объектом будет модель представления, или модель, которые являются обычными классами. Для того, чтобы воспользоватьсяRegionContext
, необходимо унаследовать этот объект от базового классаDependencyObject
.
Создание нескольких экземпляров региона
Регионы с ограниченной областью видимости доступны только при использовании внедрения представлений. Они могут понадобиться, если вам необходимо представление с собственным экземпляром региона. Представления, задающие регионы с помощью присоединённого свойства, автоматически наследуют родительский RegionManager
. Обычно, это RegionManager
общий для всего приложения, зарегистрированный в оболочке. Если приложение создаст больше, чем одно представление данного типа, каждое из них будет использовать один и тот же RegionManager
для регистрации своих регионов. Так как менеджер регионов допускает только уникальные имена регионов, вторая регистрация региона в представлении вбросит исключение.
Для предотвращения этого, можно использовать регионы с ограниченной областью видимости. В этом случае, каждое представление будут иметь своего собственного менеджера регионов, и его регионы будут регистрироваться с помощью именно этого менеджера, а не родительского. Данный подход показан на иллюстрации ниже.
Для создания локального для представления менеджера регионов, необходимо указать, что новый RegionManager
должен быть создан при добавлении представления в регион, как показано ниже.
IRegion detailsRegion = this.regionManager.Regions["DetailsRegion"];
View view = new View();
bool createRegionManagerScope = true;
IRegionManager detailsRegionManager =
detailsRegion.Add(view, null, createRegionManagerScope);
Метод Add возвращает новый менеджер регионов, которым представление может сохранить для последующего обращения к регионам с локальной области видимости.
Создание представлений
Внешний вид вашего приложения может создаваться из самых разных элементов, таких как пользовательские элементы управления, специальные элементы управления и шаблоны данных. В случае с Stock Trader RI, пользовательские элементы управления обычно используются для представления разрозненных областей в главном окне, но это не является стандартом. В вашем приложении, вы должны применять тот подход, с которым вы лучше всего знакомы, и который лучше всего подходит для дизайнера. Вне зависимости от внешнего вида вашего приложения, вы, скорее всего, будете использовать смесь из пользовательских элементов управления, специальных элементов управления и шаблонов данных. Следующий рисунок показывает, где какие подходы использует Stock Trader RI. На него также будут ссылаться последующие разделы, описывающие каждый из подходов.
Пользовательские элементы управления (User Controls)
Как Expression Blend, так и Visual Studio 2010-2012 дают богатую поддержку для создания этих элементов управления. Пользовательские элементы управления, созданные с помощью этих инструментов, рекомендуются для создания пользовательского интерфейса при использовании Prism. Как было упомянуто ранее, Stock Trader RI широко их использует для создания содержимого регионов. Пользовательский элемент управления WatchListView.xaml является хорошим примером того, как небольшая часть пользовательского интерфейса может быть помещена внутри модуля WatchModule. Такие элементы управления очень просты и прямолинейны для создания и использования.
Специальные элементы управления (Custom Controls)
В некоторых ситуациях, пользовательские элементы управления могут оказаться слишком ограниченными. В этих случаях, специальная разметка, или расширяемость может быть более важной, чем простота создания. В таких случаях могут пригодиться специальные элементы управления. В Stock Trader RI, хорошим примером этого, является элемент управления с круговой диаграммой. Этот тип элементов управления более сложен для создания, чем пользовательские, и имеет более ограниченную поддержку для создания в Expression Blend и Visual Studio 2010-2012, по сравнению с пользовательскими элементами управления.
Шаблоны данных (Data Templates)
Шаблоны данных являются важной частью большинства типов приложений, оперирующих данными. Использование шаблонов данных в list-based элементах управления особенно часто можно встретить в Stock Trader RI. Во многих случаях, используя шаблоны данных, можно обойтись вообще без создания пользовательских элементов управления. Регион ResearchRegion
использует шаблоны данных для показа статей, и, вместе со стилями для Items
, показывает, какой элемент был выделен.
В Expression Blend имеется полная поддержка визуального создания шаблонов данных. Visual Studio 2010 предоставляет редактирование шаблонов данных только через XAML. В Visual Studio 2012 визуальный редактор взят из Expression Blend, поэтому предоставляется почти аналогичная поддержка шаблонов данных, как и в самом Expression Blend.
Ресурсы (Resources)
Ресурсы, такие как стили, словари ресурсов и шаблоны, могут быть разбросаны по всему приложению. Это особенно верно для составных приложений. Когда вы думаете о то, куда поместить какой-либо ресурс, обратите особенное внимание на зависимости между элементами UI и ресурсами, в которых они нуждаются. Проект Stock Trader RI, показанный на рисунке ниже, содержит метки, показывающие, где могут жить ресурсы.
Ресурсы уровня приложения
Обычно, ресурсы уровня приложения, используются по всему приложению. Эти ресурсы, как правило, сосредоточены на корневом каталоге приложения, но они могут также предоставлять стили по умолчанию на основе типов для модулей и элементов управления. Примером может служить стиль для текстового поля, применимый к типу TextBox
в корне приложения. Этот стиль будет применён ко всем текстовым полям в приложении, если он не будет переопределён в модуле, или в самом элементе управления.
Ресурсы уровня модуля
Роль ресурсов уровня модуля аналогична ресурсам уровня приложения в том плане, что они применяются ко всем элементам в модуле. Использование ресурсов на этом уровне может предоставить согласованный вид отдельного модуля, а также обеспечить повторное использование отдельных элементов в пределах модуля. Использование ресурсов уровня модуля должно быть ограничено в пределах этого модуля. Создание зависимостей между модулями может приводить к труднообнаружимым ошибкам в отображении пользовательских элементов.
Ресурсы уровня элемента управления
Данный вид ресурсов обычно содержится в библиотеках пользовательских элементов, в которых ресурсы используются всеми содержащимися в ней элементами. Эти ресурсы обычно имеют самую малую область видимости, из-за того, что библиотеки пользовательских элементов содержат только определённые элементы и не содержат пользовательских элементов управления. (В Prism приложениях, пользовательские элементы управления обычно размещаются в тех модулях, в которых они используются.)
Автор: Unrul