Привязать к методу в 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.");
    }
}
Другие вопросы по тегам