您可以创建一个自定义的
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}---";
return m_b.ProvideValue(sp);
}
}
如果您使用上面的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;
if (!(pvt.TargetObject is DependencyObject))
return pvt.TargetObject;
var dp = (DependencyProperty)pvt.TargetProperty;
return b.ProvideValue(sp);
}
};
为了完整性,这个版本也可以与XAML对象标签语法一起使用,其中包装的Binding被设置为属性,而不是在构造函数中设置。
在指定位置插入自定义代码以操作绑定。您可以在这里做任何想做的事情,例如:
1.检查或修改运行时情况和/或状态:
```
var x = dobj.GetValue(dp);
dobj.SetValue(dp, 12345);
dobj.CoerceValue(dp); // etc.
```
2.在将绑定封闭到BindingExpression之前重新配置/自定义绑定:
```
b.Converter = new FooConverter(/* customized values here */);
b.ConverterParameter = Math.PI;
b.StringFormat = "---{0}---"; // ...as shown above
```
3.也许决定在某些情况下不需要绑定;不要继续进行绑定:
```
if (binding_not_needed)
return null;
```
- 还有很多,只受你的想象力限制。准备好后,调用绑定的
ProvideValue
方法,它会创建自己的 BindingExpression
。因为你传递了自己的 IProvideValueTarget
信息(即你的 IServiceProvider
),新的绑定将替换掉你的标记扩展。它会附加到目标对象/属性上,在那里你的 标记扩展 在 XAML 中被编写,这正是你想要的。
奖励:你还可以操作返回的 BindingExpression
如果预配置绑定不足够,注意你也可以访问已实例化的 BindingExpression
。不要像所示地 tail-calling 返回 ProvideValue
结果,而是将结果存储在本地。在返回之前,你可以通过 BindingExpression
上可用的各种通知选项设置监视或拦截绑定流量。
最后说明:如此处所讨论,当WPF标记扩展用于模板内部时,有特殊考虑事项。特别是,您会注意到,您的标记扩展最初被探测到,并将IProvideValueTarget.TargetObject
设置为System.Windows.SharedDp
的实例。因为加载模板是一个延迟的过程,我认为这里的目的是对您的标记扩展进行早期探测,以确定其特性,即在任何真实数据存在之前,可以正确填充模板。如上面的代码所示,您 [必须返回'this'] 可以返回探测对象本身来处理这些情况;如果不这样做,当真正的TargetObject
可用时,您的ProvideValue
将不会再次被调用 [参见编辑]。
编辑: WPF非常努力地使XAML资源可共享,特别是包括
BindingBase
和派生类。如果在可重用的上下文(例如
Template
)中使用我在这里描述的技术,则需要确保包装的绑定不符合可共享的标准,否则在生成
BindingExpression
后,包装的绑定将变为
BindingBase.isSealed=true
;尝试修改
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}
才是最正确的方法)。我已经更新了原帖中的代码以反映这一点,但我欢迎进一步的见解,以便在模板与非模板两种用例中实现最大资源共享。
编辑:我认为,为了在模板使用中进行可共享性确定,返回this
(如我最初建议的那样)和我的修订建议返回pvt.TargetObject
之间的主要区别在于前者源自MarkupExtension
--而System.Windows.SharedDp
的基类是Object
--并且很明显探测代码会递归到嵌套的标记扩展中。
Binding.ProvideValue
方法(实际上返回一个BindingExpression
),但该方法需要一些IServiceProvider
作为参数。我所见过的唯一可以访问此接口的地方是在某些自定义MarkupExtension
的ProvideValue
实现内部。因此看起来真的卡在那里了。 - King King