如何解析 MarkupExtension 内数据绑定的值?

发布于 2024-07-24 01:46:22 字数 813 浏览 8 评论 0原文

我已经制作了一个标记扩展,用于根据键翻译字符串。 示例

<TextBlock Text="{Translate myKey}" />

现在我希望能够使用嵌套绑定来提供我的密钥。 示例:

<TextBlock Text="{Translate {Binding KeyFromDataContext}}" />

当我这样做时,我得到一个 System.Windows.Data.Binding 对象。 通过调用 ProvideValue 并向下传递 ServiceProvider,我可以获得一个 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;

我可以获得这个绑定表达式,但 DataItem 属性为 null。 我已经像这样测试了我的绑定

<TextBlock Text="{Binding KeyFromDataContext}" />

并且工作正常。

有任何想法吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

吹泡泡o 2024-07-31 01:46:22

toxvaerd 的答案并不通用。 如果原始绑定已经有转换器,它就会中断。 或者当不可能编写转换器时。

有更好的解决方案。 我们可以声明两个构造函数。 当使用绑定时,XAML 将调用第二个接受 BindingBase 的方法。 要解析绑定的值,我们可以声明一个私有附加属性。 为此,我们需要知道标记扩展的目标元素。

有一个问题:当在模板内使用标记扩展时,没有目标元素(显然)。 在这种情况下,您是 应该ProvideValue()中使用return this - 这样,应用模板时将再次调用扩展。

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

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));
}
痴情换悲伤 2024-07-31 01:46:22

不可能获得绑定的值。 你甚至不应该尝试这样做。 WPF 使用一些奇特的反射来解析绑定,相信我 - 您不会不想自己开始尝试的。

无论如何,考虑到这一点,这就是我最终所做的,这实际上是一个很好的解决方案:

我制作了一个 TranslateConverter 来处理翻译:

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

然后在我的 TranslateExtension 中我只是这样做:

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

这样,绑定由 WPF 解析并作为值传递给转换器,而简单的文本键作为参数传递给转换器。

_targetObject_targetProperty 是从 ServiceProvider 获取的。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文