Powiązać z metodą w WPF?

90

Jak utworzyć powiązanie z metodą obiektów w tym scenariuszu w 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>

Tutaj chcę powiązać się z GetChildrenmetodą na każdym RootObjectdrzewie.

EDYCJAObjectDataProvider Wydaje się, że powiązanie z elementem an nie działa, ponieważ wiążę się z listą elementów i ObjectDataProviderpotrzebuje albo statycznej metody, albo tworzy własną instancję i używa jej.

Na przykład, używając odpowiedzi Matta, otrzymuję:

Błąd System.Windows.Data: 33: ObjectDataProvider nie może utworzyć obiektu; Type = 'RootObject'; Błąd = „Nieprawidłowe parametry konstruktora”.

Błąd System.Windows.Data: 34: ObjectDataProvider: Błąd podczas próby wywołania metody dla typu; Method = 'GetChildren'; Type = 'RootObject'; Error = „Określony członek nie może zostać wywołany w miejscu docelowym.” TargetException: „System.Reflection.TargetException: metoda niestatyczna wymaga celu.

Cameron MacFarland
źródło
Tak, masz rację. ObjectDataProvider ma właściwość ObjectInstance (aby wywołać swoją metodę w określonej instancji), ale nie sądzę, że jest to właściwość zależności, więc nie można jej powiązać (AFAIK).
Matt Hamilton
1
Tak, próbowałem powiązać z ObjectInstance i odkryłem, że nie jest to właściwość zależności.
Cameron MacFarland
W każdym razie zostawię tam swoją odpowiedź, zarówno po to, aby nadać twojej aktualizacji trochę kontekstu, jak i po to, aby pomóc komuś, kto znajdzie to pytanie z podobnym problemem.
Matt Hamilton
Czy faktycznie potrzebujesz powiązać z ObjectInstance? (Czy to się zmieni) Zakładając, że możesz zamiast tego utworzyć własną obsługę zdarzenia zmiany i zaktualizować ObjectDataProvider w kodzie ...
Tim Lovell-Smith
1
Właśnie zaktualizowałem moją odpowiedź jakimś kodem źródłowym rok po fakcie.
Drew Noakes

Odpowiedzi:

71

Innym podejściem, które może zadziałać, jest utworzenie niestandardowego, IValueConverterktóry przyjmuje nazwę metody jako parametr, tak aby był używany w następujący sposób:

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

Ten konwerter znalazłby i wywołałby metodę przy użyciu odbicia. To wymaga, aby metoda nie miała żadnych argumentów.

Oto przykład źródła takiego konwertera:

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

I odpowiedni test jednostkowy:

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

Zauważ, że ten konwerter nie wymusza tego targetTypeparametru.

Drew Noakes
źródło
6
Hmmm, ... wygląda na włamanie, ale zaczynam myśleć, że to może być jedyny sposób. Cholera, będzie najłatwiej!
EightyOne Unite
25

Nie jestem pewien, jak dobrze zadziała w Twoim scenariuszu, ale możesz użyć tej MethodNamewłaściwości, ObjectDataProvideraby wywołać określoną metodę (z określonymi parametrami MethodParameterswłaściwości) w celu pobrania danych.

Oto fragment pobrany bezpośrednio ze strony 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>

Więc to ObjectDataProviderjest wywołanie ConvertTempmetody na instancji TemperatureScaleklasy, przekazując dwa parametry ( 0i TempType.Celsius).

Matt Hamilton
źródło
11

Czy musisz wiązać się z metodą?

Czy możesz powiązać się z właściwością, która pobiera metodę?

public ObservableCollection<ChildObject> Children
{
   get
   {
      return GetChildren();
   }
}
Michael Prewecki
źródło
2
Rozumiem, że komentarz Camerona oznacza, że ​​wiąże się on z typem, do którego nie może dodać właściwości.
Drew Noakes
2
Należy unikać tworzenia powiązań z właściwościami, które wywołują metody esp, jeśli metoda może być potencjalnie długotrwała. Posiadanie takich metod nie jest dobrym projektem, ponieważ konsument kodu oczekuje, że właściwość będzie miała dostęp tylko do zmiennej lokalnej.
markmnl
@markmnl, więc jaki jest sens bezpośredniego powiązania z funkcją? Więc pytanie OP nie ma sensu w twoim przypadku?
Teoman shipahi
4

O ile nie możesz dodać właściwości, aby wywołać metodę (lub utworzyć klasę opakowania, która dodaje tę właściwość), jedynym znanym mi sposobem jest użycie ValueConverter.

Nir
źródło
3

ObjectDataProvider ma również właściwość ObjectInstance, której można użyć zamiast ObjectType

Graham Ambrose
źródło
3

Możesz użyć System.ComponentModeldo dynamicznego definiowania właściwości dla typu (nie są one częścią skompilowanych metadanych). Użyłem tego podejścia w WPF, aby włączyć powiązanie z typem, który przechowywał swoje wartości w polach, ponieważ powiązanie z polami nie jest możliwe.

Te ICustomTypeDescriptori TypeDescriptionProvidertypy mogą pozwala osiągnąć to, co chcesz. Zgodnie z tym artykułem :

TypeDescriptionProviderumożliwia napisanie oddzielnej klasy, która implementuje, ICustomTypeDescriptora następnie zarejestrować tę klasę jako dostawcę opisów dla innych typów.

Sam nie próbowałem tego podejścia, ale mam nadzieję, że będzie to pomocne w Twoim przypadku.

Drew Noakes
źródło
0

Aby powiązać z metodą obiektu w scenariuszu WPF, można powiązać z właściwością, która zwraca delegata.

Austin_Anderson
źródło