Czy istnieje sposób na połączenie wielu konwerterów wartości w XAML?

123

Mam sytuację, w której muszę pokazać wartość całkowitą, powiązaną z właściwością w moim kontekście danych, po przeprowadzeniu dwóch oddzielnych konwersji:

  1. Odwróć wartość w zakresie (np. Zakres wynosi od 1 do 100; wartość w kontekście danych to 90; użytkownik widzi wartość 10)
  2. przekonwertuj liczbę na ciąg

Zdaję sobie sprawę, że mógłbym wykonać oba kroki, tworząc własny konwerter (który implementuje IValueConverter). Jednak mam już osobny konwerter wartości, który wykonuje tylko pierwszy krok, a drugi krok jest objęty przez Int32Converter.

Czy istnieje sposób, aby połączyć te dwie istniejące klasy w XAML bez konieczności tworzenia kolejnej klasy, która je agreguje?

Jeśli muszę coś z tego wyjaśnić, daj mi znać. :)

Dzięki.

Mal Ross
źródło

Odpowiedzi:

198

Użyłem tej metody Garetha Evansa w moim projekcie Silverlight.

Oto moja implementacja:

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
}

Które można następnie użyć w języku XAML w następujący sposób:

<c:ValueConverterGroup x:Key="InvertAndVisibilitate">
   <c:BooleanInverterConverter/>
   <c:BooleanToVisibilityConverter/>
</c:ValueConverterGroup>
Miasto
źródło
3
Czy najlepiej jest, aby implementacja ConvertBack utworzyła kopię kolekcji i odwróciła ją, a następnie Aggregate nad tym? Więc ConvertBack będziereturn this.Reverse<IValueConverter>().Aggregate(value, (current, converter) => converter.ConvertBack(current, targetType, parameter, culture));
Nick Udell
5
@DLeh To nie jest zbyt eleganckie, ponieważ nie działa. Zapewnia wszystkim konwerterom ostateczny typ celu zamiast prawidłowego typu celu ...
Aleksandar Toplek
Jak mogę tego użyć z MultiValueConverter jako pierwszym konwerterem?
LightMonk
1
@Town Kolega właśnie znalazł to pytanie i sprawiło, że ponownie je sprawdziłem, ze względu na nostalgię. Tylko zauważyłem, że nie otrzymałeś zasługi, na którą zasługiwałeś (zaakceptowałem własną odpowiedź!), Więc oznaczyłem twoją odpowiedź jako zaakceptowaną. Tylko około 9 lat późno ...: facepalm:
Mal Ross
@MalRoss Haha! Dziękuję Ci! Dobrze słyszeć, że nadal jest przydatny, nie dotykałem Silverlight od około 8 lat, a mimo to jest to nadal moja najpopularniejsza odpowiedź :)
Miasto
54

Znalazłem dokładnie to, czego szukałem, dzięki uprzejmości Josha Smitha: Piping Value Converters (link do archive.org) .

Definiuje ValueConverterGroupklasę, której użycie w XAML jest dokładnie takie, na jakie liczyłem. Oto przykład:

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

Świetne rzeczy. Dzięki, Josh. :)

Mal Ross
źródło
2
W tym rozwiązaniu każdy konwerter musi obsługiwać tylko jeden typ (musi być zadeklarowany w pojedynczym atrybucie ValueConversion). Rozwiązanie @Town radzi sobie również z multikonwerterami.
Y. Shoham
9
prosimy o przesłanie realizacji; w przeciwnym razie linkrot
Jake Berger
9

Realizacja miasteczka z projektu Garetha Evansa Silverlight jest wielka, jednak nie obsługuje różne parametry konwertera.

Zmodyfikowałem go, abyś mógł podać parametry rozdzielane przecinkami (chyba że je unikniesz).

Przetwornik:

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

Uwaga: ConvertBack nie jest tutaj zaimplementowany, zobacz moją Gist dla pełnej wersji.

Realizacja:

<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
źródło
6

Tak, są sposoby na łańcuchowe konwertery, ale nie wygląda to ładnie i nie potrzebujesz tego tutaj. Jeśli kiedykolwiek będziesz tego potrzebować, zadaj sobie pytanie, czy to naprawdę właściwa droga? Prosty zawsze działa lepiej, nawet jeśli musisz napisać własny konwerter.

W twoim konkretnym przypadku wszystko, co musisz zrobić, to sformatować przekonwertowaną wartość na ciąg. StringFormatnieruchomość na a Bindingjest tutaj twoim przyjacielem.

 <TextBlock Text="{Binding Value,Converter={StaticResource myConverter},StringFormat=D}" />
wpfwannabe
źródło
5
Jeśli intensywnie używasz powiązań, pisanie niestandardowych konwerterów do konwerterów łańcuchowych kończy się tonami głupich konwerterów dla różnych konfiguracji. W takim przypadku zaakceptowana odpowiedź jest wspaniałym rozwiązaniem.
Jacek Gorgoń
0

Oto małe rozszerzenie odpowiedzi Town na obsługę wielu wiązań:

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
źródło