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.

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>

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. :)


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>

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}" />

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
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.