Привязать к методу в WPF?

90

Как вы в 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'; Error = 'Указанный член не может быть вызван на цели.' TargetException: 'System.Reflection.TargetException: нестатический метод требует цели.

Кэмерон МакФарланд
источник
Да, ты прав. ObjectDataProvider имеет свойство ObjectInstance (для вызова его метода в конкретном экземпляре), но я не думаю, что это свойство зависимости, поэтому вы не можете его привязать (AFAIK).
Мэтт Гамильтон,
1
Да, я попытался привязать к ObjectInstance и обнаружил, что это не свойство зависимости.
Кэмерон МакФарланд
В любом случае я оставлю свой ответ там, чтобы дать вашему обновлению некоторый контекст и помочь кому-то еще, кто найдет этот вопрос с достаточно похожей проблемой.
Мэтт Гамильтон,
Вам действительно нужно привязаться к ObjectInstance? (Будет ли это меняться) Предполагая, что вместо этого вы могли бы создать свою собственную обработку событий изменения и обновить ObjectDataProvider в коде ...
Тим Ловелл-Смит,
1
Только что обновил свой ответ некоторым исходным кодом, через год после этого.
Дрю Ноукс

Ответы:

71

Другой подход, который может сработать для вас, - создать кастом, 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
25

Не уверен, насколько хорошо это будет работать в вашем сценарии, но вы можете использовать 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).

Мэтт Гамильтон
источник
11

К методу привязать надо?

Можете ли вы привязать свойство к методу, получателем которого является метод?

public ObservableCollection<ChildObject> Children
{
   get
   {
      return GetChildren();
   }
}
Майкл Превецки
источник
2
Я считаю, что комментарий Кэмерона означает, что он привязан к типу, к которому он не может добавить свойство.
Дрю Ноукс
2
Вам следует избегать привязки к свойствам, которые вызывают методы, особенно, если метод потенциально может работать долго. Наличие таких методов - это не лучший дизайн, поскольку потребитель кода ожидает, что свойство будет обращаться только к локальной переменной.
markmnl
@markmnl, так в чем же смысл непосредственной привязки к функции? Итак, вопрос OP не имеет никакого смысла в вашем случае?
Теоман Шипахи,
4

Если вы не можете добавить свойство для вызова метода (или создать класс-оболочку, который добавляет это свойство), единственный известный мне способ - это использовать ValueConverter.

Нир
источник
3

ObjectDataProvider также имеет свойство ObjectInstance, которое можно использовать вместо ObjectType.

Грэм Эмброуз
источник
3

Вы можете использовать System.ComponentModelдля динамического определения свойств типа (они не являются частью скомпилированных метаданных). Я использовал этот подход в WPF, чтобы включить привязку к типу, который хранит свои значения в полях, поскольку привязка к полям невозможна.

Эти ICustomTypeDescriptorи TypeDescriptionProviderтипы могут позволить вам достичь того, чего вы хотите. Согласно этой статье :

TypeDescriptionProviderпозволяет вам написать отдельный класс, который реализует, ICustomTypeDescriptorа затем зарегистрировать этот класс в качестве поставщика описаний для других типов.

Я сам не пробовал этот подход, но надеюсь, что он поможет в вашем случае.

Дрю Ноукс
источник
0

Чтобы привязаться к методу объекта в сценарии WPF, вы можете привязаться к свойству, которое возвращает делегат.

Остин Андерсон
источник