WPF: конвертеры как MarkupExtension

в 16:59, , рубрики: .net, MarkupExtensions, wpf, XAML, Конвертеры, метки: , , ,

Конверторы являются одной из важнейшей особенностью механизма привязки в WPF. Они позволяют управлять тем, как источник привязки будет представлен в UI. В данной статье я покажу, как немного упростить использование конвертеров в XAML коде.

Рассмотрим простейший пример:

  internal class DateTimeToString : IValueConverter
  {
    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
      DateTime date = (DateTime)value;
      return date.ToShortDateString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
      return null;
    }
  }

* This source code was highlighted with Source Code Highlighter.

Тут всё просто: конвертер на вход получает значение типа DateTime и конвертирует его в строку. Обратная конвертация не предусмотрена.

Используется конвертер следующим образом:

public class DateTimeToString : ConvertorBase<DateTimeToString>
  {
    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
      DateTime date = (DateTime)value;
      return date.ToShortDateString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
      return null;
    }
  }

Здесь тоже нет ничего сложного, однако, минус такого подхода – для каждого конвертера нужно создавать соответсвующий ресурс. Причём нужно либо делать это в глобальном словаре ресурсов, либо нужно в каждом XAML файле создавать свои ресурсы для всех используемых конвертеров, что несколько напрягает. Что ещё хуже, подобное использование приводит к тому, что при каждом использовании конвертера пораждается новый экземпляр, что может сильно увеличить количество потребляемой памяти. После некоторых поисков на просторах интернета, здесь я нашёл альтернативное решение.

Вначале модифицируем сам конвертер:

  [MarkupExtensionReturnType(typeof(IValueConverter))]
  public class NumberToStringConverterExtension: MarkupExtension, IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
      DateTime date = (DateTime)value;
      return date.ToShortDateString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
      throw new NotImplementedException();
    }

    #region MarkupExtension members

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
      if (_converter == null)
        _converter = new NumberToStringConverterExtension();
      return _converter;
    }

    private static NumberToStringConverterExtension _converter = null;

    #endregion
  }

После такой модификации всё, что нужно для его использования в XAML, это:

<Label Content="{Binding Path=Date, Converter={converters:DateConverter}}" />

Естественно. нужно не забыть добавить соотвествуещее пространство имён «converters».
Приятным бонусом будет то, что при наборе отображается список доступных конвертеров:
WPF: конвертеры как MarkupExtension
Давайте не будем останавливаться на достигнутом, а для того, чтобы максимально упростить написание новых конвертеров, введём базовый класс:

  public abstract class ConvertorBase<T> : MarkupExtension, IValueConverter
    where T : class, new()
  {
    /// <summary>
    /// Must be implemented in inheritor.
    /// </summary>
    public abstract object Convert(object value, Type targetType, object parameter,
      CultureInfo culture);

    /// <summary>
    /// Override if needed.
    /// </summary>
    public virtual object ConvertBack(object value, Type targetType, object parameter,
      CultureInfo culture)
    {
      throw new NotImplementedException();
    }

    #region MarkupExtension members

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
      if (_converter == null)
        _converter = new T();
      return _converter;
    }

    private static T _converter = null;

    #endregion
  }
}

Теперь унаследуем от него наш DateConverter и имплементируем в нём метод Convert. Окончательная версия будет выглядеть так:

public class DateTimeToString : ConvertorBase<DateTimeToString>
  {
    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
      DateTime date = (DateTime)value;
      return date.ToShortDateString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
      return null;
    }
  }

XAML код остаётся идентичен второму примеру.

Итак, мы получили возможность использовать упрощённый синтаксис в XAML разметке, а приятным бонусом к синтаксису идёт использование каждого конвертера как синглтон.
Если же вам действительно каждый раз будет нужен новый конвертер, то необходимо лишь немного изменить базовый класс.

P.S. Проекты с примерами можно скачать здесь.
P.P.S. Если кто подскажет, как можно нормально вставить примеры кода в статью, буду очень признателен.

Автор: Seekeer

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


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