Pull to refresh

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

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

Рассмотрим простейший пример:
public class DateConverter : 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)
	{
		return null;
	}
}

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

Используется конвертер следующим образом:
<Window x:Class="TestConvertorMarkup.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:converters="clr-namespace:TestConvertorMarkup.Converters"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <converters:DateConverter x:Shared="false" x:Key="dateConverter"/>
    </Window.Resources>
	
    <Label Content="{Binding Path=Date, Converter={StaticResource dateConverter}}" />
</Window>

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

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

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

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

	private static NumberToStringConverterExtension _converter = null;
}

После такой модификации всё, что нужно для его использования в XAML, это:
<Label Content="{Binding Path=Date, Converter={converters:DateTimeToString}}" />

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

Давайте не будем останавливаться на достигнутом, а для того, чтобы максимально упростить написание новых конвертеров, введём базовый класс:
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. Проекты с примерами можно скачать здесь.
Tags:
Hubs:
+22
Comments 17
Comments Comments 17

Articles