您可以创建一个自定义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 被设置为属性,而不是在构造函数中。
在指示的位置插入您的自定义代码以操作绑定。您可以在这里做几乎任何您想做的事情,例如:
- 检查或修改运行时情况和/或状态:
var x = dobj.GetValue(dp);
dobj.SetValue(dp, 12345);
dobj.CoerceValue(dp); // 等等。
- 在将绑定密封到
BindingExpression 之前重新配置和/或自定义绑定:
b.Converter = new FooConverter(/* 这里自定义值 */);
b.ConverterParameter = Math.PI;
b.StringFormat = "---{0}---"; // ...如上所示
- 可能决定在某些情况下不需要绑定;不要继续绑定:
如果 (binding_not_needed)
返回空值;
- 更多,受限于您的想象力。准备好后,调用绑定的
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——很明显,探测代码递归到嵌套标记扩展中。