Инжекторы контекста xaml

от автора

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

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

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

Все эти трудности так или иначе решаемы, но существует универсальный и очень простой способ их красиво разрешить. О нём и пойдет речь.


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

Пускай имеется единое хранилище Store, из которого по ключу извлекается нужный экземпляр объекта. Идея состоит в том, чтобы с помощью расширения xaml-разметки обеспечить возможность извлечения произвольного экземпляра объекта и дальнейшее его инжектирование в качестве контекста данных в любой узел визуального дерева.

Выглядит всё очень просто:

<Control DataContext="{Store Key=viewModels:AppViewModel}"/> 

<Control> 	<Control.DataContext> 		<Store Key=viewModels:AppViewModel> 	</Control.DataContext> </Control> 

Получить доступ к вью-моделям из C#-кода также крайне легко:

var appViewModel = Store.Get<AppViewModel>(); var userViewModel = Store.Get<IUserViewModel>(); 

Более того, WPF позволяет выполнить инжектирование даже в привязку!

<Slider 	DataContext="{Store Key=viewModels:MapViewModel}" 	Minimum="{Bindind MinimumZoomValue}" 	Maximum="{Binding MaximumZoomValue}" 	Value="{Binding ZoomValue, Mode=TwoWay}" 	Visibility="{Binding ShowSlider, Source="{Store Key=viewModels:SettingsViewModel}", Converter={StaticResource TrueToVisibleConverter}}"/> 

Обратите внимание на строку Visibility="{Binding ShowSlider, Source="{Store Key=viewModels:SettingsViewModel}"…, такой гибкости тяжело достичь даже с помощью бехаин-кода, а наша запись получилась очень лаконичной.

Справедливости ради нужно сказать, что парсеры разметки на многих других платформах требуют префиксы, а также не поддерживают вложенных друг в друга расширений, но эта проблема решается элементарно с помощью расширения привязки (Binding Extension):

<Slider 	DataContext="{m:Store Key=viewModels:MapViewModel}" 	Minimum="{Bindind MinimumZoomValue}" 	Maximum="{Binding MaximumZoomValue}" 	Value="{Binding ZoomValue, Mode=TwoWay}" 	Visibility="{m:StoreBinding Path=ShowSlider, StoreKey=viewModels:SettingsViewModel, Converter={StaticResource TrueToVisibleConverter}}"/> 

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

То есть, чтобы связать экран приложения (страницу или окно) достаточно лишь нескольких строк:

<!--WP7, WP8, WPF--> <Page 	xmlns:viewModels="clr-namespace:AeroPlayer.ViewModels" 	DataContext="{m:Store Key=viewModels:SongViewModel}"> 	... </Page> 

<!--WPF--> <Window 	xmlns:viewModels="clr-namespace:AeroPlayer.ViewModels" 	DataContext="{Store viewModels:SongViewModel}"> 	... </Window> 

<!--Windows Store, WP8.1--> <Page xmlns:viewModels="using:AeroPlayer.ViewModels">       <Page.DataContext> 		<Store Key=viewModels:AppViewModel> 	</Page.DataContext> 	... </Page> 

И никакого бехаинд-кода! С контекстными меню теперь всё очень изящно:

<ContextMenu DataContext="{Store viewModels:AppViewModel}"> 	... </ContextMenu> 

Но как красиво решаются подобные ситуации:

<ListBox DataContext={Store viewModels:AppViewModel} ItemsSource={Binding Persons}>     <ListBox.ItemTemplate>         <DataTemplate>             <StackPanel>                 <TextBlock Text="{Binding FirstName}"/>                 <TextBlock Text="{Binding LastName}"/>                 <TextBlock                  	Text="{Binding Age}"                 	Visibility="{StoreBinding Path=ShowDetails, StoreKey=viewModels:SettingsViewModel, Converter={StaticResource TrueToVisibleConverter}}"/>             </StackPanel>         </DataTemplate>     <ListBox.ItemTemplate> </ListBox> 

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

Спасибо за интерес!

ссылка на оригинал статьи http://habrahabr.ru/post/254373/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *