Фишки XAML-разработчика: композитные конвертеры

в 17:59, , рубрики: .net, C#, mvvm, silverlight, windows phone, wpf, XAML, ненормальное программирование, Программирование, Промышленное программирование

Статья будет посвящена простому, но эффективному паттерну — Composite Converter [составной конвертер].
image
Встречаются ситуации, когда уже есть несколько конвертеров, но возникает необходимость в создании нового, который является логической композицией имеющихся. Чтобы не создавать классов, которые отчасти дублируют функционал, можно поступить предложенным ниже образом. Обратите внимание на свойства PostConverter и PostConverterParameter.

using System.Windows.Data;

namespace Aero.Converters.Patterns
{
    public interface ICompositeConverter : IValueConverter
    {
        IValueConverter PostConverter { get; set; }
        object PostConverterParameter { get; set; }
    }
}

Inline Converter
using System;
using System.Globalization;
using System.Windows.Data;
using Aero.Converters.Patterns;

namespace Aero.Converters
{
    public class InlineConverter : IInlineConverter, ICompositeConverter
    {
        public IValueConverter PostConverter { get; set; }
        public object PostConverterParameter { get; set; }
        public event EventHandler<ConverterEventArgs> Converting;
        public event EventHandler<ConverterEventArgs> ConvertingBack;

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var args = new ConverterEventArgs(value, targetType, parameter, culture);
            var handler = Converting;
            if (handler != null) handler(this, args);
            return PostConverter == null
                ? args.ConvertedValue
                : PostConverter.Convert(args.ConvertedValue, targetType, PostConverterParameter, culture);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var args = new ConverterEventArgs(value, targetType, parameter, culture);
            var handler = ConvertingBack;
            if (handler != null) handler(this, args);
            return PostConverter == null
                ? args.ConvertedValue
                : PostConverter.ConvertBack(args.ConvertedValue, targetType, PostConverterParameter, culture);
        }
    }
}

Switch Converter

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

Это позволит объединять конвертеры в логические цепочки различной длины и запросто строить новые на базе существующих.

<Grid.Resources>
    <BooleanConverter x:Key="YesNoConverter" OnTrue="Yes" OnFalse="No"/>
    
    <SwitchConverter x:Key="CompositeSwitchConverter" PostConverter="{StaticResource YesNoConverter}">
        <Case Key="0" Value="False"/>
        <Case Key="1" Value="True"/>
    </SwitchConverter>
</Grid.Resources>

<TextBlock Text="{Binding Number, Converter={StaticResource CompositeSwitchConverter}}"/>

Number == 1 => out: Yes
Number == 0 => out: No

Гениальное просто! Пользуйтесь на здоровье!
Живые примеры доступны с библиотекой Aero Framework.

Спасибо!

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

Автор: Makeman

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js