В первой своей статье, посвящённой конвертерам, я описал способ использование конвертеров в качестве расширения разметки. Продолжая тему конвертеров, хотелось бы рассказать о параметрах конвертера.
Зачастую, для того, чтобы правильно конвертировать источник привязки для представления в UI одного объекта недостаточно, необходимо передать в конвертер какой-то параметр, указываюший, как именно объект должен быть сконвертирован. Вернувшись к примеру даты, допустим, что у нас в программе дата может быть представлена в нескольких календарях и для правильной конвертации необходимо сообщить конвертеру, дату какого календаря представляет источник привязки, это можно сделать следующим образом:
Аналогично, в случае использования конвертера как MarkupExtension:
Когда этот конвертер вызовется, в его входном параметре “parameter” будет находиться значение “Gregorian” и мы уже будем знать, что дата в грегорианском календаре:
Это конечно замечательно, но что, если в конвертер необходимо передать не один, а два или больше параметров? Стандартными средствами это сделать не получится. Но, как подсказал, хабраюзер urrri это легко можно реализовать, в случае, если конвертер является одновременно и MarkupExtension. С его разрешения опишу этот способ.
В качестве основы будем использовать код из первой статьи. Для начала немного изменим наш базовый класс:
Посли модификации мы возвращаем не статический экземпляр нашего класса, а текущий.
Допустим, что нашему конвертеру нужны два параметра: дата и формат выдачи. Изменим конвертер:
Пример использования конвертера:
Если проект перестроить после того, как добавлен конвертер, то редактор нам будет предлагать варианты свойств:

Хочу заметить, что если какие-то из параметров не обязательны – их можно опустить:
При вызове конвертера соответсвующие параметры будут пусты. Естественно, подобные ситуации надо предусматривать. Допустим, что формат нам нужен обязателен, а календарь – дополнительный параметр, по умолчанию будем считать, что календарь грегорианский. Давайте добавим в конвертер проверку входных параметров:
Теперь в случае передачи в конвертер не даты, или если не указан формат у нас будет возникать вот такое симпатичное окошко, которое может сильно помочь при отладке:

Я так же добавил к классу аттрибут ValueConversion(typeof(DateTime), typeof(String)). Он указывает, что данный конвертер конвертирует DateTime в строку. Снабжать все свои конвертерами таким аттрибутом – хорошая практика, так как потом при взгляде на заголовок класса сразу становится понятно, что и во что он конвертирует.
В итоге мы изучили способ передавать в конвертер любое число параметров, причём параметры эти будут именнованные. Единственный недостаток подобного метода заключается в том, что для каждого элемента, в котором используется конвертера будет создаваться свой собственный экземпляр конвертера. Так что, возможно, способ будет плохо работать на коллекции с большим количеством элементов.
Зачастую, для того, чтобы правильно конвертировать источник привязки для представления в 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 в строку. Снабжать все свои конвертерами таким аттрибутом – хорошая практика, так как потом при взгляде на заголовок класса сразу становится понятно, что и во что он конвертирует.
В итоге мы изучили способ передавать в конвертер любое число параметров, причём параметры эти будут именнованные. Единственный недостаток подобного метода заключается в том, что для каждого элемента, в котором используется конвертера будет создаваться свой собственный экземпляр конвертера. Так что, возможно, способ будет плохо работать на коллекции с большим количеством элементов.
