Привязать к методу в 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>
Здесь я хочу привязать к GetChildren
метод на каждом RootObject
дерева.
РЕДАКТИРОВАТЬ Привязка к ObjectDataProvider
кажется, не работает, потому что я привязываю к списку элементов, и ObjectDataProvider
нужен либо статический метод, либо он создает собственный экземпляр и использует его.
Например, используя ответ Мэтта, я получаю:
Ошибка System.Windows.Data: 33: ObjectDataProvider не может создать объект; Тип ='RootObject'; Ошибка = 'Неправильные параметры для конструктора.'
System.Windows.Data Ошибка: 34: ObjectDataProvider: сбой при попытке вызвать метод для типа; Метод ='GetChildren'; Тип ='RootObject'; Ошибка = 'Указанный элемент не может быть вызван для цели.' TargetException:'System.Reflection.TargetException: нестатическому методу требуется цель.
8 ответов
Другой подход, который может работать на вас, - это создать 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
параметр.
Не уверен, насколько хорошо он будет работать в вашем сценарии, но вы можете использовать свойство MethodName объекта ObjectDataProvider, чтобы он вызывал определенный метод (с конкретными параметрами, если у вас есть свойство 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, который вызывает метод "ConvertTemp" для экземпляра класса "TemperatureScale", передавая два параметра (0 и TempType.Celsius
).
Вы должны привязать к методу?
Можете ли вы привязать к собственности, чей метод является добытчиком?
public ObservableCollection<ChildObject> Children
{
get
{
return GetChildren();
}
}
Если вы не можете добавить свойство для вызова метода (или создать класс-оболочку, который добавляет это свойство), единственный известный мне способ - это использование ValueConverter.
ObjectDataProvider также имеет свойство ObjectInstance, которое можно использовать вместо ObjectType
Ты можешь использовать System.ComponentModel
определять свойства для типа динамически (они не являются частью скомпилированных метаданных). Я использовал этот подход в WPF, чтобы включить привязку к типу, который сохранил свои значения в полях, поскольку привязка к полям невозможна.
ICustomTypeDescriptor
а также TypeDescriptionProvider
типы могут позволить вам достичь того, что вы хотите. Согласно этой статье:
TypeDescriptionProvider
позволяет написать отдельный класс, который реализуетICustomTypeDescriptor
а затем зарегистрировать этот класс в качестве поставщика описаний для других типов.
Я сам не пробовал такой подход, но надеюсь, что он вам пригодится.
Чтобы связать с методом объекта в вашем сценарии WPF, вы можете связать со свойством, которое возвращает делегата.
То же, что и ответ Дрю Ноукса , но с возможностью использования методов расширения.
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.");
}
}