📜 ⬆️ ⬇️

XAML Developer Chips: Conditional Converter

Switch Converter deserves special attention. Simple and convenient, it has an amazing versatility. On its basis it is easy to build many common types of converters without declaring new classes and not only ... I can not believe it - welcome!
image
In the process of analyzing medium and large projects, it is possible to identify an important pattern - a significant part of the converter classes, in fact, contains logic equivalent to the if-else constructions , as well as the switch-case-default . Immediately there is a desire to reduce everything to a common denominator in order not to create twin classes, and this is not so difficult.

ISwitchConverter
using System; using System.Collections.Generic; using System.Windows.Data; namespace Aero.Converters.Patterns { public class CaseSet : List<ICase> { public static readonly object UndefinedObject = new object(); } public interface ICase { object Key { get; set; } object Value { get; set; } Type KeyType { get; set; } } public interface ISwitchConverter : IValueConverter { CaseSet Cases { get; } object Default { get; set; } bool TypeMode { get; set; } } } 


IcompositeConverter
 using System.Windows.Data; namespace Aero.Converters.Patterns { public interface ICompositeConverter : IValueConverter { IValueConverter PostConverter { get; set; } object PostConverterParameter { get; set; } } } 


SwitchConverter
 using System; using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Data; using System.Windows.Markup; using Aero.Converters.Patterns; namespace Aero.Converters { [ContentProperty("Cases")] public class SwitchConverter : DependencyObject, ISwitchConverter, ICompositeConverter { public static readonly DependencyProperty DefaultProperty = DependencyProperty.Register( "Default", typeof(object), typeof(SwitchConverter), new PropertyMetadata(CaseSet.UndefinedObject)); public SwitchConverter() { Cases = new CaseSet(); } public object Default { get { return GetValue(DefaultProperty); } set { SetValue(DefaultProperty, value); } } public CaseSet Cases { get; private set; } public bool TypeMode { get; set; } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (TypeMode) value = value == null ? null : value.GetType(); var pair = Cases.FirstOrDefault(p => Equals(p.Key, value) || SafeCompareAsStrings(p.Key, value)); var result = pair == null ? Default : pair.Value; value = result == CaseSet.UndefinedObject ? value : result; return PostConverter == null ? value : PostConverter.Convert(value, targetType, PostConverterParameter, culture); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (TypeMode) value = value == null ? null : value.GetType(); var pair = Cases.FirstOrDefault(p => Equals(p.Value, value) || SafeCompareAsStrings(p.Value, value)); value = pair == null ? Default : pair.Key; return PostConverter == null ? value : PostConverter.ConvertBack(value, targetType, PostConverterParameter, culture); } private static bool SafeCompareAsStrings(object a, object b) { if (a == null && b == null) return true; if (a == null || b == null) return false; return string.Compare(a.ToString(), b.ToString(), StringComparison.OrdinalIgnoreCase) == 0; } public IValueConverter PostConverter { get; set; } public object PostConverterParameter { get; set; } } } 


Using the converter is quite simple, and in cases where the default value is not specified ( default ), continuous conversion is used - compare both examples below.

 <Grid.Resources> <SwitchConverter x:Key="NumberSwitchConverter"> <Case Key="0" Value="Zero"/> <Case Key="1" Value="One"/> </SwitchConverter> </Grid.Resources> <TextBlock Text="{Binding Number, Converter={StaticResource NumberSwitchConverter}}"/> Number==0 => out: Zero Number==1 => out: One Number==2 => out: 2 

 <Grid.Resources> <SwitchConverter x:Key="NumberSwitchConverter" Default="Hello"> <Case Key="0" Value="Zero"/> <Case Key="1" Value="One"/> </SwitchConverter> </Grid.Resources> <TextBlock Text="{Binding Number, Converter={StaticResource NumberSwitchConverter}}"/> Number==0 => out: Zero Number==1 => out: One Number==2 => out: Hello 

It works easily with numbers, enumerations, strings, booleans, and also supports chain-based composition .
')
Moreover, it has another significant bonus. In WPF, there is the concept of Template Selectors , which is not supported, for example, on Windows Phone Silverlight . If you have used it, then for sure you have marked a not very convenient moment - you need to create a C # class, from which you can not get access to XAML resources in a very beautiful way. Take a look at the MSDN example .

In reality, the task of template selectors in most real cases is very easy to reduce to the use of conventional converters, and the only advantage of the Template Selector is to provide additional access to the data container to which the template is applied.

Especially for such scenarios, Switch Converter supports a special mode of operation, Type Mode , where the key to the value is the type of object.

 <SwitchConverter TypeMode="True" Default="{StaticResource DefaultDataTemplate}" x:Key="InfoConverter"> <Case KeyType="local:Person" Value="{StaticResource PersonDataTemplate}"/> <Case KeyType="local:PersonGroup" Value="{StaticResource PersonGroupDataTemplate}"/> </SwitchConverter> 

 <ListBox ItemsSource="{Binding Items}"> <ListBox.ItemTemplate> <DataTemplate> <ContentControl Template="{Binding Converter={StaticResource TemplateSelectorConverter}}"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> 

It is noteworthy that there is no need to access XAML resources from C # code, and there is no need to create a separate class to implement the template selector, since all the logic is contained in the markup.

Hope you enjoyed this stuff.
Examples of using various converters are available with the Aero Framework library [ backup link ].

Thank you for your attention!

PS Previous article on composite converters

Source: https://habr.com/ru/post/276315/


All Articles