¿Vincular a un método en WPF?

90

¿Cómo se vincula a un método de objetos en este escenario en 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>

Aquí quiero vincularme al GetChildrenmétodo en cada uno RootObjectde los árboles.

EDITAR La vinculación a un ObjectDataProviderno parece funcionar porque estoy vinculando a una lista de elementos, y ObjectDataProvidernecesita un método estático o crea su propia instancia y la usa.

Por ejemplo, usando la respuesta de Matt obtengo:

Error de System.Windows.Data: 33: ObjectDataProvider no puede crear el objeto; Escriba = 'RootObject'; Error = 'Parámetros incorrectos para el constructor'.

Error de System.Windows.Data: 34: ObjectDataProvider: Error al intentar invocar el método en el tipo; Método = 'GetChildren'; Escriba = 'RootObject'; Error = 'El miembro especificado no se puede invocar en el destino'. TargetException: 'System.Reflection.TargetException: El método no estático requiere un objetivo.

Cameron MacFarland
fuente
Si, tienes razón. ObjectDataProvider tiene una propiedad ObjectInstance (para llamar a su método en una instancia específica) pero no creo que sea una propiedad de dependencia, por lo que no puede vincularla (AFAIK).
Matt Hamilton
1
Sí, traté de vincularme a ObjectInstance y descubrí que no es una propiedad de dependencia.
Cameron MacFarland
Dejaré mi respuesta allí de todos modos, tanto para darle un poco de contexto a su actualización como para ayudar a alguien más que encuentre esta pregunta con un problema bastante similar.
Matt Hamilton
¿Realmente necesitas enlazar a ObjectInstance? (¿Cambiará?) Suponiendo que, en su lugar, podría crear su propio manejo de eventos de cambio y actualizar ObjectDataProvider en el código ...
Tim Lovell-Smith
1
Acabo de actualizar mi respuesta con algún código fuente, un año después del hecho.
Drew Noakes

Respuestas:

71

Otro enfoque que podría funcionar para usted es crear una costumbre IValueConverterque tome un nombre de método como parámetro, de modo que se use así:

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

Este convertidor buscaría e invocaría el método mediante la reflexión. Esto requiere que el método no tenga argumentos.

Aquí hay un ejemplo de la fuente de dicho convertidor:

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

Y una prueba unitaria correspondiente:

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

Tenga en cuenta que este convertidor no aplica el targetTypeparámetro.

Dibujó Noakes
fuente
6
Hmmm, ... parece un truco pero estoy empezando a pensar que esta podría ser la única forma. ¡Seguro que será lo más fácil!
EightyOne Unite
25

No estoy seguro de qué tan bien funcionará en su escenario, pero puede usar la MethodNamepropiedad ObjectDataProviderpara que llame a un método específico (con parámetros específicos de ustedMethodParameters propiedad) para recuperar sus datos.

Aquí hay un fragmento tomado directamente de la página de 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>

Entonces eso es una ObjectDataProviderllamada a un ConvertTempmétodo en una instancia de una TemperatureScaleclase, pasando dos parámetros ( 0y TempType.Celsius).

Matt Hamilton
fuente
10

¿Tienes que ceñirte al método?

¿Se puede vincular a una propiedad cuyo método es getter?

public ObservableCollection<ChildObject> Children
{
   get
   {
      return GetChildren();
   }
}
Michael Prewecki
fuente
2
Supongo que el comentario de Cameron significa que está vinculado a un tipo al que no puede agregar una propiedad.
Drew Noakes
2
Debe evitar vincularse a propiedades que llaman a métodos, especialmente si el método podría ser de larga duración. Tener tales métodos de propiedades no es un buen diseño, ya que un consumidor de código espera que una propiedad solo acceda a una variable local.
markmnl
@markmnl entonces, ¿cuál es el punto de vincular directamente a la función? Entonces, ¿la pregunta de OP no tiene ningún sentido en su caso?
Teoman shipahi
4

A menos que pueda agregar una propiedad para llamar al método (o crear una clase contenedora que agregue esa propiedad), la única forma que conozco es usando un ValueConverter.

Nir
fuente
3

ObjectDataProvider también tiene una propiedad ObjectInstance que se puede usar en lugar de ObjectType

Graham Ambrose
fuente
3

Puedes usar System.ComponentModel para definir propiedades para un tipo de forma dinámica (no forman parte de los metadatos compilados). Utilicé este enfoque en WPF para habilitar la vinculación a un tipo que almacenaba sus valores en campos, ya que la vinculación a campos no es posible.

Los tipos ICustomTypeDescriptory TypeDescriptionProviderpueden permitirle lograr lo que desea. Según este artículo :

TypeDescriptionProviderle permite escribir una clase separada que implemente ICustomTypeDescriptory luego registrar esta clase como el proveedor de descripciones para otros tipos.

No he probado este enfoque por mí mismo, pero espero que sea útil en su caso.

Dibujó Noakes
fuente
0

Para enlazar al método de un objeto en su escenario de WPF, puede enlazar a una propiedad que devuelve un delegado.

Austin_Anderson
fuente