[WPF]複数のValueConverterを連結して順番に変換する

WPF等のXAMLファミリーのBindingで、バインド元とバインド先の値の型が異なる場合、
値コンバータクラスを利用して変換をかける。

例えば、IsVisible みたいな名前のboolプロパティによって、trueになったら表示、falseになったら非表示というようなバインドをしたい場合は BooleanToVisibilityConverter を使うことになる。

BooleanToVisibilityConverterは標準で用意されているクラスだが、同様に、たとえばColor→Brushの変換をしたければ、
IValueConverterインタフェースを実装したColorToBrushConverterクラスを自作してやればよい。

[ValueConversion(typeof(Color), typeof(Brush))]
public class ColorToBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null || !(value is Color)) return DependencyProperty.UnsetValue;

        var col = (Color)value;
        return new SolidColorBrush(col);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var brush = value as SolidColorBrush;
        if (brush == null) return DependencyProperty.UnsetValue;

        return brush.Color;
    }
}

では、たとえば string→Color→Brushのように、複数の変換をリレーしたい場合はどうするか。

話はかんたんで、StringToBrushConverterクラスを作って、
内部でStringToColorConverterとColorToBrushConverterを順に呼んでやればいい。

しかし、どうせなら、汎用的なリレーコンバートクラスを作っておきたいですね。

調べてみると、当然ながら先人がいる。

Piping Value Converters in WPF – CodeProject

しかし、アイデア自体は単純なので、まずはシンプルに自作してみよう。
 

先に、StringToColorConverterを作っておく。
「あか」「あお」「きいろ」の文字列だけ変換する雑なもの。

[ValueConversion(typeof(string), typeof(Color))]
public class StringToColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var str = value as string;
        if (str == null) return DependencyProperty.UnsetValue;

        switch (str)
        {
            case "あか":
                return Colors.Red;
            case "あお":
                return Colors.Blue;
            case "きいろ":
                return Colors.Yellow;
            default:
                return Colors.Black;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null || !(value is Color)) return DependencyProperty.UnsetValue;

        var col = (Color)value;

        return col == Colors.Red ? "あか"
             : col == Colors.Blue ? "あお"
             : col == Colors.Yellow ? "きいろ"
             : DependencyProperty.UnsetValue;
    }
}

 

さてでは、このStringToColorConverterとColorToBrushConverterを連結するためのコンバータを作る。

[ContentProperty(nameof(Converters))]
public class ValueConverterGroup : IValueConverter
{
    public Collection<IValueConverter> Converters { get; } = new Collection<IValueConverter>();

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var result = value;

        if (Converters == null) return result;

        foreach (var conv in Converters)
        {
            result = conv.Convert(result, targetType, parameter, culture);
        }

        return result;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var result = value;

        if (Converters == null) return result;

        foreach (var conv in Converters.Reverse())
        {
            result = conv.ConvertBack(result, targetType, parameter, culture);
        }

        return result;
    }
}

targetTypeのあたりがだいぶ不真面目ですね。
先ほどのリンク先のコードではもうちょっとちゃんとやってた。

まあともかく、↓こんなふうに使います。

<Window x:Class="ValueConverterPipeSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ValueConverterPipeSample"
        Height="100" Width="300" FontSize="16">
    <Window.Resources>
        <local:ValueConverterGroup x:Key="stringToBrushConverter">
            <local:StringToColorConverter />
            <local:ColorToBrushConverter />
        </local:ValueConverterGroup>
    </Window.Resources>
    <StackPanel Orientation="Vertical">
        <!--入力欄-->
        <TextBox Name="_textBox" />
        <!--入力された文字列を文字色に反映して表示-->
        <TextBlock Text="「あか」と入れれば赤くなるよ"
                   Foreground="{Binding ElementName=_textBox, Path=Text, Converter={StaticResource stringToBrushConverter}}"/>
    </StackPanel>
</Window>

実行。


 
以上。