如何从“绑定”对象获取“绑定表达式”? [英] How do I get a `BindingExpression` from a `Binding` object?

查看:132
本文介绍了如何从“绑定”对象获取“绑定表达式”?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简而言之,我有一个 ListView (target)单向绑定到一个 XmlDataProvider (源)双向绑定到 TextBox (target)使用标准XAML作为控件绑定和自定义XAML扩展绑定到 XmlDataProvider 。这是一个方便的应用程序,因为在应用程序运行后,$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $

无论如何,在运行时,修改 TextBox.Text 属性后, IMultiValueConverter.ConvertBack(...)方法将更新从此目标传回源。但是,因为 XmlDataProvider 对象不是 DependencyProperty ,更新不会从更改的 XmlDataProvider 来源于另一个绑定到 ListView 目标。



没有重新建立,您可以合法地建议,我需要通知WPF,这个 XmlDataProvider 作为源的目标需要更新。我希望保持一个通用的,可重用的绑定类,到目前为止,我喜欢我的大多数XAML解决方案的低代码负担。



目前,唯一的代码是访问我来自 IMultiValueConverter.ConvertBack(...)方法。从这个方法中我可以访问绑定对象 XmlDataProvider < - > TextBox 链接。如果我可以为 Binding.Source 对象获取 BindingExpression ,则可以调用 BindingExpression.UpdateTarget()来完成更新传播,...



但是,我不知道如何获得一个 BindingExpression Binding.Source 对象,它不与



提前感谢您的建议和帮助。

解决方案

您可以创建一个自定义的 MarkupExtension ,它接受一个绑定作为构造函数参数。在XAML的使用中,你的将是一个将WPF包装起来的外部绑定:

 < StackPanel> 
< TextBox x:Name =tb/>
< TextBlock Text ={local:MyBinding {Binding ElementName = tb,Path = Text,Mode = OneWay}}/>
< / StackPanel>

MyBinding 构造函数中,您将收到一个WPF 绑定对象。当您的 ProvideValue 被调用时,请存储一份副本。那时你可以在你保存的绑定上调用$ code> ProvideValue ,而把传递给你现在拥有的IServiceProvider实例。您将收到一个 BindingExpression ,然后您可以从您自己的 ProvideValue 返回。



这是一个最小的例子。对于一个简单的演示,它只是将(或覆盖)一个 Binding.StringFormat 属性添加到内部(包装)绑定。

  [MarkupExtensionReturnType(typeof(BindingExpression))] 
公开密封类MyBindingExtension:MarkupExtension
{
public MyBindingExtension(Binding b){this.m_b = b; }

绑定m_b;

public override Object ProvideValue(IServiceProvider sp)
{
m_b.StringFormat =--- {0} ---; //修改包装首先绑定...

返回m_b.ProvideValue(sp); // ...然后获取其BindingExpression
}
}

如果你尝试使用上面的XAML,您将看到一个实时绑定确实设置在目标上,而您根本无需解压缩 IProvideValueTarget 。 p>

这涵盖了基本的洞察力,所以如果你现在知道要做什么,你可能不需要阅读这个答案的其余部分...






更多细节



在大多数情况下,挖掘进入 IProvideValueTarget 实际上是整个练习的要点,因为您可以根据运行时情况动态修改包装绑定。下面的扩展的 MarkupExtension 显示了相关对象和属性的提取,并且显然有很多可能性,您可以从那里做什么。

  [MarkupExtensionReturnType(typeof(BindingExpression))] 
