绑定到 WPF 中的方法? ?

在这个场景中,如何在 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方法。

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

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

数据错误: 33: ObjectDataProvider 不能创建对象; Type = ‘ RootObject’; Error = ‘构造函数的错误参数’

系统。窗户。数据错误: 34: ObjectDataProvider: 尝试在类型上调用方法失败; Method = ‘ GetChildren’; Type = ‘ RootObject’; Error = ‘指定的成员不能在目标上调用。TargetException: 系统。反射。TargetException: 非静态方法需要一个目标。

99763 次浏览

Not sure how well it will work in your scenario, but you can use the MethodName property on ObjectDataProvider to have it call a specific method (with specific parameters of you MethodParameters property) to retrieve its data.

Here's a snippet taken directly from the MSDN page:

<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>

So that's an ObjectDataProvider that's calling a ConvertTemp method on an instance of a TemperatureScale class, passing two parameters (0 and TempType.Celsius).

Do you have to bind to the method?

Can you bind to a property who's getter is the method?

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

Unless you can add a property to call the method (or create a wrapper class that adds that property) the only way I know of is using a ValueConverter.

ObjectDataProvider also has an ObjectInstance property that can be used instead of ObjectType

You can use System.ComponentModel to define properties for a type dynamically (they're not part of the compiled metadata). I used this approach in WPF to enable binding to a type that stored its values in fields, as binding to fields is not possible.

The ICustomTypeDescriptor and TypeDescriptionProvider types might allow you to achieve what you want. According to this article:

TypeDescriptionProvider allows you to write a separate class that implements ICustomTypeDescriptor and then to register this class as the provider of descriptions for other types.

I haven't tried this approach myself, but I hope it's helpful in your case.

Another approach that might work for you is to create a custom IValueConverter that takes a method name as a parameter, so that it would be used like this:

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

This converter would find and invoke the method using reflection. This requires the method to not have any arguments.

Here's an example of such a converter's source:

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.");
}
}

And a corresponding unit test:

[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));
}

Note that this converter does not enforce the targetType parameter.

To bind to an object's method in your WPF scenario, you can bind to a property that returns a delegate.

Same as Drew Noakes's answer, but with an ability to use extension methods.

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.");
}
}