Existe uma maneira de encadear vários conversores de valor em XAML?

123

Eu tenho uma situação em que preciso mostrar um valor inteiro, vinculado a uma propriedade no meu contexto de dados, depois de colocá-lo em duas conversões separadas:

  1. Inverta o valor dentro de um intervalo (por exemplo, o intervalo é de 1 a 100; o valor no datacontext é 90; o usuário vê o valor 10)
  2. converter o número em uma string

Sei que poderia executar as duas etapas criando meu próprio conversor (que implementa o IValueConverter). No entanto, eu já tenho um conversor de valores separado que executa apenas o primeiro passo e o segundo passo é coberto pelo Int32Converter.

Existe uma maneira de encadear essas duas classes existentes no XAML sem precisar criar outra classe que as agregue?

Se precisar esclarecer alguma coisa, entre em contato. :)

Obrigado.

Mal Ross
fonte

Respostas:

198

Eu usei esse método por Gareth Evans no meu projeto Silverlight.

Aqui está minha implementação:

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 pode ser usado no XAML assim:

<c:ValueConverterGroup x:Key="InvertAndVisibilitate">
   <c:BooleanInverterConverter/>
   <c:BooleanToVisibilityConverter/>
</c:ValueConverterGroup>
Cidade
fonte
3
É melhor para uma implementação do ConvertBack fazer uma cópia da coleção e revertê-la e depois Agregar sobre isso? Portanto, o ConvertBack seriareturn this.Reverse<IValueConverter>().Aggregate(value, (current, converter) => converter.ConvertBack(current, targetType, parameter, culture));
Nick Udell
5
@DLeh Isso não é realmente elegante, pois não funciona. Ele fornece todos os conversores com tipo de destino final, em vez do tipo alvo correto ...
Aleksandar Toplek
Como posso usar isso com um MultiValueConverter como primeiro conversor?
LightMonk 17/01/19
1
@Town Um colega acabou de encontrar essa pergunta e me fez procurá-la novamente, pelo bem da nostalgia. Só que notei que você não estava recebendo o crédito que merecia (aceitei minha própria resposta!), Agora marquei sua resposta como aceita. Apenas cerca de 9 anos atrasado ...: facepalm:
Mal Ross
@MalRoss Haha! Obrigado! É bom ouvir ainda é útil, eu não toquei Silverlight agora para cerca de 8 desses anos e ainda esta ainda é a minha resposta mais popular :)
Cidade
54

Encontrei exatamente o que estava procurando, cortesia de Josh Smith: Piping Value Converters (link archive.org) .

Ele define uma ValueConverterGroupclasse, cujo uso em XAML é exatamente o que eu esperava. Aqui está um exemplo:

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

Coisas boas. Obrigado Josh. :)

Mal Ross
fonte
2
Nesta solução, cada conversor deve lidar com apenas um tipo (ele deve ser declarado no atributo ValueConversion único). A solução @Town também pode lidar com multiconversores.
Y. Shoham
9
poste a implementação; caso contrário, linkrot
Jake Berger
9

A implementação de Town do projeto Silverlight de Gareth Evans é excelente, no entanto, não suporta parâmetros de conversor diferentes.

Eu o modifiquei para que você possa fornecer parâmetros, delimitados por vírgulas (a menos que você escape deles, é claro).

Conversor:

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 não está implementado aqui, veja meu Gist para a versão completa.

Implementação:

<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
fonte
6

Sim, existem maneiras de encadear conversores, mas ele não parece bonito e você não precisa dele aqui. Se você precisar disso, pergunte a si mesmo: esse é realmente o caminho a seguir? Simples sempre funciona melhor, mesmo se você tiver que escrever seu próprio conversor.

No seu caso específico, tudo o que você precisa fazer é formatar um valor convertido em uma sequência. StringFormatpropriedade em um Bindingé seu amigo aqui.

 <TextBlock Text="{Binding Value,Converter={StaticResource myConverter},StringFormat=D}" />
wpfwannabe
fonte
5
Se você usar muito as ligações, escrever um conversor personalizado em conversores em cadeia acaba com toneladas de conversores mudos para todos os tipos de configurações. Nesse caso, a resposta aceita é uma solução maravilhosa.
Jacek Gorgoń
0

Aqui está uma pequena extensão da resposta da Town para apoiar a ligação múltipla:

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
fonte