Проблема: WPF классная технология, но местами недоработанная. Например, вот такой код плюнет в вас эксепшином, поскольку ConverterParameter не является наследником DependencyObject’a:
<...Text={Binding SourceProperty, Converter={StaticResource SomethingToSomethingElseConverter} ConverterParameter={Binding AnotherSourceProperty}} />
Собственно, это и проблема. А ниже ее решение.
В принципе, решить проблему можно двумя путями: первое DependencyProperty.Register(..), второе — .RegisterAttached(..). Разница в том, что второй вариант и концептуально, и архитектурно ущербный. Поэтому вот так:
using System; using System.Globalization; using System.Windows; using System.Windows.Data; namespace BindableConverter { [ValueConversion(typeof(object), typeof(object))] public class BindableConverterBase : DependencyObject, IValueConverter, IMultiValueConverter { #region BindableParameters #region BindableParameter1 public object BindableParameter1 { get { return GetValue(BindableParameter1Property); } set { SetValue(BindableParameter1Property, value); } } public static readonly DependencyProperty BindableParameter1Property = DependencyProperty.Register( nameof(BindableParameter1), typeof(object), typeof(BindableConverterBase), new PropertyMetadata(String.Empty) ); #endregion #region BindableParameter2 public object BindableParameter2 { get { return GetValue(BindableParameter2Property); } set { SetValue(BindableParameter2Property, value); } } public static readonly DependencyProperty BindableParameter2Property = DependencyProperty.Register( nameof(BindableParameter2), typeof(object), typeof(BindableConverterBase), new PropertyMetadata(String.Empty) ); #endregion #region BindableParameter3 public object BindableParameter3 { get { return GetValue(BindableParameter3Property); } set { SetValue(BindableParameter3Property, value); } } public static readonly DependencyProperty BindableParameter3Property = DependencyProperty.Register( nameof(BindableParameter3), typeof(object), typeof(BindableConverterBase), new PropertyMetadata(String.Empty) ); #endregion #endregion #region IValueConverter public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } #endregion #region IMultiValueConverter public virtual object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } public virtual object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } #endregion } }
Поскольку класс реализовал оба интерфейс, то будет работать и с обычным биндингом, и с MultiBinding. Собственно, осталось только унаследоваться от BindableConverter и переопределить нужный (нужные, что редкость) методы.
В использовании в XAML есть один принципиальный момент. Наивная попытка типа:
<Window.Resources> <local:NameAndAgeToVladimirPutinConverter x:Key="NameAndAgeToVladimirPutin" BindableParameter1="{Binding FirstName}" BindableParameter2="{Binding Age}" /> </Window.Resources>
… приведет к тому, что оба параметра будут равны дефолтному значению. Всегда — независимо от того, где именно в ресурсах вы объявите ссылку на конвертор.
Признаюсь, окончательного понимания почему так происходит у меня нет. В общих словах смысл в том, что конвертор находится вне Logical\VisualTree UI-элементов, поэтому привязаться ему просто. Во всяком случае, именно такое объяснение я раскопал на StackOverflow. Решение проблемы выглядит вот так:
<bc:BindingProxy x:Key="BindingProxy" Data="{Binding}" /> <local:NameAndAgeToVladimirPutinConverter x:Key="NameAndAgeToVladimirPutin" BindableParameter1="{Binding Source={StaticResource BindingProxy}, Path=Data.FirstName}" BindableParameter2="{Binding Source={StaticResource BindingProxy}, Path=Data.Age}"/> ... <TextBlock Grid.Row="0" Text="{Binding Name, Converter={StaticResource NameAndAgeToVladimirPutin}}" />
using System.Windows; namespace BindableConverter { public class BindingProxy : Freezable { protected override Freezable CreateInstanceCore() { return new BindingProxy(); } /// <summary> /// Binding data. /// </summary> public object Data { get { return GetValue(DataProperty); } set { SetValue(DataProperty, value); } } public static readonly DependencyProperty DataProperty = DependencyProperty.Register( nameof(Data), typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null) ); } }
P.S. В планах есть прикрутить MarkupExtension, чтобы можно было вытворять что-то типа Converter={bc:BindableConverter BindingProxy={StaticResource Proxy}, Parameter1=FirstName, Parameter2=Age}. Если у кого-то есть идеи как это сделать еще
красивее и лаконичнее, то пожалуйста.
Еще очень актуален вопрос о том, почему все таки без BindingProxy не работает.
ссылка на оригинал статьи http://habrahabr.ru/post/275223/
Добавить комментарий