¿Hay alguna forma de encadenar varios convertidores de valor en XAML?

123

Tengo una situación en la que necesito mostrar un valor entero, vinculado a una propiedad en mi contexto de datos, después de someterlo a dos conversiones separadas:

  1. Invierta el valor dentro de un rango (por ejemplo, el rango es de 1 a 100; el valor en el contexto de datos es 90; el usuario ve el valor de 10)
  2. convierte el número en una cadena

Me doy cuenta de que podría hacer ambos pasos creando mi propio convertidor (que implementa IValueConverter). Sin embargo, ya tengo un convertidor de valores separado que hace solo el primer paso, y el segundo paso está cubierto por Int32Converter.

¿Hay alguna manera de que pueda encadenar estas dos clases existentes en XAML sin tener que crear una clase adicional que las agregue?

Si necesito aclarar algo de esto, hágamelo saber. :)

Gracias.

Mal Ross
fuente

Respuestas:

198

Usé este método de Gareth Evans en mi proyecto Silverlight.

Aquí está mi implementación:

public class ValueConverterGroup : List<IValueConverter>, IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return this.Aggregate(value, (current, converter) => converter.Convert(current, targetType, parameter, culture));
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

Que luego se puede usar en XAML de esta manera:

<c:ValueConverterGroup x:Key="InvertAndVisibilitate">
   <c:BooleanInverterConverter/>
   <c:BooleanToVisibilityConverter/>
</c:ValueConverterGroup>
Pueblo
fuente
3
¿Es mejor para una implementación de ConvertBack hacer una copia de la colección y revertirla, y luego agregar sobre eso? Entonces el ConvertBack seríareturn this.Reverse<IValueConverter>().Aggregate(value, (current, converter) => converter.ConvertBack(current, targetType, parameter, culture));
Nick Udell
5
@DLeh Esto no es realmente elegante ya que no funciona. Proporciona a todos los convertidores el tipo de objetivo final en lugar del tipo de objetivo correcto ...
Aleksandar Toplek
¿Cómo puedo usar esto con un MultiValueConverter como primer convertidor?
LightMonk
1
@ Town Un colega acaba de encontrar esta pregunta y me hizo buscarla de nuevo, por nostalgia. Solo que me di cuenta de que no recibías el crédito que merecías (¡había aceptado mi propia respuesta!), Así que ahora he marcado tu respuesta como aceptada. Solo unos 9 años tarde ...: facepalm:
Mal Ross
@MalRoss ¡Jaja! ¡Gracias! Es bueno escuchar que todavía es útil, no he tocado Silverlight ahora durante aproximadamente 8 de esos años y, sin embargo, esta sigue siendo mi respuesta más popular :)
Town
54

Encontré exactamente lo que estaba buscando, cortesía de Josh Smith: Piping Value Converters (enlace de archive.org) .

Él define una ValueConverterGroupclase, cuyo uso en XAML es exactamente como esperaba. He aquí un ejemplo:

<!-- Converts the Status attribute text to a SolidColorBrush used to draw 
     the output of statusDisplayNameGroup. -->
<local:ValueConverterGroup x:Key="statusForegroundGroup">
  <local:IntegerStringToProcessingStateConverter  />
  <local:ProcessingStateToColorConverter />
  <local:ColorToSolidColorBrushConverter />
</local:ValueConverterGroup> 

Buena cosa. Gracias, Josh. :)

Mal Ross
fuente
2
En esta solución, cada convertidor debe tratar con un solo tipo (debe declararse en el atributo single-ValueConversion). La solución @Town también puede hacer frente a los inversores múltiples.
Y. Shoham
9
publique la implementación; de lo contrario, linkrot
Jake Berger
9

La implementación de Town del proyecto Silverlight de Gareth Evans es excelente, sin embargo, no admite diferentes parámetros del convertidor.

Lo modifiqué para que pueda proporcionar parámetros, delimitados por comas (a menos que los escape, por supuesto).

Convertidor:

public class ValueConverterGroup : List<IValueConverter>, IValueConverter
{
    private string[] _parameters;

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if(parameter != null)
            _parameters = Regex.Split(parameter.ToString(), @"(?<!\\),");

        return (this).Aggregate(value, (current, converter) => converter.Convert(current, targetType, GetParameter(converter), culture));
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    private string GetParameter(IValueConverter converter)
    {
        if (_parameters == null)
            return null;

        var index = IndexOf(converter as IValueConverter);
        string parameter;

        try
        {
            parameter = _parameters[index];
        }

        catch (IndexOutOfRangeException ex)
        {
            parameter = null;
        }

        if (parameter != null)
            parameter = Regex.Unescape(parameter);

        return parameter;
    }
}

Nota: ConvertBack no se implementa aquí, consulte mi Gist para obtener la versión completa.

Implementación:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:converters="clr-namespace:ATXF.Converters;assembly=ATXF" x:Class="ATXF.TestPage">
  <ResourceDictionary>
    <converters:ValueConverterGroup x:Key="converters">
      <converters:ConverterOne />
      <converters:ConverterTwo />
    </converters:ValueConverterGroup>
  </ResourceDictionary>

  <Label Text="{Binding InitialValue, Converter={StaticResource converters}, ConverterParameter='Parameter1,Parameter2'}" />
</ContentPage>
Trevi Awater
fuente
6

Sí, hay formas de encadenar convertidores, pero no se ve bonito y no lo necesita aquí. Si alguna vez llega a necesitar esto, pregúntese si realmente es ese el camino a seguir. Simple siempre funciona mejor incluso si tienes que escribir tu propio convertidor.

En su caso particular, todo lo que necesita hacer es formatear un valor convertido en una cadena. StringFormatla propiedad en un Bindinges su amigo aquí.

 <TextBlock Text="{Binding Value,Converter={StaticResource myConverter},StringFormat=D}" />
wpfwannabe
fuente
5
Si usa mucho los enlaces, escribir convertidores personalizados en convertidores de cadena termina con toneladas de convertidores tontos para todo tipo de configuraciones. En ese caso, la respuesta aceptada es una solución maravillosa.
Jacek Gorgoń
0

Aquí hay una pequeña extensión de la respuesta de Town para admitir enlaces múltiples:

public class ValueConverterGroup : List<IValueConverter>, IValueConverter, IMultiValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return this.Aggregate(value, (current, converter) => converter.Convert(current, targetType, parameter, culture));
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        return Convert(values as object, targetType, parameter, culture);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}
Aaron
fuente