Для сложных случаев связывания данных, когда свойство интерфейсного элемента зависит от нескольких параметров, 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)И решение было найдено. Я завел абстрактный класс, под названием UniConverter, который использовался в качестве базового для всех конвертеров и реализовывал интерфейс IMultiValueConverter.
{
return AvailableHeight * (OriginalHeight / TotalHeight);
}
В качестве параметра конвертера из 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 с двумя параметрами.
Связанные материалы
- Конвертер для получения пути к файлу из папку и имени файла
- Вывод чисел с существительными в правильном падеже
P.S. Все примеры взяты из исходных кодов программы для управления портфелем проектов ProjectProfiler.
Комментариев нет:
Отправить комментарий