BindableConverter для WPF

от автора

Проблема: 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/