[ContentProperty(SourceBinding)]
public sealed class MyBindingExtension:MarkupExtension
{
public MyBindingExtension(){}
public MyBindingExtension(Binding b){this.b = b; }

绑定b;
public Binding SourceBinding {get {return b; } set {b = value;

public override Object ProvideValue(IServiceProvider sp)
{
if(b == null)
throw new ArgumentNullException(nameof(SourceBinding));

IProvideValueTarget pvt;
if((pvt = sp as IProvideValueTarget)== null)
return null; //防止XAML设计器崩溃

DependencyObject dobj;
if((dobj = pvt.TargetObject as DependencyObject)== null)
return pvt.TargetObject; //必需的模板重新绑定

var dp =(DependencyProperty)pvt.TargetProperty;

/ ***插入您的代码*** /

//将绑定作为绑定表达式附加到目标
返回b.ProvideValue(sp);
}
}

为了完整性,此版本也可以与 XAML 对象标记语法,其中包装的绑定被设置为属性,而不是在构造函数中。



在指定的位置插入您的自定义代码来操作绑定。您可以在这里做任何事情,例如:


  1. 检查或修改运行时情况和/或状态:



 
var x = dobj.GetValue(dp);
dobj.SetValue(dp,12345);
dobj.CoerceValue(dp); //等




  1. 重新配置和/或自定义绑定,然后再将其封装成 BindingExpression



 
b.Converter =新的FooConverter(/ *定制值在这里* /);
b.ConverterParameter = Math.PI;
b.StringFormat =--- {0} ---; // ...如上所示




  1. 也许决定绑定是不必要的案件;不要继续约束:



 
if(binding_not_needed)
return null;




  1. 更多的受限于你的想象力。准备好后,调用绑定的 ProvideValue 方法,它将创建其 BindingExpression 。因为你通过它自己的 IProvideValueTarget 信息(即你的 IServiceProvider ),新绑定将替换自己的标记扩展名。它附加到目标对象/属性,其中 标记扩展在XAML中创作,这正是您想要的。

奖金:您还可以操作返回的BindingExpression



- 配置绑定是不够的,请注意,您还可以访问实例化的 BindingExpression 。而不是尾呼叫 ProvideValue 结果如图所示,只将结果存储到本地。在返回之前,您可以通过 BindingExpression 上提供的各种通知选项来设置绑定流量的监视或截取。






最终注释:正如讨论这里,当WPF标记扩展在模板中使用时,有特别的注意事项。特别是,您将注意到,最初使用 IProvideValueTarget.TargetObject 设置为 System.Windows.SharedDp 。因为加载模板是一个自然的延迟过程,我相信这里的目的是早期探测您的标记扩展以确定其特征,即在存在适当填充模板的任何真实数据之前很久。如上面的代码所示,你[必须返回'this']可以返回这些情况下的探针对象本身; 如果没有,则当 TargetObject 可用时,您的 ProvideValue 将不再被调用 [请参阅编辑]






编辑: XAML资源可共享,特别是包含 BindingBase 和派生类。如果使用我在这里描述的技术在可重复使用的上下文(例如 Template )中,则需要确保包装的绑定不符合可共享的标准,否则在第一次生成一个 BindingExpression 之后,包装绑定将成为 BindingBase.isSealed = true ;随后尝试修改绑定将会失败:


InvalidOperationException:绑定不能在使用之后更改。


有几种解决方法可以做到这一点,您可以通过研究(非公开)WPF函数 TemplateContent.TrySharingValue 。我发现的一种方法是在标记扩展名中随时显示 System.Windows.SharedDp 对象。您可以通过查找任何非 DependencyObject 值或通过以下方式检测 System.Windows.SharedDp

  pvt.TargetObject.GetType()。GUID == 
new Guid(0x00b36157,0xdfb7,0x3372,0x8b,0x08,0xab ,0x9d,0x74,0xad,0xc2,0xfd)

我已经更新了我的原始帖子中的代码以反映这一点,但我欢迎进一步了解如何保留两种用例,模板与非模板的最大资源共享。






编辑:我认为,为了共享性确定,返回之间的主要区别(正如我最初建议的)并返回 pvt.TargetObject 是前者从 MarkupExten sion (与基础类 System.Windows.SharedDp 正在对象),它是清除探测代码转化为嵌套标记扩展名。


In short(?), I have a ListView (target) one-way bound to an XmlDataProvider (source) two-way bound to a TextBox (target) using standard XAML for the control bindings and custom XAML extensions for the bindings to the XmlDataProvider. This is a convenience for the application as the XmlDataProvider is dynamically loaded from user inputs after the application is running,...

Anyway, at run-time, after modifying the TextBox.Text property, the IMultiValueConverter.ConvertBack(...) method is called to propagate the update from this target back to the source. But, because the XmlDataProvider object is not a DependencyProperty, the update is not further propagated from the changed XmlDataProvider source to the other binding to the ListView target.

Without rearchitecting, which you could legitimately advise, I need to notify WPF that any target with this XmlDataProvider as a source needs to be updated. I am hoping to maintain a generic, reusable binding class and have, so far, enjoyed the low coding burden of my mostly XAML solution.

Currently, the only code behind access I have is from within the IMultiValueConverter.ConvertBack(...) method. From within this method I do have access to the Binding object for the XmlDataProvider <--> TextBox link. If I could get the BindingExpression for the Binding.Source object I could then make the call to BindingExpression.UpdateTarget() to complete the update propagation,...

But, I do not know how to get a BindingExpressionfrom a Binding.Source object, that is not associated with a DependencyProperty.

Thanks in advance for your advice and assistance.

解决方案

You can create a custom MarkupExtension which accepts a Binding as a constructor argument. In XAML usage, yours will be an outer binding that wraps the WPF one:

<StackPanel>
    <TextBox x:Name="tb" />
    <TextBlock Text="{local:MyBinding {Binding ElementName=tb,Path=Text,Mode=OneWay}}" />
</StackPanel>

In the MyBinding constructor you will receive a WPF Binding object. Store a copy for later when your ProvideValue is called. At that time, you can call ProvideValue on the binding you saved--and pass it the IServiceProvider instance you now have. You'll get back a BindingExpression that you can then return from your own ProvideValue.

Here's a minimal example. For a simple demonstration, it just adds (or overwrites) a Binding.StringFormat property to the inner (wrapped) binding.

[MarkupExtensionReturnType(typeof(BindingExpression))]
public sealed class MyBindingExtension : MarkupExtension
{
    public MyBindingExtension(Binding b) { this.m_b = b; }

    Binding m_b;

    public override Object ProvideValue(IServiceProvider sp)
    {
        m_b.StringFormat = "---{0}---";   // modify wrapped Binding first...

        return m_b.ProvideValue(sp);    // ...then obtain its BindingExpression
    }
}

If you try it with the XAML above, you'll see that a live binding is indeed set on the target, and you didn't have to unpack the IProvideValueTarget at all.

This covers the basic insight, so if you know exactly what to do now, you probably won't need to read the rest of this answer...


More details

In most cases, digging into the IProvideValueTarget is actually the point of the whole exercise, because you can then modify the wrapped binding dynamically according to runtime conditions. The expanded MarkupExtension below shows the extraction of the relevant objects and properties, and there are obviously numerous possibilities for what you can do from there.

[MarkupExtensionReturnType(typeof(BindingExpression))]
[ContentProperty("SourceBinding")]
public sealed class MyBindingExtension : MarkupExtension
{
    public MyBindingExtension() { }
    public MyBindingExtension(Binding b) { this.b = b; }

    Binding b;
    public Binding SourceBinding { get { return b; } set { b = value; } }

    public override Object ProvideValue(IServiceProvider sp)
    {
        if (b == null)
            throw new ArgumentNullException(nameof(SourceBinding));

        IProvideValueTarget pvt;
        if ((pvt = sp as IProvideValueTarget) == null)
            return null;         // prevents XAML Designer crashes

        DependencyObject dobj;
        if ((dobj = pvt.TargetObject as DependencyObject) == null)
            return pvt.TargetObject;    // required for template re-binding

        var dp = (DependencyProperty)pvt.TargetProperty;

        /*** INSERT YOUR CODE HERE ***/

        // finalize binding as a BindingExpression attached to target
        return b.ProvideValue(sp);
    }
}

For completeness, this version can also be used with XAML object tag syntax, where the wrapped Binding is set as a property, instead of in the constructor.

Insert your customization code for manipulating the binding where indicated. You can do pretty much anything you want here, such as:

  1. Check or modify the runtime situation and/or state:

var x = dobj.GetValue(dp);
dobj.SetValue(dp, 12345);
dobj.CoerceValue(dp); // etc.

  1. Reconfigure and/or customize the binding prior to sealing it into the BindingExpression:

b.Converter = new FooConverter(/* customized values here */);
b.ConverterParameter = Math.PI;
b.StringFormat = "---{0}---";   // ...as shown above

  1. Perhaps decide binding is not needed in certain cases; do not proceed with binding:

if (binding_not_needed)
    return null;

  1. Lots more, limited by your imagination. When ready, call the binding's ProvideValue method and it will create its BindingExpression. Because you pass it your own IProvideValueTarget info (i.e. your IServiceProvider), the new binding will substitute itself for your markup extension. It gets attached to the target object/property where your MarkupExtension was authored in XAML, which is exactly what you want.

Bonus: You can also manipulate the returned BindingExpression

If pre-configuring the binding isn't enough, note that you also have access to the instantiated BindingExpression. Instead of tail-calling the ProvideValue result as shown, just store the result into a local. Prior to returning it, you can set up monitoring or interception of the binding traffic via the various notification options that are available on BindingExpression.


Final note: as discussed here, there are special considerations when WPF markup extensions are used inside templates. In particular, you will notice that your markup extension is initially probed with IProvideValueTarget.TargetObject set to an instance of System.Windows.SharedDp. Because loading templates is a naturally a deferred process, I believe the purpose here is early probing of your markup extension to determine its characteristics, i.e. long prior to any real data exists for populating the template properly. As shown in the above code, you [must return 'this'] c̲a̲n̲ r̲e̲t̲u̲r̲n̲ t̲h̲e̲ p̲r̲o̲b̲e̲ o̲b̲j̲e̲c̲t̲ i̲t̲s̲e̲l̲f̲ for these cases; if you don't, your ProvideValue won't be called back again when the real TargetObject is available [see edit].


edit: WPF tries really hard to make XAML resources shareable, and this especially includes the BindingBase and derived classes. If using the technique I describe here in a reusable context (such as a Template), you need to make sure that the wrapped binding does not meet the criteria for shareability, otherwise the wrapped binding will become BindingBase.isSealed=true after the first time it generates a BindingExpression; subsequent attempts to modify the Binding will fail with:

InvalidOperationException: Binding cannot be changed after it has been used.

There are several workarounds to do this, which you can ascertain by studying the source code of the (non-public) WPF function TemplateContent.TrySharingValue. One method I found was to return the System.Windows.SharedDp object from your markup extension anytime it shows up. You can detect System.Windows.SharedDp either by looking for any non-DependencyObject value, or via:

pvt.TargetObject.GetType().GUID == 
             new Guid(0x00b36157,0xdfb7,0x3372,0x8b,0x08,0xab,0x9d,0x74,0xad,0xc2,0xfd)

I've updated the code in my original post to reflect this, but I welcome further insight on how to preserve maximal resource sharing for both of the use cases, template vs. non-template.


edit: I'm thinking that, for the purposes of the sharability determination in template usage, the main difference between returning this (as I had originally suggested) and returning pvt.TargetObject is that the former derives from MarkupExtension (versus base class of System.Windows.SharedDp being Object), and it's clear that the probing code recurses into nested markup extensions.

这篇关于如何从“绑定”对象获取“绑定表达式”?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