Фишки XAML-разработчика: условный конвертер

от автора

Switch Converter заслуживает особенного внимания. Простой и удобный он обладает поразительной универсальностью. На его основе легко построить множество распространённых типов конвертеров без декларирования новых классов и не только… Не верится — добро пожаловать!
image
В процессе анализа средних и крупных проектов можно выявить важную закономерность — значительная часть классов-конвертеров, по сути, содержит логику эквивалентную конструкциям if-else, а также же switch-case-default. Сразу возникает желание свести всё к общему знаменателю, дабы не создавать классов-близнецов, и делается это не так уж и сложно.

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; }     } } 

Применяется конвертер довольно просто, а в случаях, когда не задано значение по умолчанию (default), используется сквозная конвертация — сравните оба примера ниже.

<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 

Он легко работает с числами, перечислениями, строками, булевыми значениями, а также поддерживает цепочную композицию.

Более того имеет ещё один значимый бонус. В WPF существует концепция Template Selectors, которая не поддерживается, к примеру, на Windows Phone Silverlight. Если вам доводилось ею пользоваться, то, наверняка, вы отметили не очень-то удобный момент — нужно создавать C#-класс, откуда не слишком красивым образом получать доступ к XAML-ресурсам. Взгляните на пример с MSDN.

В действительности же задачу селекторов шаблонов в большинстве реальных случаев очень легко свести к применению обычных конвертеров, а единственное преимущество Template Selector в дополнительном предоставлении доступа к контейнеру данных, к которому шаблон применяется.

Специально для таких сценариев Switch Converter поддерживает особый режим работы Type Mode, где ключом к значению является тип объекта.

<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> 

Примечательно, что нет необходимости в доступе из C# кода к ресурсам XAML, да и не нужно создавать отдельный класс для реализации селектора шаблонов, поскольку вся логика содержится в разметке.

Надеюсь, вам понравился этот материал.
Примеры использования различных конвертеров доступны с библиотекой Aero Framework.

Спасибо за ваше внимание!

P.S. Предыдущая статья о композитных конвертерах

После прочтения статей, возникает ли у Вас желание ближе познакомиться с библиотекой Aero Framework?

Никто ещё не голосовал. Воздержавшихся нет.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

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


Комментарии

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

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