【问题标题】:How do I get a `BindingExpression` from a `Binding` object?如何从“Binding”对象中获取“BindingExpression”?
【发布时间】:2015-01-08 18:09:15
【问题描述】:

简而言之(?),我有一个ListView(目标)单向绑定到XmlDataProvider(源)双向绑定到TextBox(目标),使用标准XAML进行控制绑定和用于绑定到 XmlDataProvider 的自定义 XAML 扩展。这对应用程序来说很方便,因为XmlDataProvider 是在应用程序运行后从用户输入动态加载的,...

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

如果不重新架构(您可以合理地建议),我需要通知 WPF 以 XmlDataProvider 作为源的任何目标都需要更新。我希望维护一个通用的、可重用的绑定类,并且到目前为止,我享受了我主要是 XAML 解决方案的低编码负担。

目前,我拥有的唯一访问权限代码来自 IMultiValueConverter.ConvertBack(...) 方法。在此方法中,我确实可以访问XmlDataProvider TextBox 链接的Binding 对象。如果我可以获得Binding.Source 对象的BindingExpression,那么我就可以调用BindingExpression.UpdateTarget() 来完成更新传播,...

但是,我不知道如何从Binding.Source 对象中获取BindingExpression,它与DependencyProperty 无关。

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

【问题讨论】:

  • 您可以做到这一点的唯一方法是使用方法Binding.ProvideValue(这实际上返回一个BindingExpression),但该方法需要一些IServiceProvider 作为参数。我看到我们可以访问此类接口的唯一地方是在一些自定义MarkupExtensionProvideValue 实现中。所以看起来它真的卡在那里了。
  • 这是一条我不知道的大道,值得一看。

标签: c# wpf xaml data-binding binding


【解决方案1】:

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

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

MyBinding 构造函数中,您将收到一个WPF Binding 对象。存储一个副本以供以后调用 ProvideValue 时使用。那时,您可以在您保存的绑定上调用 ProvideValue将您现在拥有的 IServiceProvider 实例传递给它。你会得到一个BindingExpression,然后你可以从你自己的ProvideValue返回。

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

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

如果您尝试使用上面的 XAML,您会发现确实在目标上设置了实时绑定,并且您根本不需要解压缩 IProvideValueTarget

这涵盖了基本见解,因此如果您确切知道现在该做什么,您可能不需要阅读此答案的其余部分...


更多详情

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

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

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

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

        if (!(sp is IProvideValueTarget pvt))
            return null;                // prevents XAML Designer crashes

        if (!(pvt.TargetObject is DependencyObject))
            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);
    }
};

为了完整起见,此版本还可以与 XAML 对象标记语法一起使用,其中包装的 Binding 被设置为属性,而不是在构造函数中。

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

  1. 检查或修改运行时情况和/或状态:
var x = dobj.GetValue(dp); dobj.SetValue(dp, 12345); dobj.CoerceValue(dp); // 等等。
  1. 在将绑定密封到BindingExpression 之前重新配置和/或自定义绑定:
b.Converter = new FooConverter(/* 这里自定义值 */); b.ConverterParameter = Math.PI; b.StringFormat = "---{0}---"; // ...如上所示
  1. 可能决定在某些情况下不需要绑定;不要继续绑定:
如果 (binding_not_needed) 返回空值;
  1. 更多,受限于您的想象力。准备好后,调用绑定的ProvideValue 方法,它将创建它的BindingExpression。因为您将自己的IProvideValueTarget 信息(即您的IServiceProvider)传递给它,所以新绑定将自己替换您的标记扩展。它附加到 your MarkupExtension 使用 XAML 编写的目标对象/属性,这正是您想要的。

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

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


最后说明:正如 here 所讨论的,在 模板 中使用 WPF 标记扩展时有一些特殊注意事项。特别是,您会注意到您的标记扩展最初是用IProvideValueTarget.TargetObject 设置为System.Windows.SharedDp 的实例来探测的。因为加载模板自然是一个延迟过程,所以我相信这里的目的是及早探测您的标记扩展以确定其特征,即早在任何可以正确填充模板的真实数据存在之前.如上代码所示,对于这些情况,您 [必须返回 '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̲; 如果你不这样做,当真正的TargetObject 可用时,你的ProvideValue 将不会被再次回调 [查看编辑]。


编辑: WPF 非常努力地使 XAML 资源可共享,这尤其包括 BindingBase 和派生类。如果在可重用的上下文中使用我这里描述的技术(例如Template),则需要确保包装的绑定不满足可共享性的标准,否则包装的绑定在第一次之后将变为BindingBase.isSealed=true它生成一个BindingExpression;随后修改Binding 的尝试将失败,并显示:

InvalidOperationException:绑定使用后无法更改。

有几种解决方法可以做到这一点,您可以通过研究(非公共)WPF 函数TemplateContent.TrySharingValue 的源代码来确定。我发现的一种方法是随时从您的标记扩展中返回 System.Windows.SharedDp 对象。您可以通过查找任何非DependencyObject 值来检测System.Windows.SharedDp,或者更具体地说,如下所示:

if (pvt.TargetObject.GetType().Name == "SharedDp")
   return pvt.TargetObject;

(从技术上讲,检查.GUID{00b36157-dfb7-3372-8b08-ab9d74adc2fd} 是最正确的)。我已经更新了原始帖子中的代码以反映这一点,但我欢迎进一步了解如何为模板与非模板这两个用例保持最大的资源共享。


编辑:我认为,就模板使用中的sharability determination 而言,返回this(正如我最初建议的那样)和我修改后的建议之间的主要区别是return pvt.TargetObject 是前者派生自MarkupExtension——而System.Windows.SharedDp 的基类是Object——很明显,探测代码递归到嵌套标记扩展中。

【讨论】:

  • 在我找到这个答案之前,我自己试过这个。它在运行时确实有效。但是在设计器中,我收到错误 XLS0504 属性“文本”不支持“MyBinding (BindingExpression)”类型的值。你有过这个问题吗?
猜你喜欢
  • 1970-01-01
  • 2010-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-17
  • 2010-11-23
相关资源
最近更新 更多