Проблема: WPF классная технология, но местами недоработанная. Например, вот такой код выплюнет не помню точно какой Exception, поскольку ConverterParameter не является наследником DependencyObject'a:
Собственно, это и проблема. А ниже ее решение.
Обновление от 18.01.16:
1) Если базовый класс конвертера унаследовать от Freezable, то прокси просто не нужен. В таком случае конвертер работает в точности как работал бы обычный:
… пожалуйста, учтите это при чтении!
В принципе, решить проблему можно двумя путями: первое DependencyProperty.Register(..), второе — .RegisterAttached(..). Разница в том, что второй вариант и концептуально, и архитектурно ущербный. Поэтому вот так:
Поскольку класс реализовал оба интерфейса, то будет работать и с обычным Binding'ом, и с MultiBinding. Собственно, осталось только унаследоваться от BindableConverter, переопределив нужный (или нужные, что редкость) методы.
В использовании в XAML есть один принципиальный момент. Наивная попытка типа:
… приведет к тому, что оба параметра будут равны дефолтному значению. Всегда — независимо от того, где именно в ресурсах вы объявите ссылку на конвертор.
Признаюсь, окончательного понимания почему так происходит, у меня нет. В общих словах смысл в том, что конвертор находится вне Logical\VisualTree UI-элементов, поэтому привязаться ему просто не к кому. Во всяком случае, именно такое объяснение я раскопал на StackOverflow. Решение проблемы выглядит вот так:
P.S. В планах есть прикрутить MarkupExtension, чтобы можно было вытворять что-то типа Converter={bc:BindableConverter BindingProxy={StaticResource Proxy}, Parameter1=FirstName, Parameter2=Age}. Если у кого-то есть идеи как это сделать еще красивее и лаконичнее, то пожалуйста.
Также очень актуален вопрос о том, почему все таки без BindingProxy не работает.
<...Text={Binding SourceProperty, Converter={StaticResource SomethingToSomethingElseConverter} ConverterParameter={Binding AnotherSourceProperty}} />
Собственно, это и проблема. А ниже ее решение.
Обновление от 18.01.16:
1) Если базовый класс конвертера унаследовать от Freezable, то прокси просто не нужен. В таком случае конвертер работает в точности как работал бы обычный:
<что-то.Resources>
<converters:такой-то_конвертер x:Key="такой-то_ключ" BindingParameter1="{Binding такое-то_свойство}" />
</что-то.Resources>
… пожалуйста, учтите это при чтении!
В принципе, решить проблему можно двумя путями: первое 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
}
}
Поскольку класс реализовал оба интерфейса, то будет работать и с обычным Binding'ом, и с 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 не работает.