Lier à une méthode dans WPF?

90

Comment liez-vous à une méthode d'objets dans ce scénario dans 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>

Ici, je veux me lier à la GetChildrenméthode sur chacun RootObjectdes arbres.

EDIT La liaison à un ObjectDataProviderne semble pas fonctionner parce que je me lie à une liste d'éléments et que les ObjectDataProviderbesoins soit une méthode statique, soit elle crée sa propre instance et l'utilise.

Par exemple, en utilisant la réponse de Matt, j'obtiens:

Erreur System.Windows.Data: 33: ObjectDataProvider ne peut pas créer d'objet; Type = 'objet racine'; Error = 'Mauvais paramètres pour le constructeur.'

Erreur System.Windows.Data: 34: ObjectDataProvider: échec de la tentative d'appel de la méthode sur le type; Méthode = 'GetChildren'; Type = 'objet racine'; Error = 'Le membre spécifié ne peut pas être appelé sur la cible.' TargetException: 'System.Reflection.TargetException: La méthode non statique nécessite une cible.

Cameron MacFarland
la source
Oui vous avez raison. ObjectDataProvider a une propriété ObjectInstance (pour appeler sa méthode sur une instance spécifique) mais je ne pense pas que ce soit une propriété de dépendance, vous ne pouvez donc pas la lier (AFAIK).
Matt Hamilton
1
Ouais, j'ai essayé de me lier à ObjectInstance et j'ai découvert que ce n'était pas une propriété de dépendance.
Cameron MacFarland le
Je laisserai ma réponse là de toute façon, à la fois pour donner un peu de contexte à votre mise à jour et pour aider quelqu'un d'autre qui trouve cette question avec un problème assez similaire.
Matt Hamilton
Avez-vous réellement besoin de vous lier à ObjectInstance? (Cela changera-t-il) En supposant que vous puissiez à la place créer votre propre gestion des événements de changement et mettre à jour l'ObjectDataProvider dans le code ...
Tim Lovell-Smith
1
Je viens de mettre à jour ma réponse avec du code source, un an après le fait.
Drew Noakes le

Réponses:

71

Une autre approche qui pourrait fonctionner pour vous est de créer une personnalisation IValueConverterqui prend un nom de méthode en tant que paramètre, afin qu'elle soit utilisée comme ceci:

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

Ce convertisseur trouverait et invoquerait la méthode en utilisant la réflexion. Cela nécessite que la méthode n'ait aucun argument.

Voici un exemple de la source d'un tel convertisseur:

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

Et un test unitaire correspondant:

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

Notez que ce convertisseur n'applique pas le targetTypeparamètre.

Drew Noakes
la source
6
Hmmm, ... cela ressemble à un hack mais je commence à penser que c'est peut-être le seul moyen. Ce sera sûrement le plus simple!
EightyOne Unite
25

Vous ne savez pas comment cela fonctionnera dans votre scénario, mais vous pouvez utiliser la MethodNamepropriété sur ObjectDataProviderpour lui faire appeler une méthode spécifique (avec des paramètres spécifiques de votre MethodParameterspropriété) pour récupérer ses données.

Voici un extrait de code extrait directement de la page 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>

C'est donc ObjectDataProviderune ConvertTempméthode qui appelle une méthode sur une instance d'une TemperatureScaleclasse, en passant deux paramètres ( 0et TempType.Celsius).

Matt Hamilton
la source
10

Devez-vous vous lier à la méthode?

Pouvez-vous vous lier à une propriété dont le getter est la méthode?

public ObservableCollection<ChildObject> Children
{
   get
   {
      return GetChildren();
   }
}
Michael Prewecki
la source
2
Je suppose que le commentaire de Cameron signifie qu'il est lié à un type auquel il ne peut pas ajouter de propriété.
Drew Noakes
2
Vous devez éviter de lier des propriétés qui appellent des méthodes, surtout si la méthode peut être longue. Le fait d'avoir de telles méthodes ses propriétés n'est pas une bonne conception car un consommateur de code s'attend à ce qu'une propriété n'accède qu'à une variable locale.
markmnl
@markmnl alors quel est l'intérêt de se lier directement à la fonction? La question d'OP n'a donc aucun sens dans votre cas?
Teoman shipahi
4

À moins que vous ne puissiez ajouter une propriété pour appeler la méthode (ou créer une classe wrapper qui ajoute cette propriété), le seul moyen que je connaisse est d'utiliser un ValueConverter.

Nir
la source
3

ObjectDataProvider a également une propriété ObjectInstance qui peut être utilisée à la place de ObjectType

Graham Ambrose
la source
3

Vous pouvez utiliser System.ComponentModelpour définir dynamiquement les propriétés d'un type (elles ne font pas partie des métadonnées compilées). J'ai utilisé cette approche dans WPF pour activer la liaison à un type qui stockait ses valeurs dans des champs, car la liaison aux champs n'est pas possible.

Les types ICustomTypeDescriptoret TypeDescriptionProviderpeuvent vous permettre d'obtenir ce que vous voulez. D'après cet article :

TypeDescriptionProvidervous permet d'écrire une classe distincte qui implémente ICustomTypeDescriptor, puis d'enregistrer cette classe en tant que fournisseur de descriptions pour d'autres types.

Je n'ai pas essayé cette approche moi-même, mais j'espère qu'elle vous sera utile dans votre cas.

Drew Noakes
la source
0

Pour lier à la méthode d'un objet dans votre scénario WPF, vous pouvez lier à une propriété qui retourne un délégué.

Austin_Anderson
la source