В первой своей статье, посвящённой конвертерам, я описал способ использование конвертеров в качестве расширения разметки. Продолжая тему конвертеров, хотелось бы рассказать о параметрах конвертера.
Зачастую, для того, чтобы правильно конвертировать источник привязки для представления в UI одного объекта недостаточно, необходимо передать в конвертер какой-то параметр, указываюший, как именно объект должен быть сконвертирован. Вернувшись к примеру даты, допустим, что у нас в программе дата может быть представлена в нескольких календарях и для правильной конвертации необходимо сообщить конвертеру, дату какого календаря представляет источник привязки, это можно сделать следующим образом:
<Label Content="{Binding Path=Date, Converter={StaticResource dateConverter}, ConverterParameter='Gregorian'}" />
Аналогично, в случае использования конвертера как MarkupExtension:
<Label Content="{Binding Path=Date, Converter={converters:DateTimeToString}, ConverterParameter='Gregorian'}" />
Когда этот конвертер вызовется, в его входном параметре “parameter” будет находиться значение “Gregorian” и мы уже будем знать, что дата в грегорианском календаре:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DateTime date = (DateTime)value;
string calendar = (string)parameter;
if (calendar == "Gregorian")
return DateTimeHelpers.GregorianToString(calendar);
else if (calendar == "Gregorian")
return DateTimeHelpers.GregorianToString(calendar);
}
Несколько параметров
Это конечно замечательно, но что, если в конвертер необходимо передать не один, а два или больше параметров? Стандартными средствами это сделать не получится. Но, как подсказал, читатель urrri это легко можно реализовать, в случае, если конвертер является одновременно и 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();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
Посли модификации мы возвращаем не статический экземпляр нашего класса, а текущий.
Допустим, что нашему конвертеру нужны два параметра: дата и формат выдачи. Изменим конвертер:
public class DateTimeToString : ConvertorBase<DateTimeToString>
{
/// Format for converting DateTime to string.
/// </summary>
public string Format { set; private get; }
/// <summary>
/// Date of what calendar current instance is representing.
/// </summary>
public string Calendar { set; private get; }
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DateTime date = (DateTime)value;
if (calendar == "Gregorian")
return DateTimeHelpers.GregorianToString(date, Format);
else if (calendar == "Gregorian")
return DateTimeHelpers.GregorianToString(date, Format);
}
}
Пример использования конвертера:
<Label Content="{Binding Path=Date, Converter={converters:DateTimeToString Calendar='Gregorian', Format ='Today is {0}'}}" />
Если проект перестроить после того, как добавлен конвертер, то редактор нам будет предлагать варианты свойств:
Хочу заметить, что если какие-то из параметров не обязательны – их можно опустить:
<Label Content="{Binding Path=Date, Converter={converters:DateTimeToString}" />
При вызове конвертера соответсвующие параметры будут пусты. Естественно, подобные ситуации надо предусматривать. Допустим, что формат нам нужен обязателен, а календарь – дополнительный параметр, по умолчанию будем считать, что календарь грегорианский. Давайте добавим в конвертер проверку входных параметров:
[ValueConversion(typeof(DateTime), typeof(String))]
public class DateTimeToString : ConvertorBase<DateTimeToString>
{
/// <summary>
/// Format string.
/// </summary>
public string Format { set; private get; }
/// <summary>
/// Date of what calendar current instance is representing.
/// </summary>
public string Calendar
{
set
{
_calendar = value;
}
private get
{
return _calendar;
}
}
/// <summary>
/// Default calendar.
/// </summary>
private string _calendar = "Gregorian";
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DateTime date = (DateTime)value;
Debug.Assert(date != null, "Date is missing.");
Debug.Assert(Format != null, "Format is missing.");
if (Calendar == "Gregorian")
return DateTimeHelpers.GregorianToString(date, Format);
else if (Calendar == "Gregorian")
return DateTimeHelpers.GregorianToString(date, Format);
}
}
Теперь в случае передачи в конвертер не даты, или если не указан формат у нас будет возникать вот такое симпатичное окошко, которое может сильно помочь при отладке:
Я так же добавил к классу аттрибут ValueConversion(typeof(DateTime), typeof(String)). Он указывает, что данный конвертер конвертирует DateTime в строку. Снабжать все свои конвертерами таким аттрибутом – хорошая практика, так как потом при взгляде на заголовок класса сразу становится понятно, что и во что он конвертирует.
В итоге мы изучили способ передавать в конвертер любое число параметров, причём параметры эти будут именнованные. Единственный недостаток подобного метода заключается в том, что для каждого элемента, в котором используется конвертера будет создаваться свой собственный экземпляр конвертера. Так что, возможно, способ будет плохо работать на коллекции с большим количеством элементов.
Автор: Seekeer