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

后端 未结 2 1528
名媛妹妹
名媛妹妹 2020-12-06 07:47

I\'ve made a markup extension for translating strings based on a key. Example


Now I want to

相关标签:
2条回答
  • 2020-12-06 08:11

    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.

    0 讨论(0)
  • 2020-12-06 08:19

    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));
    }
    
    0 讨论(0)
提交回复
热议问题