Фишки XAML-разработчика: условный конвертер
Switch Converter заслуживает особенного внимания. Простой и удобный он обладает поразительной универсальностью. На его основе легко построить множество распространённых типов конвертеров без декларирования новых классов и не только… Не верится — добро пожаловать!
В процессе анализа средних и крупных проектов можно выявить важную закономерность — значительная часть классов-конвертеров, по сути, содержит логику эквивалентную конструкциям if-else, а также же switch-case-default. Сразу возникает желание свести всё к общему знаменателю, дабы не создавать классов-близнецов, и делается это не так уж и сложно.
using System;
using System.Collections.Generic;
using System.Windows.Data;
namespace Aero.Converters.Patterns
{
public class CaseSet : List
{
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; }
}
}
using System.Windows.Data;
namespace Aero.Converters.Patterns
{
public interface ICompositeConverter : IValueConverter
{
IValueConverter PostConverter { get; set; }
object PostConverterParameter { get; set; }
}
}
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), используется сквозная конвертация — сравните оба примера ниже.
Number==0 => out: Zero
Number==1 => out: One
Number==2 => out: 2
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, где ключом к значению является тип объекта.
Примечательно, что нет необходимости в доступе из C# кода к ресурсам XAML, да и не нужно создавать отдельный класс для реализации селектора шаблонов, поскольку вся логика содержится в разметке.
Надеюсь, вам понравился этот материал.
Примеры использования различных конвертеров доступны с библиотекой Aero Framework.
Спасибо за ваше внимание!
P.S. Предыдущая статья о композитных конвертерах
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.