如何使用WPF解析绑定表达式中的绑定对象?

27

大家好,有没有人知道是否有内置类可以从绑定表达式中解析出绑定对象及其数据项和属性路径?

我试图为文本框编写 Blend 3 行为,该行为会自动调用绑定到文本框 Text 属性的对象上的方法。

文本框绑定到 ViewModel 类的属性。我想要做的是从绑定表达式中解析出 ViewModel 类,然后对其进行调用。

我首先从行为的关联对象中检索绑定表达式,如下所示:

private BindingExpression GetTextBinding()
{
    return this.AssociatedObject.GetBindingExpression(TextBox.TextProperty);
}

完成这一步后,如果我们查看绑定表达式,我们可以看到它通过绑定表达式的DataItem属性引用了数据上下文。

此外,我们还有绑定表达式父绑定上绑定的属性的相对路径。

因此,我们可以获取这些信息:

var bindingExpression = GetTextBinding();
object dataContextItem = bindingExpression.DataItem;
PropertyPath relativePropertyPath = bindingExpression.ParentBinding.Path;
现在,这个属性路径可能是一个深度嵌套和复杂的路径,我非常希望避免(重新?)实现它的解析。我已经在.NET文档中搜索并通过反射器来回跳转了所有程序集,但都没有找到我想要的东西 - 必须存在某个类来执行数据项(数据上下文)路径的解析。
有人知道这可能存在的位置吗?还有其他解决绑定对象的替代方法吗?
请注意,我正在尝试访问绑定属性(在此为字符串)的父对象 - 我显然可以轻松地访问绑定值,但我需要的是父对象。
提前感谢您的任何帮助! Phil
6个回答

32

针对未来可能遇到这个问题的人:

当.NET 4.5发布时,将在BindingExpression上增加一些新属性,以极大地简化您所寻找的内容。

ResolvedSource - 实际绑定的对象,在像'grandparent.parent.me.Name'这样的绑定源中很有帮助。这将返回“me”对象。

ResolvedSourcePropertyName - ResolvedSource上绑定的属性名称。在上面的例子中是“Name”。

同样,还有TargetTargetName属性。

通过BindingExpression上的这些辅助属性,您可以使用一些更短、更简化的反射,这些反射在极端情况下(索引器)更容易起作用。


你知道是否有任何方法可以强制绑定表达式更新这些属性吗?我处于一个奇特的情况,当控件的数据上下文更改时,我正在克隆绑定并从绑定表达式获取源,但是这些属性似乎仍然为空,直到属性更改和数据上下文更改事件之后。 - Alex Hope O'Connor
@AlexHopeO'Connor 我可以有多种方式来解释你的问题。我建议你开一个新的问题,而不是在这里添加。 - Kevin Kalitowski

26
以下是一个快速实现的扩展方法,可以做到您想要的功能。我也没有找到与此相关的任何内容。如果由于某种原因无法找到值,则下面的方法将始终返回null。当路径包含[]时,该方法将无法工作。希望这能帮到您!
public static T GetValue<T>(this BindingExpression expression, object dataItem)            
{
    if (expression == null || dataItem == null)
    {
        return default(T);
    }

    string bindingPath = expression.ParentBinding.Path.Path;
    string[] properties = bindingPath.Split('.');

    object currentObject = dataItem;
    Type currentType = null;

    for (int i = 0; i < properties.Length; i++)
    {
        currentType = currentObject.GetType();                
        PropertyInfo property = currentType.GetProperty(properties[i]);
        if (property == null)
        {                    
            currentObject = null;
            break;
        }
        currentObject = property.GetValue(currentObject, null);
        if (currentObject == null)
        {
            break;
        }
    }

    return (T)currentObject;
}

6
为什么要传递对象数据项dataItem?为什么不只改变方法签名以接受一个参数而不是两个,并从BindingExpression中获取dataItem呢?我的看法是这样的。 - Terrance
这似乎不能解析带有索引器的属性路径,是吗? - O. R. Mapper
未来证明的方法如下。 - Robetto

11

仅供参考,PropertyPath的解析由一个名为PropertyPathWorker的内部MS类处理,该类位于MS.Internal.Data下的PresentationFramework中。如果在Reflector中打开它,您会发现它非常复杂,因此我不建议尝试复制其功能。它与整个Binding架构紧密耦合。

支持所有属性路径语法(包括附加依赖属性和分层遍历)的最可靠方法可能是创建具有DependencyProperty的虚拟DependencyObject。然后,您可以从您的“owner”路径创建一个绑定到虚拟依赖属性,创建一个新的BindingExpression,然后调用表达式的UpdateTarget。这是一种相当繁重的方法来完成表面上看起来像一个简单任务的工作,但我认为在绑定属性路径解析的方式中存在许多隐藏的陷阱。


8
我相信这里发布的解决方案也可能适用于您。
复制代码块以供参考,请阅读Thomas Levesque提供的更多细节。
public static class PropertyPathHelper
{
    public static object GetValue(object obj, string propertyPath)
    {
        Binding binding = new Binding(propertyPath);
        binding.Mode = BindingMode.OneTime;
        binding.Source = obj;
        BindingOperations.SetBinding(_dummy, Dummy.ValueProperty, binding);
        return _dummy.GetValue(Dummy.ValueProperty);
    }

    private static readonly Dummy _dummy = new Dummy();

    private class Dummy : DependencyObject
    {
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(object), typeof(Dummy), new UIPropertyMetadata(null));
    }
}

3

正如Dan Bryant所指出的,PropertyPath解析与整个绑定架构紧密耦合。
如果您需要与WPF完全相同的解析方式,则应该使用Thomas Levesque在这个问题中的答案。

然而,如果您只需要一般的路径解析,您可以使用我开发的nuget包Pather.CSharp来实现。

它基本上类似于zhech的答案,但更为复杂。

它的主要方法是Resolver类上的Resolve。传入目标对象路径作为字符串返回所需的结果。
一个示例:

IResolver resolver = new Resolver(); 
var target = new { Property1 = new { Property2 = "value" } };   
object result = r.Resolve(target, "Property1.Property2");

它还支持通过索引进行集合访问或通过键进行字典访问
这些的示例路径为:
"ArrayProperty[5]"
"DictionaryProperty[Key]"

0

如果其他人发现有用的话,下面是一个快速的一行扩展方法,用于 .net4.5(及以上版本)来检索绑定值。

public static object GetValue(this BindingExpression bindingExpression)
{
    return bindingExpression?.ResolvedSource.GetType().GetProperty(bindingExpression.ResolvedSourcePropertyName)?.GetValue(bindingExpression.ResolvedSource);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接