В статье представлены обобщённые подходы применения конвертеров значений
при написании XAML
-кода.
IValueConverter
Data Binding
XAML
WPF
UWP
Xamarin Forms
UI
SwitchConverter
KeyToValueConverter
InlineConverter
AggregateConverter
ResourceDictionary
Конвертеры значений
совместно с механизмом привязки данных являются важными компонентами в разработке пользовательских интерфейсов на основе XAML
. Конвертеры значений подразумевают наличие логики, помещенной в отдельный класс, реализующий интерфейс IValueConverter
. Обычно имя класса отражает функциональное назначение, а сами экземпляры декларируются в разметке.
Switch Converter & Key To Value Converter
На практике многие конвертеры значений имеют тривиальную логику схожую по структуре с тернарным оператором (?:)
или конструкциям if-else
, switch-case-default
. Однако существуют обобщенные шаблоны KeyToValueConverter
и SwitchConverter
, которые позволяют избежать добавления в проект однотипных по структуре классов путём декларирования логических значений и ветвлений непосредственно в разметке.
Концепция
<KeyToValueConverter
Key="KeyForMatching"
Value="ValueIfKeyMatched"
ByDefault="ValueIfKeyNotMatched" />
<SwitchConverter
ByDefault="ValueZ">
<Case
Key="KeyA"
Value="ValueA" />
<Case
Key="KeyB"
Value="ValueB" />
<Case
Key="KeyC"
Value="ValueC" />
</SwitchConverter>
Применение
<KeyToValueConverter
x:Key="TrueToVisibleConverter"
Key="True"
Value="Visible"
ByDefault="Collapsed" />
<ProgressBar
Visibility="{Binding IsBusy, Converter={StaticResource TrueToVisibleConverter}}" />
<SwitchConverter
x:Key="CodeToBackgroundConverter"
ByDefault="White">
<Case
Key="R"
Value="Red" />
<Case
Key="G"
Value="Green" />
<Case
Key="B"
Value="Blue" />
</SwitchConverter>
<Control
Background="{Binding Code, Converter={StaticResource CodeToBackgroundConverter}}" />
KeyToValueConverter
- проверяет входное значение на соответствие со значением из свойства Key
, если соответствие выполнено, то в качестве выходного берётся значение из свойства Value
, в противном случае из свойства ByDefault
.
SwitchConverter
- выполняет поиск первого соответствующего Case
из списка по его ключу из свойства Key
, если соответствующий Case
найден, то берётся заданное в нём значение из свойства Value
, в противном случае из свойства ByDefault
, заданного в самом конвертере значений.
Если свойство Value
или ByDefault
явно не задано, но выполняется соответствующее ему условие, то в таком случае происходит обыкновенный проброс входного значения в качестве выходного.
Также у KeyToValueConverter
иногда полезно задавать ключ в ConverterParameter
через свойство KeySource
<KeyToValueConverter
x:Key="EqualsToHiddenConverter"
KeySource="ConverterParameter"
Value="Collapsed"
ByDefault="Visible" />
<Control
Visiblity="{Binding Items.Count, ConverterParameter=0, Converter={StaticResource EqualsToHiddenConverter}}" />
<TextBlock
Visiblity="{Binding Text, ConverterParameter='Hide Me', Converter={StaticResource EqualsToHiddenConverter}}" />
Для особых случаев у KeySource
возможны четыре режима работы:
Manual
(by default
) - в качестве ключа при проверке соответствия всегда используется значение из свойства Key
либо выполняется проброс значения, когда оно не задано
ConverterParameter
- в качестве ключа всегда используется значение из свойства привязки ConverterParameter
либо выполняется проброс значения, когда оно не задано
PreferManual
- если manual Key
явно задан, то он имеет приоритет перед ConverterParameter
PreferConverterParameter
- если ConverterParameter
явно задан, то он имеет приоритет перед manual Key
Стоит также отметить, что у SwitchConverter
помимо обычных Case
доступны также TypedCase
, основное отличие которых в проверке на соответствие по типу значения
<SwitchConverter
ByDefault="Undefined value">
<TypedCase
Key="system:String"
Value="String value" />
<Case
Key="0"
Value="Zero" />
<Case
Key="1"
Value="One" />
<TypedCase
Key="system:Int32"
Value="Int32 value" />
</SwitchConverter>
Иногда возникает необходимость продебажить работу конвертера значений. Для этой цели у SwitchConverter
предусмотрено свойство DiagnosticKey
, если оно задано, то при срабатывании привязки данных в Trace
будут выводится диагностические сообщения следующего формата
var diagnosticMessage = matchedCase.Is()
? $"{DiagnosticKey}: '{matchedValue}' matched by key '{matchedCase.Key}' for '{value}' and converted to '{convertedValue}'"
: $"{DiagnosticKey}: The default value '{matchedValue}' matched for '{value}' and converted to '{convertedValue}'";
Trace.WriteLine(diagnosticMessage);
<SwitchConverter
DiagnosticKey="UniqDiagnosticKey"
x:Key="CodeToBackgroundConverter"
ByDefault="White">
...
</SwitchConverter>
Dependency Value Converter
Также свойства Key
, Value
и ByDefault
полезно объявлять в качестве свойств зависимости
(Dependency Properties
), то есть наследовать конвертеры и Case
s от класса DependencyObject
. Хотя конвертеры значений обычно не являются элементами визуального дерева, что отчасти ограничивает работу механизма привязки данных, тем не менее остаётся возможность производить привязку к статическим ресурсам или наследникам класса Binding
, например
<KeyToValueConverter
Key="AnyKey"
Value="{Binding MatchedValue, Source={StaticResource AnyResource}}"
ByDefault="{Binding DefaultValue, Source={StaticResource AnyResource}}" />
<KeyToValueConverter
Key="AnyKey"
Value="{Localizing MatchedTitle}"
ByDefault="{Localizing DefaultTitle}" />
Inline Converter
Встраиваемый конвертер позволят перенести логику по преобразованию значений из отдельного класса, реализующего интерфейс IValueConverter
, в code-behind
класс конкретного представления на основе событийной модели.
Это позволяет получить доступ к представлению и его отдельным визуальным элементам из логики конвертирования при реализации сложных сценариев, которые затруднительно реализовать при классическом подходе.
Для этого необходимо добавить декларацию конвертера в разметку, а code-behind
классе определить обработчики для соответствующих событий Converting
и ConvertingBack
<Grid>
<Grid.Resources>
<InlineConverter
x:Key="ComplexInlineConverter"
Converting="InlineConverter_OnConverting"
ConvertingBack="InlineConverter_OnConverting" />
</Grid.Resources>
<TextBlock
Text="{Binding Number, Converter={StaticResource InlineConverter}}"/>
</Grid>
private void InlineConverter_OnConverting(object sender, ConverterEventArgs e)
{
// e.Value - access to input value
// this.DataContext - access to Data Context or another properties of the view
// access to child visual elements of this root view
e.ConvertedValue = // set output value
$"DataContext: {DataContext}, Converter Value: {e.Value}";
}
private void InlineConverter_OnConvertingBack(object sender, ConverterEventArgs e)
{
// ...
}
Aggregate Converter
Агрегирующий конвертер предназначен для объединения конвертеров в цепочки, при этом преобразование значения происходит последовательно в порядке декларирования вложенных конвертеров.
<AggregateConverter>
<StepAConverter />
<StepBConverter />
<StepCConverter />
</AggregateConverter>
App.xaml
Обобщённые конвертеры значений полезно помещать в отдельный Resource Dictionary, а затем мержить их в качестве глобальных ресурсов в файл App.xaml. Это позволяет переиспользовать конвертеры значений в различных представлениях без их повторного декларирования.
<Application
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Any.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="AppConverters.xaml" />
...
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Ace Framework
Примеры реализации представленных конвертеров можно найти в библиотеке Ace Framework
gitlab bitbucket
С благодарностью за внимание и интерес!