I've made a markup extension for translating strings based on a key. Example
<TextBlock Text="{Translate myKey}" />
Now I want to be able to use nested bindings for providing my keys. Example:
<TextBlock Text="{Translate {Binding KeyFromDataContext}}" />
When I do this I get a System.Windows.Data.Binding object. By calling ProvideValue and passing down the ServiceProvider I can get a BindingExpression:
var binding = Key as Binding;
if (binding == null) {
return null;
}
var bindingExpression = binding.ProvideValue(_serviceProvider) as BindingExpression;
if (bindingExpression == null) {
return null;
}
var bindingKey = bindingExpression.DataItem;
I can get this bindingExpression, but the DataItem property is null. I've tested my binding like this
<TextBlock Text="{Binding KeyFromDataContext}" />
and it works fine.
Any ideas?
The toxvaerd's answer is not universal. It breaks if the original binding already had a converter. Or when writing a converter is not possible.
There's a better solution. We can declare two constructors. The second one accepting BindingBase
will be called by XAML when a binding is used. To resolve the value of the binding, we can declare a private attached property. For this to work we need to know the target element of the markup extension.
There's a catch: when the markup extension is used inside a template, there is no target element (obviously). In this case you are supposed to use return this
in ProvideValue()
- this way the extension will be called again when the template is applied.
public class TranslateExtension : MarkupExtension
{
private readonly BindingBase _binding;
public TranslateExtension(BindingBase binding)
{
_binding = binding;
}
public TranslateExtension(string key)
{
Key = key;
}
[ConstructorArgument("key")]
public string Key { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (_binding != null)
{
var pvt = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var target = pvt.TargetObject as DependencyObject;
// if we are inside a template, WPF will call us again when it is applied
if (target == null)
return this;
BindingOperations.SetBinding(target, ValueProperty, _binding);
Key = (string)target.GetValue(ValueProperty);
BindingOperations.ClearBinding(target, ValueProperty);
}
// now do the translation using Key
return ...;
}
private static readonly DependencyProperty ValueProperty =
DependencyProperty.RegisterAttached("Value", typeof(string), typeof(TranslateExtension));
}
It is not possible to get the value of a binding. You're not supposed to be even trying to do this. WPF uses some fancy reflection to resolve the bindings and trust me - you do not wan't to start trying that yourself.
Anyway with that in mind, this is what I ended up doing, which actually is a nice solution:
I made a TranslateConverter
that took care of the translation:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var key = value as string ?? parameter as string;
if (key != null)
{
// Do translation based on the key
}
return null;
}
Then in my TranslateExtension
I simply do this:
var binding = Key as Binding ?? new Binding{Mode = BindingMode.OneWay};
binding.Converter = new TranslateConverter(_targetObject, _targetProperty, Dictionary, Converter);
binding.ConverterParameter = Key is Binding ? null : Key as string;
return binding.ProvideValue(serviceProvider);
This way a binding is resolved by WPF and is passed to the converter as value, while a simple text-key is passed to the converter as a paramter.
_targetObject
and _targetProperty
are obtained from the ServiceProvider.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With