Сегодня спустя
столь долгого перерыва в пять месяцев я решил снова вернуться к статьям по Prism 5, так как вижу, что эта тема интересна для
читателей, один из которых недавно поделился проблемами, которые он пытался
решить с помощью призма. Интересные моменты, которые, возможно, пригодятся вам
для решения подобных проблем, мы рассмотрим сегодня в статье, а также в
следующих статьях по призму. Надеюсь, что тема будет нескучная и полезная. А
мне достаточно того факта, что я поборол свою лень и апатию и смог наконец-то
вернуться к написанию статей по программированию. Поэтому сегодня мы будем
играться с регионами в Prism. Мне почему-то
казалось, что использование регионов не должно вызвать сложностей, но,
оказывается, здесь действительно есть много нюансов, которые сложны для
понимания.
Мы рассмотрим
два способа компоновки Views (представлений):
с помощью View Discovery и View Injection. Если вы только начинаете свое
знакомство с регионами, то рекомендую статью "Введение в Prism
5. Работа с регионами", в которой на простом примере
продемонстрировано использование регионов. Сегодня также каждую тему, связанную
с регионами, мы будем демонстрировать на практике. Поэтому рекомендую сразу
сделать себе заготовку, в которой мы последовательно будем разбирать каждый
отдельный этап. Поехали.
Как и в
предыдущих статьях, я использую последний Prism 5,
поэтому если вы работаете с версией 4.1, то вам, возможно, придется слегка
модифицировать ваш код. Но в целом мы не будем использовать много разных
моделей (Model) и моделей представлений (ViewModel)
с классического паттерна MVVM, а будем в основном обходиться только представлениями (View). Для демонстрации работы регионов этого будет
достаточно. Давайте создадим новый WPF проект
с выбором фреймворка 4.5. Назовем наш проект "RegionViewerSample".
Дальше, как обычно, нужно создать структуру для нашего проекта. Поэтому создаем такую структуру
папок:
Эта структура,
как несложно догадаться, реализует классическую схему паттерна MVVM, где в папку Models мы
складываем модели, в ViewModels – модели
представления, и в папку Views – сами
представления (внешний вид контролов, окна и т.д.). Две папки добавлены уже
специально под логику призма. Modules – работа с
модулями, Regions – UI
контролы
для заполнения регионов. Ничего сложного на этом этапе не должно возникнуть.
Так как я использую Prism, то стараюсь
оперировать его понятиями, а в призме
основное окно
называется оболочкой (Shell). Поэтому
первое, что я сделаю, – это переименую мой класс MainWindow.xaml в Shell.xaml. Затем перенесу это класс в папку Views. Затем уберу с App.xaml метод StartupUri для
запуска главного окна. После этого нужно создать загрузчик, который будет
создавать и инициализировать наше главное окно, а также, если это необходимо,
устанавливать все начальные параметры.
Я использую для таких случаев в 90%
загрузчик UnityBootstrapper. Скачивать его
нужно отдельно, как и саму библиотеку Prism. Сделать это
можно с помощью Manager NuGet Packages.
Для этого открываем Manager NuGet Packages, вводим слово Prism и устанавливаем библиотеки призма,
которые идут самые первые.
Затем на второй
странице также устанавливаем Prism.UnityExtensions, который и содержит загрузчик UnityBootstrapper. Этот загрузчик
использует IoC контейнер Unity для управления зависимостями. Этот IoC контейнер мне нравится своими возможностями, поэтому предпочитаю использовать именно его. Затем переходим к созданию класса загрузчика.
Назовем этот класс просто: Bootstrapper. Ниже приведена его реализация.
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<Shell>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow = (Window) Shell;
Application.Current.MainWindow.Show();
}
}
Не забывайте,
что мы уже переименовали с MainWindow.xaml на Shell.xaml и все поправили в соответствии
с этим. Затем нам нужно завершить финальную стадию подготовки нашего проекта.
Для этого нужно перейти в класс App.xaml.cs и переопределить метод OnStartup на приведенный ниже.
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
var bootstrapper = new Bootstrapper();
bootstrapper.Run();
}
}
После этого ваш
проект должен успешно собраться и запуститься, показав главное пустое окно. Если
у вас что-то не получилось, не расстраивайтесь. Просто посмотрите вот на эту
статью, в которой эти шаги описаны более детально: "Введение в Prism 5. Bootstrapper". Со временем
на подготовку проекта у вас будет уходить очень немного времени. Или вы можете
создать свой темплейт, который будете использовать в дальнейшем в своих проектах. Например,
по аналогии, как это сделано в MVVM Light. У вас подготовка проекта для работы
с призмом займет считанные минуты.
А теперь
вернемся к нашим регионам и рассмотрим, как с ними работать. Сначала немного
теории, но лишь чуть-чуть. Просто для того, чтобы понимать суть происходящего и
иметь представление о том, почему было сделано так, а не иначе. С регионами мы
можем работать с помощью менеджера регионов, а также адаптеров регионов. Что
первое, что второе – довольно сложная тема, мы ее рассмотрим только
поверхностно. Постараюсь детальнее к ней вернуться в следующих статьях.
Вернемся для начала к менеджеру регионов (RegionManager).
Это класс, который отвечает за то, чтобы создать регионы и связать их с
элементами управления. Ниже показан рисунок, взятый с документации, который
демонстрирует работу класса RegionManager.
С помощью RegionManager
мы можем создавать регионы как в xaml-разметке, так и в коде. Приложение может
содержать один или несколько экземпляров RegionManager. Есть возможность
выбирать, в каком экземпляре менеджера регионов необходимо зарегистрировать
регион. Это может оказаться полезным, если вы хотите передвинуть элемент
управления в визуальном дереве, но не хотите, чтобы регион очистился после
очищения присоединённого свойства.
RegionManager
предоставляет присоединённое свойство RegionContext, с помощью которого можно
обмениваться данными между регионами. У меня недавно спрашивали, как передать
данные в другой регион для обмена. Вот один из ответов. Например, через
RegionContext, хотя я больше предпочитаю белее сложное управление через
навигацию на основе моделей.
Мы так много
говорим о регионах; пора бы уже, наверное, подойти вплотную к определению этого
понятия. В призме регионом называется класс, который реализует интерфейс
IRegion. По сути, это контейнер, в котором содержится динамическое содержимое
пользовательского интерфейса. Еще мы упомянули о таком понятии, как адаптеры
регионов. Так вот: для того чтобы подставить элемент в регион, он должен иметь
необходимый адаптер. Каждый адаптер регионов адаптирует определённый тип
элементов управления. Prism предоставляет следующие адаптеры:
- ContentControlRegionAdapter. Он адаптирует ContentControl и его наследники.
- SelectorRegionAdapter адаптирует Selector и его наследники, такие как TabControl.
- ItemsControlRegionAdapter адаптирует ItemsControl и его наследники.
Давайте рассмотрим
использование регионов на простеньком примере. Для этого
откроем редактор нашей оболочки Shell.xaml и добавим следующий код.
Window x:Class="RegionViewerSample.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:regions="http://www.codeplex.com/CompositeWPF"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ItemsControl regions:RegionManager.RegionName="ComboBoxRegion" Grid.Row="0"/>
<ItemsControl regions:RegionManager.RegionName="GroupBoxRegion" Grid.Row="1"/>
<ContentControl regions:RegionManager.RegionName="CompositeRegion" Grid.Row="2"/>
</Grid>
</Window>
Если мы выполним
наш код, то ничего, к сожалению, не увидим на экране, кроме главного окна. Это
потому что мы не указали, какие представления мы должны отображать в данных
регионах. Для этого нам понадобится использовать класс RegionManager и один из способов композиции представлений.
Композицией представлений является способ создания представлений. Представления
могут быть созданы как автоматически, через обнаружение представлений (View Discovery), так и программно, через внедрение представлений (View Injection). Мы рассмотрим
сначала первый способ с обнаружением представлений. При этом подходе необходимо
создать отношение между именем региона и представлением через класс. RegionViewRegistry, используя метод RegisterViewWithRegion. Но можно пойти проще, так как для класса RegionManager написано куча методов расширения, то у нас есть метод такой же метод для интерфейса IRegionManager (ну и, понятное дело, что этот метод
реализует внутри себя класс RegionViewRegistry).
А если мы заглянем
в исходники библиотеки и откроем пространство имен Regions, то можем увидеть, сколько логики и
невиданных дебрей для нас припасено.
Я и сам не знаю большей части из этого, так как не было возможности все использовать из-за специфики
использования призма. Теперь давайте перейдем в папочку Regions в нашем проекте и добавим туда несколько UserControl, которые будут
заполнять наши регионы. Первым добавим контрол ComboBoxView.xaml. Реализация
этого контрола приведена ниже.
<UserControl x:Class="RegionViewerSample.Views.Regions.ComboBoxView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ComboBox>
<ComboBoxItem Content="Test1"/>
<ComboBoxItem Content="Test2"/>
<ComboBoxItem Content="Test3"/>
</ComboBox>
</Grid>
</UserControl>
Реализация его
очень примитивная. Обычный комбобокс с трема пунктами выбора. Следующим
создадим контрол GroupBoxView.xaml.
<UserControl x:Class="RegionViewerSample.Views.Regions.GroupBoxView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<GroupBox>
<StackPanel Orientation="Vertical">
<RadioButton Content="1" />
<RadioButton Content="2" />
<RadioButton Content="3" />
<RadioButton Content="4" />
<RadioButton Content="5" />
</StackPanel>
</GroupBox>
</Grid>
</UserControl>
Ну и напоследок
создадим контрол, который внутри себя будет содержать еще дочерние регионы CompositeView.xaml.
<UserControl x:Class="RegionViewerSample.Views.Regions.CompositeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:regions="http://www.codeplex.com/CompositeWPF"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Border Background="DarkSeaGreen" Margin="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentControl regions:RegionManager.RegionName="RegionOne" Grid.Column="0"/>
<ContentControl regions:RegionManager.RegionName="RegionTwo" Grid.Column="1"/>
</Grid>
</Border>
</Grid>
</UserControl>
Теперь давайте это
все свяжем с помощью модулей в призме. Для этого перейдем в папку Modules и создадим класс MainModule. Реализация этого класса приведена ниже.
public class MainModule : IModule
{
private readonly IRegionManager _regionManager;
public MainModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void Initialize()
{
_regionManager.RegisterViewWithRegion("ComboBoxRegion",
typeof(ComboBoxView));
_regionManager.RegisterViewWithRegion("GroupBoxRegion",
typeof(GroupBoxView));
_regionManager.RegisterViewWithRegion("CompositeRegion",
typeof(CompositeView));
_regionManager.RegisterViewWithRegion("RegionOne",
typeof(ComboBoxView));
_regionManager.RegisterViewWithRegion("RegionTwo",
typeof(GroupBoxView));
}
}
Код, как
мне кажется, очень прост для понимания. Мы указываем, какой регион какой контрол
будет отображать. Затем нам нужно инициализировать этот модуль в нашем
загрузчике (класс Bootstrapper который мы добавили раньше).
Для этого нужно переопределить метод InitializeModules().
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<Shell>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow = (Window)Shell;
Application.Current.MainWindow.Show();
}
protected override void InitializeModules()
{
IModule mainModule =
Container.Resolve<MainModule>();
mainModule.Initialize();
}
}
После
этого мы можем запустить проект на экране и увидеть результат того, что у нас
получилось.
Вид этого,
конечно, не товарный. Но наша задача – научиться пока это
использовать, а не рисовать красивый интерфейс в xaml. А теперь давайте в этом
же примере вместо View Discovery воспользуемся внедрением представлений (View
Injection). В этом случае ваш код получает ссылку на регион, и затем программно
добавляет представление в него. Обычно это делается во время загрузки модуля
или в ответ на действие пользователя. Ваш код должен запросить RegionManager
для необходимого региона по его имени, после чего внедрить в него
представление. Так вы имеете гораздо больший контроль над тем, как
представления создаются и подставляются в регионы. Этот код намного лучше
подходит для динамичной компоновки регионов. Но для наше примера он тоже
неплохо подойдет. Для этого перейдем в класс MainModule и модифицируем его
следующим образом:
public class MainModule : IModule
{
private readonly IRegionManager _regionManager;
public readonly IUnityContainer _container;
public MainModule(IUnityContainer container, IRegionManager
regionManager)
{
_container = container;
_regionManager = regionManager;
}
public void Initialize()
{
_regionManager.Regions["ComboBoxRegion"].Add(_container.Resolve<ComboBoxView>());
_regionManager.Regions["GroupBoxRegion"].Add(_container.Resolve<GroupBoxView>());
_regionManager.Regions["CompositeRegion"].Add(_container.Resolve<CompositeView>());
_regionManager.Regions["RegionOne"].Add(_container.Resolve<ComboBoxView>());
_regionManager.Regions["RegionTwo"].Add(_container.Resolve<GroupBoxView>());
}
}
Дело в том,
что зачастую вам придётся использовать View Injection, так как такой подход внедрения представлений более
гибкий и позволяет вам управлять изменением содержимого региона самим. На этом закончу свою небольшую статью по работе с регионами и управлению в них
представлениями. В следующей статье постараемся рассмотреть еще что-то интересное по Prism 5.
No comments:
Post a Comment