Есть ли способ связать несколько преобразователей значений в XAML?

123

У меня есть ситуация, в которой мне нужно показать целочисленное значение, привязанное к свойству в моем контексте данных, после двух отдельных преобразований:

  1. Поменяйте местами значение в пределах диапазона (например, диапазон от 1 до 100; значение в контексте данных - 90; пользователь видит значение 10)
  2. преобразовать число в строку

Я понимаю, что могу выполнить оба шага, создав свой собственный конвертер (который реализует IValueConverter). Однако у меня уже есть отдельный преобразователь значений, который выполняет только первый шаг, а второй шаг покрывается Int32Converter.

Есть ли способ связать эти два существующих класса в XAML без необходимости создавать дополнительный класс, который их объединяет?

Если мне нужно что-то прояснить, дайте мне знать. :)

Спасибо.

Мал Росс
источник

Ответы:

198

Я использовал этот метод Гарета Эванса в моем проекте Silverlight.

Вот моя реализация:

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
}

Что затем можно использовать в XAML следующим образом:

<c:ValueConverterGroup x:Key="InvertAndVisibilitate">
   <c:BooleanInverterConverter/>
   <c:BooleanToVisibilityConverter/>
</c:ValueConverterGroup>
Город
источник
3
Лучше ли для реализации ConvertBack сделать копию коллекции и отменить ее, а затем выполнить агрегирование по этому поводу? Итак, ConvertBack будетreturn this.Reverse<IValueConverter>().Aggregate(value, (current, converter) => converter.ConvertBack(current, targetType, parameter, culture));
Ник Уделл
5
@DLeh Это не очень элегантно, потому что не работает. Он предоставляет всем конвертерам окончательный тип цели вместо правильного типа цели ...
Александр Топлек
Как я могу использовать это с MultiValueConverter в качестве первого конвертера?
LightMonk
1
@Town Коллега только что нашел этот вопрос, и ради ностальгии он заставил меня поискать его снова. Только я только что заметил, что вы не получили должного внимания (я принял свой ответ!), Поэтому теперь я пометил ваш ответ как принятый. Всего лишь на 9 лет позже ...: facepalm:
Мэл Росс
@MalRoss Ха-ха! Спасибо! Приятно слышать, что это все еще полезно, я не касался Silverlight уже около 8 из этих лет, и все же это все еще мой самый популярный ответ :)
Town
54

Нашел именно то, что искал, любезно предоставленный Джошем Смитом: преобразователи ценности трубопроводов (ссылка на archive.org) .

Он определяет ValueConverterGroupкласс, использование которого в XAML в точности соответствует моим ожиданиям. Вот пример:

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

Отличный материал. Спасибо, Джош. :)

Мал Росс
источник
2
В этом решении каждый преобразователь должен иметь дело только с одним типом (он должен быть объявлен в атрибуте single-ValueConversion). Решение @Town может справиться и с мультиконвертерами.
Y. Shoham
9
опубликуйте, пожалуйста, реализацию; в противном случае, линкрот
Джейк Бергер
9

Реализация города из проекта Silverlight Гарета Эванса велик, однако он не поддерживает различные параметры преобразователя.

Я изменил его, чтобы вы могли указывать параметры, разделенные запятыми (если, конечно, вы их не избегаете).

Преобразователь:

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

Примечание: ConvertBack не реализован здесь, увидеть мой GIST для полной версии.

Реализация:

<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

Да, есть способы связать преобразователи в цепочку, но это выглядит некрасиво и здесь вам не нужно. Если вам когда-нибудь это понадобится, спросите себя, действительно ли это правильный путь? Простой всегда работает лучше, даже если вам нужно написать свой собственный конвертер.

В вашем конкретном случае все, что вам нужно сделать, это отформатировать преобразованное значение в строку. StringFormatнедвижимость на это Bindingваш друг.

 <TextBlock Text="{Binding Value,Converter={StaticResource myConverter},StringFormat=D}" />
wpfwannabe
источник
5
Если вы интенсивно используете привязки, написание пользовательского преобразователя в цепные преобразователи заканчивается множеством «глупых» преобразователей для всех видов конфигураций. В таком случае принятый ответ - прекрасное решение.
Яцек Горгонь
0

Вот небольшое расширение ответа Town для поддержки множественной привязки:

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
}
Аарон
источник