An eine Methode in WPF binden?

90

Wie binden Sie in diesem Szenario in WPF an eine Objektmethode?

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>

Hier möchte ich an die GetChildrenMethode in jedem RootObjectBaum binden .

BEARBEITEN Die Bindung an eine ObjectDataProviderscheint nicht zu funktionieren, da ich an eine Liste von Elementen binde und ObjectDataProviderentweder eine statische Methode benötige oder eine eigene Instanz erstellt und diese verwendet.

Wenn ich zum Beispiel Matts Antwort verwende, bekomme ich:

System.Windows.Data-Fehler: 33: ObjectDataProvider kann kein Objekt erstellen. Type = 'RootObject'; Fehler = 'Falsche Parameter für Konstruktor.'

System.Windows.Data-Fehler: 34: ObjectDataProvider: Fehler beim Aufrufen der Methode für den Typ; Methode = 'GetChildren'; Type = 'RootObject'; Fehler = 'Das angegebene Mitglied kann nicht auf dem Ziel aufgerufen werden.' TargetException: 'System.Reflection.TargetException: Für eine nicht statische Methode ist ein Ziel erforderlich.

Cameron MacFarland
quelle
Ja, du hast recht. ObjectDataProvider verfügt zwar über eine ObjectInstance-Eigenschaft (um die Methode für eine bestimmte Instanz aufzurufen), ich glaube jedoch nicht, dass es sich um eine Abhängigkeitseigenschaft handelt, sodass Sie sie nicht binden können (AFAIK).
Matt Hamilton
1
Ja, ich habe versucht, mich an ObjectInstance zu binden, und festgestellt, dass es sich nicht um eine Abhängigkeitseigenschaft handelt.
Cameron MacFarland
Ich werde meine Antwort trotzdem dort lassen, um Ihrem Update einen Kontext zu geben und um jemand anderem zu helfen, der diese Frage mit einem ausreichend ähnlichen Problem findet.
Matt Hamilton
Müssen Sie tatsächlich an ObjectInstance binden? (Wird es sich ändern) Angenommen, Sie könnten stattdessen Ihre eigene Änderungsereignisbehandlung erstellen und den ObjectDataProvider im Code aktualisieren ...
Tim Lovell-Smith
1
Ich habe gerade meine Antwort mit einem Quellcode aktualisiert, ein Jahr später.
Drew Noakes

Antworten:

70

Ein anderer Ansatz, der für Sie möglicherweise funktioniert, besteht darin, eine benutzerdefinierte IValueConverterMethode zu erstellen , die einen Methodennamen als Parameter verwendet, damit dieser wie folgt verwendet wird:

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

Dieser Konverter würde die Methode unter Verwendung von Reflexion finden und aufrufen. Dies erfordert, dass die Methode keine Argumente hat.

Hier ist ein Beispiel für die Quelle eines solchen Konverters:

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

Und ein entsprechender 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));
}

Beachten Sie, dass dieser Konverter den targetTypeParameter nicht erzwingt .

Drew Noakes
quelle
5
Hmmm, ... scheint ein Hack zu sein, aber ich fange an zu denken, dass dies der einzige Weg sein könnte. Es wird verdammt sicher am einfachsten sein!
EightyOne Unite
25

Sie sind sich nicht sicher, wie gut es in Ihrem Szenario funktioniert, aber Sie können die MethodNameEigenschaft on verwenden ObjectDataProvider, um eine bestimmte Methode (mit bestimmten Parametern von Ihnen) aufzurufenMethodParameters Eigenschaft) , um die Daten abzurufen.

Hier ist ein Ausschnitt, der direkt von der MSDN-Seite stammt:

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

Das ist also ObjectDataProvidereine ConvertTempMethode , die eine Methode für eine Instanz einer TemperatureScaleKlasse aufruft und zwei Parameter ( 0und TempType.Celsius) übergibt .

Matt Hamilton
quelle
10

Müssen Sie sich an die Methode binden?

Können Sie sich an eine Eigenschaft binden, deren Getter die Methode ist?

public ObservableCollection<ChildObject> Children
{
   get
   {
      return GetChildren();
   }
}
Michael Prewecki
quelle
2
Ich verstehe Camerons Kommentar so, dass er an einen Typ gebunden ist, dem er keine Eigenschaft hinzufügen kann.
Drew Noakes
2
Sie sollten die Bindung an Eigenschaften vermeiden, die Methoden aufrufen, insbesondere wenn die Methode möglicherweise lange ausgeführt wird. Mit solchen Methoden ist es kein gutes Design, da ein Konsument von Code erwartet, dass eine Eigenschaft nur auf eine lokale Variable zugreift.
Markmnl
@markmnl Also, was ist der Sinn der direkten Bindung an die Funktion? Die Frage von OP ergibt in Ihrem Fall also keinen Sinn?
Teoman Shipahi
4

Sofern Sie keine Eigenschaft zum Aufrufen der Methode hinzufügen können (oder eine Wrapper-Klasse erstellen, die diese Eigenschaft hinzufügt), ist die einzige mir bekannte Möglichkeit die Verwendung eines ValueConverter.

Nir
quelle
3

ObjectDataProvider verfügt auch über eine ObjectInstance-Eigenschaft, die anstelle von ObjectType verwendet werden kann

Graham Ambrose
quelle
3

Sie können verwenden System.ComponentModel Eigenschaften für einen Typ dynamisch definieren (sie sind nicht Teil der kompilierten Metadaten). Ich habe diesen Ansatz in WPF verwendet, um die Bindung an einen Typ zu aktivieren, der seine Werte in Feldern gespeichert hat, da eine Bindung an Felder nicht möglich ist.

Mit den Typen ICustomTypeDescriptorund TypeDescriptionProviderkönnen Sie möglicherweise das erreichen, was Sie möchten. Nach diesem Artikel :

TypeDescriptionProviderMit dieser Option können Sie eine separate Klasse schreiben ICustomTypeDescriptor, die diese Klasse implementiert, und diese dann als Anbieter von Beschreibungen für andere Typen registrieren.

Ich habe diesen Ansatz nicht selbst ausprobiert, aber ich hoffe, er ist in Ihrem Fall hilfreich.

Drew Noakes
quelle
0

Um an die Methode eines Objekts in Ihrem WPF-Szenario zu binden, können Sie an eine Eigenschaft binden, die einen Delegaten zurückgibt.

Austin_Anderson
quelle