gpt4 book ai didi

c# - 如何从 `BindingExpression` 对象获取 `Binding`?

转载 作者:行者123 更新时间:2023-11-30 20:46:28 27 4
gpt4 key购买 nike

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

无论如何,在运行时,修改 TextBox.Text 属性后,将调用 IMultiValueConverter.ConvertBack(...) 方法来传播来自此的更新目标回到源头。但是,因为 XmlDataProvider 对象不是 DependencyProperty,所以更新不会从已更改的 XmlDataProvider 源进一步传播到到 的其他绑定(bind)code>ListView 目标。

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

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

但是,我不知道如何从未与 DependencyProperty 关联的 Binding.Source 对象中获取 BindingExpression

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

最佳答案

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

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

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

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

[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 进行尝试,您会发现目标上确实设置了实时绑定(bind),并且您根本不需要解压缩 IProvideValueTarget

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


更多详情

在大多数情况下,深入了解 IProvideValueTarget 实际上是整个练习的重点,因为您随后可以根据运行时条件动态修改包装绑定(bind)。下面展开的 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 设置为属性,而不是在构造函数中。

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

  1. 检查或修改运行时情况和/或状态:
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 the existence of any real data which could 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 more specifically as follows:

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

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


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

关于c# - 如何从 `BindingExpression` 对象获取 `Binding`?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26877676/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com