понедельник, 12 сентября 2011 г.

Упрощаем множественные привязки к данным (MultiBinding)

Для сложных случаев связывания данных, когда свойство интерфейсного элемента зависит от нескольких параметров, WPF предлагает замечательный способ – множественная привязка к данными с использованием класса MultiBinding. Функция, вычисляющая значение свойства, оформляется в виде класса, реализующего интерфейс IMultiValueConverter. Однако необходимость частой реализации множественной привязки быстро показывает неудобность этого способа.
Реализовав несколько таких конвертеров примерно таким образом:
/// <summary>
/// вычисляет высоту многоугольника многоугольника
/// </summary>
class PolygonHeightConverter : IMultiValueConverter
{
/// <summary>
/// параметры
/// 1 полная высота всего графика в исходных единицах
/// 2 высота вычисляемого графика в исходных единицах
/// 3 фактическая высота холста
/// </summary>
/// <param name="values"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Double AllHeight = (Double)values[0];
Double MyOriginalHeight = (Double)values[1];
Double AllAvailableHeight = (Double)values[2];

return AllAvailableHeight * (MyOriginalHeight / AllHeight);
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}

}
Я невольно задумался – а нельзя ли использовать встроенный в язык контроль типов данных?
Почему я должен писать каждый раз такие громоздкие конструкции в случае, когда всего лишь нужно вызвать функцию:

double GetPoligonHeight(double AvailableHeight, double OriginalHeight, double TotalHeight)
{
return AvailableHeight * (OriginalHeight / TotalHeight);
}
И решение было найдено. Я завел абстрактный класс, под названием UniConverter, который использовался в качестве базового для всех конвертеров и реализовывал интерфейс IMultiValueConverter.
В качестве параметра конвертера из XAML в него передается имя функции наследного класса, которую следует вызвать для конвертации. Вызов осуществляется через Reflection.
Таким образом, упомянутый выше конвертер будет выглядеть как-то так:
/// <summary>
/// вычисляет высоту многоугольника многоугольника
/// </summary>
class PolygonHeightConverter : UniConverter
{
public double GetPoligonHeight(double AvailableHeight, double OriginalHeight, double TotalHeight)
{
return AvailableHeight * (OriginalHeight / TotalHeight);
}
}








Согласитесь, получается гораздо лаконичнее и изящнее первого варианта. В качестве дополнительной возможности получаем еще одно удобство – теперь в одном классе конвертера можно помещать несколько методов для конвертации! Это дает возможность группировать методы по смыслу и удобно использовать один из другого.
Ну, и напоследок – класс UniConverter, который дает возможность реализовываться множественные привязки к данным гораздо проще.

    /// <summary>
    /// базовый класс конвертера данных
    /// реализует интерфейсы конвертирования. В качестве параметра получает имя метода
    /// наследного класса, который вызывается для конвертирования 
    /// с использованием параметров конвертера. 
    /// ВНИМАНИЕ: Не поддерживает обратного конвертирования
    /// </summary>
    public abstract class UniConverter : IValueConverter, IMultiValueConverter
    {
        #region IValueConverter Members
 
        virtual public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
           return Convert(new object[] { value }, targetType, parameter, culture);
        }
 
        virtual public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }
 
        #endregion
 
        #region IMultiValueConverter Members
 
        virtual public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (parameter == null)
                throw new ArgumentNullException("parameter");
 
            string methodName = parameter.ToString();
            if (string.IsNullOrEmpty(methodName))
                throw new ArgumentException("Method name not specified");
            
            if (values.Where(x => x != null).Any(x=> x.GetType().FullName.Contains("MS.Internal.NamedObject")))
            {
                //передаваемый набор содержит неинициализированные значения
                return GetDefaultValue(targetType);
            }
 
            var mi = GetType().GetMethod(methodName);
            if (mi == null)
                throw new MissingMethodException(GetType().FullName, methodName);
 
            return mi.Invoke(this, values);
        }
 
        class def<T>
        {
            public static T get()
            {
                return default(T);
            }
        }
 
        public static object GetDefaultValue(Type targetType)
        {
            var t = typeof(def<>).MakeGenericType(targetType);
            return t.GetMethod("get").Invoke(null, null);
        }
 
        virtual public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }
 
        #endregion
    }




Этот класс теперь реализует не только интерфейс IMultiConverter, но и IValueConverter – это дает возможность объединять в наследных классах простые (с одним параметром) и сложные конвертеры.
В XAML обращение к конвертеру выглядит следующим образом:

<disp:DiagramBuilder x:Key="DiagramBuilder" />





<MultiBinding Converter="{StaticResource DiagramBuilder}" ConverterParameter="Total" >
<Binding Path="Items"/>
<Binding Path="DataContext.ModelView" />
</MultiBinding>

Здесь DiagramBuilder – класс, унаследованный от UniConverter, который имеет метод Total с двумя параметрами.

Связанные материалы



  1. Конвертер для получения пути к файлу из папку и имени файла

  2. Вывод чисел с существительными в правильном падеже


P.S. Все примеры взяты из исходных кодов программы для управления портфелем проектов ProjectProfiler.


 

Комментариев нет:

Отправить комментарий