Pull to refresh

WPF: Несколько параметров для конвертера

Reading time 4 min
Views 17K
В первой своей статье, посвящённой конвертерам, я описал способ использование конвертеров в качестве расширения разметки. Продолжая тему конвертеров, хотелось бы рассказать о параметрах конвертера.

Зачастую, для того, чтобы правильно конвертировать источник привязки для представления в 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 в строку. Снабжать все свои конвертерами таким аттрибутом – хорошая практика, так как потом при взгляде на заголовок класса сразу становится понятно, что и во что он конвертирует.

В итоге мы изучили способ передавать в конвертер любое число параметров, причём параметры эти будут именнованные. Единственный недостаток подобного метода заключается в том, что для каждого элемента, в котором используется конвертера будет создаваться свой собственный экземпляр конвертера. Так что, возможно, способ будет плохо работать на коллекции с большим количеством элементов.
Tags:
Hubs:
+11
Comments 15
Comments Comments 15

Articles