IValueConverterとTypeConverterの違い

WPFでバインディングをしていると登場する、似て非なる2つのConverterのおはなし。

それらはIValueConverterとTypeConverterです。私的な印象を言うと、IValueConverterはよく使うけどTypeConverterはあんまり使わない、IValueConverterは双方向の変換を用意することもあるがTypeConverterなら一方向だけの変換しか使わない、そんな感じ。

まずは今回のコンバーターで使うための簡単なクラスを定義しておきます。

ConversionNeeded.cs(1)

namespace WpfConvertersDifference
{
    using System;
    using System.ComponentModel;

    public class ConversionNeeded
    {
        public int inInt { get; set; }
        public string inString { get; set; }
        public double inDouble { get; set; }

        public override string ToString()
        {
            return inString + inInt + inDouble;
        }
    }
}

 

1.IValueConverterの作成

IValueConverterはバインディングをするときに値の変換を行うためのものです。前回・前々回で説明したとおり、データバインディングでは値をバインドのUIに渡しますが、その元の値がバインド先の型と違う時に変換するために用います。WPFのバインディングはバインド先は依存関係プロパティである必要があるため、IValueConverterは何かの型から依存関係プロパティの型に変換するものとなります。バインド先の型がstring型であると仮定して上のテスト用クラスからstringへの変換をするIValueConverterを実装するクラスを作ります。

InheritIValueConverter.cs

namespace WpfConvertersDifference
{
    using System;
    using System.Windows.Data;

    [ValueConversion(typeof(ConversionNeeded), typeof(string))]
    class InheritIValueConverter : IValueConverter
    {
        object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            ConversionNeeded src = (ConversionNeeded)value;
            return src.ToString();
        }

        object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

本来は必要に応じて逆変換も用意してください。

 

2.TypeConverterの作成

次はTypeConverterです。TypeConverterは.NET1.0からあるクラスでWPF(.NET3.0以降ですよね)の生まれる前から存在するクラスです。TypeConverterはXAMLパーサーが使うコンバーターです。というのもXAMLではほとんどすべてのプロパティや値をstringで指定しますが、それぞれが適切な型として適用されるにはその間にコンバーターが介在しているからで、それがTypeConverterなのです。数値や列挙型等組み込みのコンバーターは事前に定義されているので気付かぬうちに使っている、ありがたい存在です。なのでXAMLでTypeConverterを使う場合、変換元の多くはstring型です。ここではstring型から先ほどのテストクラスに変換する例をみてみます。
InheritTypeValueConverter.cs

namespace WpfConvertersDifference
{
    using System;
    using System.ComponentModel;
    public class InheritTypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(string))
            {
                return true;
            }
            return base.CanConvertFrom(context, sourceType);
        }
        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if (value is string)
            {
                if (((string)value).Contains("1")){
                    return new ConversionNeeded() { inDouble = 1.0, inInt = 1, inString = "one"};
                }
                return new ConversionNeeded();
            }
            return base.ConvertFrom(context, culture, value);
        }
    }
}

ConvertFromとCanConvertFromの2つをオーバーライドしました。逆変換もありますが、上記理由でオーバーライドはやめておきます。TypeConverterは単に実装するだけではだめでこれを適用するには変換対象のクラスに属性を付けておく必要があります。

ConversionNeeded.cs(2)

namespace WpfConvertersDifference
{
    using System;
    using System.ComponentModel;

    [TypeConverter(typeof(InheritTypeConverter))]
    public class ConversionNeeded
    {
        public int inInt { get; set; }
        public string inString { get; set; }
        public double inDouble { get; set; }

        public override string ToString()
        {
            return inString + inInt + inDouble;
        }
    }
}

 

3.2つのConverterを使う

さぁ2つのコンバーターを使うのですが、使用法がわかりやすくなるため上記ConversionNeededクラスをプロパティに持つクラスを定義しておきます。

 UserDefinedTestClass.cs

namespace WpfConvertersDifference
{
    public class UserDefinedTestClass
    {
        public ConversionNeeded InterProperty{ get; set; }
        public override string ToString()
        {
            return InterProperty.ToString();
        }
    }
}

ふたつのコンバーターを使ったMainWindowはこんな感じ。

<Window x:Class="WpfConvertersDifference.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:WpfConvertersDifference"
        Title="MainWindow" Height="164" Width="276">
    <Window.Resources>
        <my:InheritIValueConverter x:Key="ivalConverter"/> <!-- ←IValueConverterをリソースに定義 -->
        <my:UserDefinedTestClass x:Key="obj" InterProperty="1"/> <!-- ←TypeConverterを使っている! -->
    </Window.Resources>
    <Grid>
        <TextBox HorizontalAlignment="Left" Height="25" Margin="56,50,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="154">
            <Binding Source="{StaticResource obj}" Path="InterProperty" Converter="{StaticResource ivalConverter}"/> <!--IValueConverterを使っている!-->
        </TextBox>
    </Grid>
</Window>

converter_動作様子

まずTypeConverterはXAML上の文字列からオブジェクトを作成するときに使われるのでした。UserDefinedTestClassのインスタンスを作成した際に、プロパティとしてバインド予定のオブジェクトを作成しようとしていますが、ここに”1″という文字列を渡しています。ここでInheritTypeConverterが作用して”1″という文字列から内部に1やoneといった値を持つConversionNeededオブジェクトを”変換“して作成します。もしInheritTypeConverterクラスが無いとここで”1″なんかで指定はできません。

次にIValueConverterはバインド時に値を変換するのでした。IValueConverterはBindingのConverterプロパティで指定するので、事前にリソースとして登録して利用できるようにしています。これにより、上記TypeConveterによって作成したUserDefinedTestClassのInterPropertyプロパティ(型はConversionNeeded)とTextBoxのText間の型違いを”変換“しています。