Existe-t-il un moyen de chaîner plusieurs convertisseurs de valeur en XAML?

123

J'ai une situation dans laquelle je dois afficher une valeur entière, liée à une propriété sur mon contexte de données, après l'avoir soumise à deux conversions distinctes:

  1. Inverser la valeur dans une plage (par exemple, la plage est de 1 à 100; la valeur dans datacontext est 90; l'utilisateur voit la valeur de 10)
  2. convertir le nombre en chaîne

Je me rends compte que je pourrais faire les deux étapes en créant mon propre convertisseur (qui implémente IValueConverter). Cependant, j'ai déjà un convertisseur de valeur séparé qui ne fait que la première étape, et la deuxième étape est couverte par Int32Converter.

Existe-t-il un moyen de chaîner ces deux classes existantes en XAML sans avoir à créer une autre classe qui les agrège?

Si j'ai besoin de clarifier quelque chose de cela, veuillez me le faire savoir. :)

Merci.

Mal Ross
la source

Réponses:

198

J'ai utilisé cette méthode par Gareth Evans dans mon projet Silverlight.

Voici ma mise en œuvre:

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
}

Qui peut ensuite être utilisé en XAML comme ceci:

<c:ValueConverterGroup x:Key="InvertAndVisibilitate">
   <c:BooleanInverterConverter/>
   <c:BooleanToVisibilityConverter/>
</c:ValueConverterGroup>
Ville
la source
3
Est-il préférable pour une implémentation de ConvertBack de faire une copie de la collection et de l'inverser, puis d'agréger sur cela? Donc, le ConvertBack seraitreturn this.Reverse<IValueConverter>().Aggregate(value, (current, converter) => converter.ConvertBack(current, targetType, parameter, culture));
Nick Udell
5
@DLeh Ce n'est pas vraiment élégant car cela ne fonctionne pas. Il fournit à tous les convertisseurs le type de cible final au lieu du type de cible correct ...
Aleksandar Toplek
Comment puis-je l'utiliser avec un MultiValueConverter comme premier convertisseur?
LightMonk
1
@Town Un collègue vient de trouver cette question et cela m'a incité à la chercher à nouveau, par nostalgie. Seulement, j'ai juste remarqué que vous n'obteniez pas le crédit que vous méritiez (j'avais accepté ma propre réponse!), Alors j'ai maintenant marqué votre réponse comme acceptée. Seulement environ 9 ans de retard ...: facepalm:
Mal Ross
@MalRoss Haha! Je vous remercie! C'est bien d'entendre que c'est toujours utile, je n'ai pas touché Silverlight maintenant depuis environ 8 de ces années et pourtant c'est toujours ma réponse la plus populaire :)
Ville
54

J'ai trouvé exactement ce que je cherchais, avec l'aimable autorisation de Josh Smith: Piping Value Converters (lien archive.org) .

Il définit une ValueConverterGroupclasse dont l'utilisation en XAML correspond exactement à ce que j'espérais. Voici un exemple:

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

Super truc. Merci Josh. :)

Mal Ross
la source
2
Dans cette solution, chaque convertisseur doit traiter un seul type (il doit être déclaré dans l'attribut single-ValueConversion). La solution @Town peut également faire face aux multi-convertisseurs.
Y. Shoham
9
veuillez poster la mise en œuvre; sinon, linkrot
Jake Berger
9

L'implémentation par Town du projet Silverlight de Gareth Evans est excellente, mais elle ne prend pas en charge différents paramètres de conversion.

Je l'ai modifié pour que vous puissiez fournir des paramètres séparés par des virgules (à moins que vous ne les échappiez bien sûr).

Convertisseur:

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

Remarque: ConvertBack n'est pas implémenté ici, voir mon Gist pour la version complète.

La mise en oeuvre:

<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
la source
6

Oui, il existe des moyens de chaîner les convertisseurs, mais cela n'a pas l'air joli et vous n'en avez pas besoin ici. Si jamais vous en avez besoin, demandez-vous si c'est vraiment la voie à suivre? Simple fonctionne toujours mieux même si vous devez écrire votre propre convertisseur.

Dans votre cas particulier, tout ce que vous avez à faire est de formater une valeur convertie en chaîne. StringFormatla propriété sur un Bindingest votre ami ici.

 <TextBlock Text="{Binding Value,Converter={StaticResource myConverter},StringFormat=D}" />
wpfwannabe
la source
5
Si vous utilisez beaucoup les liaisons, l'écriture d'un convertisseur personnalisé en convertisseurs en chaîne se termine par des tonnes de convertisseurs stupides pour toutes sortes de configurations. Dans ce cas, la réponse acceptée est une merveilleuse solution.
Jacek Gorgoń
0

Voici une petite extension de la réponse de Town pour prendre en charge la multi-liaison:

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