有没有一种方法可以在XAML中链接多个值转换器?

139

我有一个情况,需要展示一个整数值,绑定到我的数据上下文中的属性,并经过两个独立的转换:

  1. 在一个区间内反转值(例如:范围是1到100;数据上下文中的值为90;用户看到的值为10)
  2. 将数字转换为字符串

我意识到我可以通过创建自己的转换器(实现IValueConverter接口)来完成这两个步骤。然而,我已经有一个单独的值转换器只进行第一步,而第二步则由Int32Converter完成。

是否有一种方法可以在XAML中链接这两个现有类,而无需创建一个进一步聚合它们的类?

如果我需要澄清任何内容,请告诉我。:)

谢谢。

6个回答

217

我在我的Silverlight项目中使用了Gareth Evans的这个方法

以下是我的实现:

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>

5
在实现ConvertBack时,是否最好复制集合并将其反转,然后对其进行聚合?因此,ConvertBack应为return this.Reverse<IValueConverter>().Aggregate(value, (current, converter) => converter.ConvertBack(current, targetType, parameter, culture)); - Nick Udell
5
@DLeh 这不是很优雅,因为它不能正常工作。它提供了最终目标类型给所有的转换器,而不是正确的目标类型... - Aleksandar Toplek
我该如何将其作为第一个转换器与MultiValueConverter一起使用? - LightMonk
1
@Town 一个同事刚刚发现了这个问题,让我为怀旧之情再次查找它。只是,我刚刚注意到你没有得到应有的荣誉(我接受了我的答案!),所以我现在将你的答案标记为已接受。只是晚了大约9年... :facepalm: - Mal Ross
@MalRoss 哈哈!谢谢!听到它仍然有用很好,我已经有大约8年没有接触Silverlight了,但这仍然是我的最受欢迎的答案 :) - Town
1
这种实现的缺点是所有转换器的目标类型必须相同。我建议查看Mal Ross的答案 - Katjoek

55

通过Josh Smith的贡献,我找到了我需要的东西:WPF中的管道值转换器(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> 

很棒的东西。谢谢,Josh. :)


2
在这个解决方案中,每个转换器只能处理一种类型(必须在single-ValueConversion属性中声明)。@Town的解决方案也可以处理多个转换器。 - Yaakov Shoham
16
请发布实现代码,否则会出现链接失效的情况。 - Jake Berger

10

城镇的实现 Gareth Evans的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;
    }
}
< p >注意:此处未实现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
是的,有方法可以链接转换器,但它看起来并不美观,而且你在这里不需要它。如果你曾经需要这样做,请问自己这真的是正确的方式吗?即使你必须编写自己的转换器,简单总是更好。
在你的特定情况下,你只需要将转换后的值格式化为字符串。在Binding上使用StringFormat属性即可。
 <TextBlock Text="{Binding Value,Converter={StaticResource myConverter},StringFormat=D}" />

5
如果您经常使用绑定(bindings),编写自定义转换器来链接转换器会导致大量的愚蠢转换器用于各种配置。在这种情况下,被接受的答案是一个很好的解决方案。 - Jacek Gorgoń

2
这是对 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
}

0

这是对Town's answer的另一种扩展。根据我的特定需求,我需要传递特定类型的参数,而像Trevi's answer这样基于字符串的解决方案不支持此功能。

我的解决方案需要一些冗长,但作为XAML用户,我们对此并不陌生 ;)

public class ValueConverterChainParameters : List<object>
{
    
}

public class ValueConverterChain : List<IValueConverter>, IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (parameter is not (null or ValueConverterChainParameters))
        {
            throw new ApplicationException($"{nameof(ValueConverterChain)} parameter must be empty/null or a {nameof(ValueConverterChainParameters)} instance where each element is the parameter to pass to the corresponding converter.");
        }

        ValueConverterChainParameters parameterList = parameter as ValueConverterChainParameters;

        return this
            .Select((converter, index) => (converter, index))
            .Aggregate(value, (currentValue, element) =>
            {
                (IValueConverter converter, int index) = element;
                return converter.Convert(currentValue, targetType, parameterList?[index], culture);
            });
    }

以下是用法。在这种情况下(我知道有点牵强),模板父级上的属性MyAngle因某种原因是一个字符串。更改类型转换器只需执行return System.Convert.ChangeType(value, targetType);以获取期望在Angle属性上使用的double类型。然后将其传递给我的乘法转换器,该转换器将使用参数进行乘法运算。

<RotateTransform >
    <RotateTransform.Angle>
        <Binding Path="MyAngle" RelativeSource="{RelativeSource TemplatedParent}">
            <Binding.Converter>
                <conv:ValueConverterChain>
                    <conv:ChangeTypeConverter/>
                    <conv:MultiplyConverter/>
                </conv:ValueConverterChain>
            </Binding.Converter>
            <Binding.ConverterParameter>
                <conv:ValueConverterChainParameters>
                    <x:Null/>
                    <sys:Int32>-1</sys:Int32>
                </conv:ValueConverterChainParameters>
            </Binding.ConverterParameter>
        </Binding>
    </RotateTransform.Angle>
</RotateTransform>

是的,它有点冗长,但支持在不想传递参数和传递特定类型参数时传递null


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接