Search code examples
c#wpfdata-bindingmarkup-extensions

How do I resolve the value of a databinding inside a MarkupExtension?


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?


Solution

  • 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));
    }