- PVSM.RU - https://www.pvsm.ru -
Нужно признать, xaml-код бывает отчасти многословным, что вызывает иногда некоторый дискомфорт при разработке. В статье рассмотрим оптимизации, которые помогут существенно улучшить организацию разметки и сделать её более читаемой. Особенно это касается работы с конвертерами, которые неотъемлемо связаны с механизмом привязки данных.
Нам понадобятся некоторые знания из прошлых статей [1], в частности, понимание принципа прямых инжекций [2].
Bindable Converters
Рано или поздно многие xaml-разработчики сталкивается с вопросом, возможно ли создать конвертер, поддерживающий привязку каких-либо параметров? Но даже если помимо реализации интерфейса IValueConverter, произвести наследование от класса DependencyObject и объявить в конвертере DependencyProperty, то привязка работать в большинстве случаев не станет, поскольку конвертер не является элементом визуального дерева! Конечно, возможно пойти ещё дальше и создать гибрид контрола-конвертера, незаметно помещаемого на представление, но такое экзотическое решения вряд ли можно назвать красивым, да и спектр его применения ограничен.
Но на выручку приходит принцип прямых инжекций, ведь ничто не мешает применить StoreBinding к Dependency Converter.
<BooleanConverter
x:Key="BindableConverter"
OnTrue="Value1"
OnFalse="Value2"
OnNull="{StoreBinding StoreKey=viewModels: SettingsViewModel, Path=AnyValue3}"/>
Всё гениальное просто!
Отметим, что таким образом нельзя привязать конвертер к элементу визуального дерева, даже если они находятся на одном представлении. Но и такая проблема решаема, например, с помощью Attached Property у контрола и создания соответствующего расширения привязки.
<ToggleButton a:SourceKey="MyToogleButton">
<BooleanConverter
x:Key="BindableConverter"
OnTrue="Value1"
OnFalse="Value2"
OnNull="{RemoteBinding SourceKey=MyToogleButton, Path=IsChecked}"/>
Причём, такой вариант будет работать, даже если контрол и конвертер не находятся на одном представлении! Необходимо лишь быть осторожным с его реализацией, использовать для хранения слабую ссылку на контрол, чтобы не получилось утечек памяти.
Switch Converter
Часто в больших проектах приходится создавать много различных однотипных классов-конвертеров, логика которых очень напоминает поведение операторов if-else и switch, например, для различных перечислений (Enums). Но на самом деле, в таких случаях достаточно ограничиться применением универсального Switch Converter:
<SwitchConverter Default="ResultValue0" x:Key="ValueConverter1">
<Case Key="KeyValue1" Value="ResultValue1"/>
<Case Key="KeyValue2" Value="ResultValue2"/>
</SwitchConverter>
Более того, свойства этого конвертера (в том числе конструкции Case) являются Dependency, то есть доступными для привязки с помощью StoreBinding! Кроме того, поддерживается Type Mode, когда ключом является не само значение объекта, а его тип:
<SwitchConverter TypeMode="True" Default="{StaticResource DefaultDataTemplate}" x:Key="InfoConverter">
<Case Type="local:Person" Value="{StaticResource PersonDataTemplate}"/>
<Case Type="local:PersonGroup" Value="{StaticResource PersonGroupDataTemplate}"/>
</SwitchConverter>
Получается, что такой конвертер запросто применим в качестве DataTemplateSelector даже там, где последний не поддерживается! Универсальность Switch Converter позволяет покрыть огромное число случаев, стоит только применить к нему немного фантазии.
<c:SwitchConverter Default="{StaticResource ControlTemplate0}" x:Key="TemplateSelectorConverter">
<m:Case Key='Value1' Value="{StaticResource ControlTemplate1}"/>
<m:Case Key='Value2' Value="{StaticResource ControlTemplate2}"/>
</c:SwitchConverter>
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl Template="{Binding DataType, Converter={StaticResource TemplateSelectorConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Global Resources
Раз уж зашёл разговор о конвертерах, то стоит рассказать, как лучше всего организовать работу с ними. Прежде всего самые распространённые нужно вынести в отдельный словарь ресурсов:
<!--AppConverters .xaml-->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<BooleanConverter x:Key="NullToTrueConverter" OnNull="True" OnNotNull="False"/>
<BooleanConverter x:Key="NullToFalseConverter" OnNull="False" OnNotNull="True"/>
<BooleanConverter x:Key="NullToVisibleConverter" OnNull="Visible" OnNotNull="Collapsed"/>
<BooleanConverter x:Key="NullToCollapsedConverter" OnNull="Collapsed" OnNotNull="Visible"/>
<BooleanConverter x:Key="TrueToFalseConverter" OnTrue="False" OnFalse="True" OnNull="True"/>
<BooleanConverter x:Key="FalseToTrueConverter" OnTrue="False" OnFalse="True" OnNull="False"/>
<BooleanConverter x:Key="TrueToVisibleConverter" OnTrue="Visible" OnFalse="Collapsed" OnNull="Collapsed"/>
<BooleanConverter x:Key="TrueToCollapsedConverter" OnTrue="Collapsed" OnFalse="Visible" OnNull="Visible"/>
<BooleanConverter x:Key="FalseToVisibleConverter" OnTrue="Collapsed" OnFalse="Visible" OnNull="Collapsed"/>
<BooleanConverter x:Key="FalseToCollapsedConverter" OnTrue="Visible" OnFalse="Collapsed" OnNull="Visible"/>
<EqualsConverter x:Key="EqualsToCollapsedConverter" OnEqual="Collapsed" OnNotEqual="Visible"/>
<EqualsConverter x:Key="EqualsToVisibleConverter" OnEqual="Visible" OnNotEqual="Collapsed"/>
<EqualsConverter x:Key="EqualsToFalseConverter" OnEqual="False" OnNotEqual="True"/>
<EqualsConverter x:Key="EqualsToTrueConverter" OnEqual="True" OnNotEqual="False"/>
<AnyConverter x:Key="AnyToCollapsedConverter" OnAny="Collapsed" OnNotAny="Vsible"/>
<AnyConverter x:Key="AnyToVisibleConverter" OnAny="Visible" OnNotAny="Collapsed"/>
<AnyConverter x:Key="AnyToFalseConverter" OnAny="False" OnNotAny="True"/>
<AnyConverter x:Key="AnyToTrueConverter" OnAny="True" OnNotAny="False"/>
</ResourceDictionary>
После чего необходимо прямо или косвенно смержить этот словарь с ресурсами в App.xaml, что позволит использовать основные конвертеры практически в любых xaml-файлах приложения без дополнительных действий. Такое добавление в глобальные ресурсы приложения полезно производить для любых более-менее общих вещей: цветов, кистей, шаблонов и стилей, — что помогает очень просто реализовать, к примеру, механизмы смены тем в приложении.
<Application
x:Class="Sparrow.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Views/AppView.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--<ResourceDictionary Source="AppConverters.xaml"/>-->
<ResourceDictionary Source="AppStore.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Sets
Очень полезным приёмом является использование в xaml универсальной коллекции Set, применимой во множестве случаев. Она позволяет избежать «многоэтажных конструкций», вынести общие моменты и сделать разметку намного более аккуратной, а также передавать несколько аргументов в команду.
public class Set : ObservableCollection<object>
{
}
<Set x:Key="EditMenuSet" x:Shared="False">
<MenuItem
Header="{Localizing Undo}"
Command="Undo"/>
<MenuItem
Header="{Localizing Redo}"
Command="Redo"/>
<Separator/>
<MenuItem
Header="{Localizing Cut}"
Command="Cut"/>
<MenuItem
Header="{Localizing Copy}"
Command="Copy"/>
<MenuItem
Header="{Localizing Paste}"
Command="Paste"/>
<MenuItem
Header="{Localizing Delete}"
Command="Delete"/>
<Separator/>
<MenuItem
Header="{Localizing SelectAll}"
Command="SelectAll"/>
</Set>
<MenuItem Header="{Localizing Edit}" ItemsSource="{StaticResource EditMenuSet}"/>
<Set x:Key="ParameterSet">
<system:String>/Views/AnyView.xaml</system:String>
<system:String>SecondParaneter</system:String>
</Set>
<Button
Content="{Localizing GoToAnyView}"
Command="{Context Key=GoTo}"
CommandParameter="{StaticResource ParameterSet}">
Благодарю за внимание!
Обращение автора
Надеюсь, что все эти статьи интересны и полезны разработчикам. Особенно хочется отметить взаимную согласованность предложенных концепций между собой, они как бы дополняют и усиливают друг друга. Думаю, что те люди, которые изучат материалы достаточно хорошо, смогут применить их в работе и оценить по достоинству.
Глупо скрывать эти знания или брать за них плату, поскольку даже воспринять и осознать их оказывается не так просто. Возможно, через некоторое время кто-то поймёт, как много ресурсов помогли сохранить эти простые по своей форме советы, и захочет поблагодарить создателя библиотеки Aero Framework, потому реквизиты для пожертвований доступны на этой странице [3]. Быть может, некоторые даже пожелают ещё более углубиться в изучение и приобрести исходные коды реальных проектов [4] (конечно, для индивидуальных разработчиков их стоимость может показаться ощутимой, но для компаний это символическая плата).
Поэтому, даже если вы пользуетесь библиотекой бесплатно, то, пожалуйста, рекомендуйте её своим коллегам, друзья и всем тем, кто в теме! Спасибо!
Автор: Makeman
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/87747
Ссылки в тексте:
[1] прошлых статей: http://habrahabr.ru/users/makeman/topics/
[2] принципа прямых инжекций: http://habrahabr.ru/post/254373/
[3] этой странице: http://makeloft.by/ru/tools
[4] исходные коды реальных проектов: http://makeloft.by/ru/works
[5] Источник: http://habrahabr.ru/post/254731/
Нажмите здесь для печати.