Vincular a um método no WPF?

90

Como você vincula a um método de objetos neste cenário no 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>

Aqui, desejo vincular ao GetChildrenmétodo em cada uma RootObjectdas árvores.

EDITAR Vincular a um ObjectDataProvidernão parece funcionar porque estou vinculando a uma lista de itens e ObjectDataProviderprecisa de um método estático ou cria sua própria instância e usa isso.

Por exemplo, usando a resposta de Matt, recebo:

Erro System.Windows.Data: 33: ObjectDataProvider não pode criar objeto; Tipo = 'RootObject'; Erro = 'Parâmetros errados para o construtor.'

Erro System.Windows.Data: 34: ObjectDataProvider: falha ao tentar invocar o método no tipo; Método = 'GetChildren'; Tipo = 'RootObject'; Erro = 'O membro especificado não pode ser chamado no destino.' TargetException: 'System.Reflection.TargetException: O método não estático requer um destino.

Cameron MacFarland
fonte
Sim você está certo. ObjectDataProvider tem uma propriedade ObjectInstance (para chamar seu método em uma instância específica), mas não acho que seja uma propriedade de dependência, então você não pode vinculá-la (AFAIK).
Matt Hamilton
1
Sim, eu tentei vincular a ObjectInstance e descobri que não é uma propriedade de dependência.
Cameron MacFarland
Vou deixar minha resposta lá de qualquer maneira, para dar algum contexto à sua atualização e para ajudar alguém que encontre esta questão com um problema semelhante.
Matt Hamilton
Você realmente precisa vincular a ObjectInstance? (Será que vai mudar) Supondo que você possa, em vez disso, criar sua própria manipulação de eventos de mudança e atualizar o ObjectDataProvider no código ...
Tim Lovell-Smith
1
Acabei de atualizar minha resposta com algum código-fonte, um ano após o fato.
Drew Noakes em

Respostas:

71

Outra abordagem que pode funcionar para você é criar um personalizado IValueConverterque tenha um nome de método como parâmetro, de modo que seja usado assim:

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

Este conversor iria encontrar e invocar o método usando reflexão. Isso requer que o método não tenha argumentos.

Aqui está um exemplo de fonte de tal conversor:

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

E um teste de unidade correspondente:

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

Observe que este conversor não impõe o targetTypeparâmetro.

Drew Noakes
fonte
6
Hmmm, ... parece um hack, mas estou começando a pensar que essa pode ser a única maneira. Com certeza vai ser o mais fácil!
EightyOne Unite
25

Não tenho certeza se funcionará bem em seu cenário, mas você pode usar a MethodNamepropriedade on ObjectDataProviderpara que ele chame um método específico (com parâmetros específicos de sua MethodParameterspropriedade) para recuperar seus dados.

Aqui está um snippet retirado diretamente da página do 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>

Então, ObjectDataProviderisso está chamando um ConvertTempmétodo em uma instância de uma TemperatureScaleclasse, passando dois parâmetros ( 0eTempType.Celsius ).

Matt Hamilton
fonte
10

Você tem que se vincular ao método?

Você pode vincular a uma propriedade cujo método é getter?

public ObservableCollection<ChildObject> Children
{
   get
   {
      return GetChildren();
   }
}
Michael Prewecki
fonte
2
Considero o comentário de Cameron como significando que ele está vinculado a um tipo ao qual não pode adicionar uma propriedade.
Drew Noakes em
2
Você deve evitar a ligação a propriedades que chamam métodos, especialmente se o método puder ser de longa duração. Ter essas propriedades de métodos não é um bom design, pois um consumidor de código espera que uma propriedade acesse apenas uma variável local.
markmnl
@markmnl então qual é o ponto de ligação direta para funcionar? Então a pergunta da OP não faz sentido no seu caso?
Teoman shipahi
4

A menos que você possa adicionar uma propriedade para chamar o método (ou criar uma classe de wrapper que adiciona essa propriedade), a única maneira que conheço é usando um ValueConverter.

Nir
fonte
3

ObjectDataProvider também tem uma propriedade ObjectInstance que pode ser usada em vez de ObjectType

Graham Ambrose
fonte
3

Você pode usar System.ComponentModel para definir propriedades para um tipo dinamicamente (eles não fazem parte dos metadados compilados). Usei essa abordagem no WPF para permitir a vinculação a um tipo que armazenava seus valores em campos, já que a vinculação a campos não é possível.

Os tipos ICustomTypeDescriptore TypeDescriptionProviderpodem permitir que você alcance o que deseja. De acordo com este artigo :

TypeDescriptionProviderpermite que você escreva uma classe separada que implemente ICustomTypeDescriptore, em seguida, registre essa classe como o provedor de descrições para outros tipos.

Eu não tentei essa abordagem, mas espero que seja útil no seu caso.

Drew Noakes
fonte
0

Para vincular ao método de um objeto em seu cenário WPF, você pode vincular a uma propriedade que retorna um delegado.

Austin_Anderson
fonte