OITA: Oika's Information Technological Activities

@oika 情報技術的活動日誌。

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>  

実行。

以上。