在WPF中绑定方法?

92

在WPF中,如何绑定到对象的方法?

public class RootObject
{
    public string Name { get; }

    public ObservableCollection<ChildObject> GetChildren() {...}
}

public class ChildObject
{
    public string Name { get; }
}

XAML:

<TreeView ItemsSource="some list of RootObjects">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type data:RootObject}" 
                                  ItemsSource="???">
            <TextBlock Text="{Binding Path=Name}" />
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate DataType="{x:Type data:ChildObject}">
            <TextBlock Text="{Binding Path=Name}" />
        </HierarchicalDataTemplate>
    </TreeView.Resources>
</TreeView>

我想在树的每个RootObject上绑定到GetChildren方法。

编辑绑定到ObjectDataProvider似乎行不通,因为我绑定到了一个项目列表,而ObjectDataProvider需要一个静态方法,或者它创建自己的实例并使用它。

例如,使用Matt的答案,我得到:

System.Windows.Data Error: 33 : ObjectDataProvider无法创建对象;Type='RootObject';Error='Wrong parameters for constructor.'

System.Windows.Data Error: 34 : ObjectDataProvider:尝试在类型上调用方法失败;Method='GetChildren';Type='RootObject';Error='The specified member cannot be invoked on target.' TargetException:'System.Reflection.TargetException: Non-static method requires a target.


没错,ObjectDataProvider确实有一个ObjectInstance属性(用于在特定实例上调用其方法),但我认为它不是一个依赖属性,所以你不能绑定它(据我所知)。 - Matt Hamilton
1
是的,我尝试绑定到ObjectInstance并发现它不是一个依赖属性。 - Cameron MacFarland
我还是会留下我的答案,既是为了给你的更新提供一些背景信息,也是为了帮助其他遇到类似问题的人。 - Matt Hamilton
你是否真的需要绑定到ObjectInstance?(它会改变吗)如果是这样,您可以创建自己的更改事件处理,并在代码中更新ObjectDataProvider... - Tim Lovell-Smith
1
刚刚更新了我的回答,添加了一些源代码,距离发布已经过去一年了。 - Drew Noakes
8个回答

73

另一种可能适合您的方法是创建一个自定义IValueConverter,并将方法名称作为参数传递,以便像这样使用:

ItemsSource="{Binding 
    Converter={StaticResource MethodToValueConverter},
    ConverterParameter='GetChildren'}"

这个转换器会使用反射来查找并调用方法。这需要被调用的方法没有任何参数。

以下是这样一个转换器的源代码示例:

public sealed class MethodToValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var methodName = parameter as string;
        if (value==null || methodName==null)
            return value;
        var methodInfo = value.GetType().GetMethod(methodName, new Type[0]);
        if (methodInfo==null)
            return value;
        return methodInfo.Invoke(value, new object[0]);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException("MethodToValueConverter can only be used for one way conversion.");
    }
}

还有一个相应的单元测试:

[Test]
public void Convert()
{
    var converter = new MethodToValueConverter();
    Assert.AreEqual("1234", converter.Convert(1234, typeof(string), "ToString", null));
    Assert.AreEqual("ABCD", converter.Convert(" ABCD ", typeof(string), "Trim", null));

    Assert.IsNull(converter.Convert(null, typeof(string), "ToString", null));

    Assert.AreEqual("Pineapple", converter.Convert("Pineapple", typeof(string), "InvalidMethodName", null));
}

请注意,此转换器不强制执行targetType参数。


6
嗯,看起来像是一个黑客攻击,但我开始觉得这可能是唯一的方法。毫无疑问,这肯定是最容易的! - EightyOne Unite
这很有用,但无法捕获扩展方法,包括所有的Linq。在实践中,这些方法会非常有用,比如在序列中绑定到Last() - undefined

27

不确定在你的场景中它能否很好地工作,但你可以使用ObjectDataProvider上的MethodName属性来调用一个特定的方法(使用你的MethodParameters属性的特定参数)以检索其数据。

下面是从MSDN页面直接摘录的代码片段:

<Window.Resources>
    <ObjectDataProvider ObjectType="{x:Type local:TemperatureScale}"
        MethodName="ConvertTemp" x:Key="convertTemp">
        <ObjectDataProvider.MethodParameters>
            <system:Double>0</system:Double>
            <local:TempType>Celsius</local:TempType>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</Window.Resources>

这是一个ObjectDataProvider,它调用了TemperatureScale类的实例上的ConvertTemp方法,并传递了两个参数(0TempType.Celsius)。


13

你必须绑定方法吗?

你能够绑定其getter是方法的属性吗?

public ObservableCollection<ChildObject> Children
{
   get
   {
      return GetChildren();
   }
}

3
我理解Cameron的评论是指他正在绑定到一种类型,而无法添加属性。 - Drew Noakes
2
如果方法可能运行时间较长,应避免绑定调用方法的属性。在属性中包含这样的方法不是良好的设计,因为代码的使用者期望属性只访问本地变量。 - markmnl
1
@markmnl,那么直接绑定函数的意义是什么?所以OP的问题在你的情况下没有任何意义? - Teoman shipahi

6

除非你能添加一个属性来调用该方法(或创建一个添加了该属性的包装类),否则我所知道的唯一方法是使用ValueConverter。


3
您可以使用System.ComponentModel动态定义类型的属性(它们不是编译后元数据的一部分)。我在WPF中使用了这种方法,以便绑定到将其值存储在字段中的类型,因为无法绑定到字段。

ICustomTypeDescriptorTypeDescriptionProvider类型可能允许您实现所需的功能。根据本文:

TypeDescriptionProvider允许您编写一个单独的类来实现ICustomTypeDescriptor,然后将此类注册为其他类型的描述提供程序。

我自己没有尝试过这种方法,但希望它对您有所帮助。


3

ObjectDataProvider还有一个ObjectInstance属性,可以代替ObjectType属性。


1
在您的WPF场景中绑定对象方法,您可以绑定到返回委托的属性。

0
与Drew Noakes的答案相同,但具有使用扩展方法的能力。
public sealed class MethodToValueConverter : IValueConverter
{
    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        var methodName = parameter as string;
        if (value == null || methodName == null)
            return value;
        var methodInfo = value.GetType().GetMethod(methodName, Type.EmptyTypes);
        if (methodInfo == null)
        {
            methodInfo = GetExtensionMethod(value.GetType(), methodName);
            if (methodInfo == null) return value;
            return methodInfo.Invoke(null, new[] { value });
        }
        return methodInfo.Invoke(value, Array.Empty<object>());
    }

    static MethodInfo? GetExtensionMethod(Type extendedType, string methodName)
    {
        var method = Assembly.GetExecutingAssembly()
            .GetTypes()
            .Where(type => !type.IsGenericType && !type.IsNested)
            .SelectMany(type => type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic), (_, method) => method)
            .Where(m => m.IsDefined(typeof(ExtensionAttribute), false))
            .Where(m => m.GetParameters()[0].ParameterType == extendedType)
            .FirstOrDefault(m => m.Name == methodName);
        return method;
    }

    public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        throw new NotSupportedException("MethodToValueConverter can only be used for one way conversion.");
    }
}

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