Help us understand the problem. What is going on with this article?

汎用コンバータを作る

More than 5 years have passed since last update.

ユーザー定義された型

独自に定義されたUserColor型があるとする…

独自定義された型
using System.Windows.Media;
public sealed class UserColor {
    public Color Value {
        get;
        set;
    }

    public static bool TryParse( string input , out UserColor value ) {
        value = new UserColor();

        switch( input ) {
            case "赤":
                value.Value = Colors.Red;
                return true;
            case "青":
                value.Value = Colors.Blue;
                return true;
            case "黄":
                value.Value = Colors.Yellow;
                return true;
            default:
                break;
        }

        return false;
    }

    public override string ToString() => this.Value.ToString();
}

次にUserColor型のプロパティを一つ持つ、ビューモデルを定義する。

ビューモデル
using System.ComponentModel;
public sealed class ViewModel : INotifyPropertyChanged {
    public UserColor SelectColor {
        get {
            return this._SelectColor;
        }
        set {
            if( this._SelectColor == value )
                return;
            this._SelectColor = value;
            this.PropertyChanged?.Invoke( this , new PropertyChangedEventArgs( nameof(SelectColor) ) );
        }
    }
    private UserColor _SelectColor;

    public event PropertyChangedEventHandler PropertyChanged;
}

データバインディング

XAMLでは、SelectColorプロパティとデータバインドしたとき、その値を文字列で指定しなければならない。
単純ではあるが、IValueConverterインターフェースを継承して、UserColor用のコンバータを定義した。

public sealed class UserColorConverter : IValueConverter {
    public object Convert( object value , Type targetType , object parameter , CultureInfo culture ) => value?.ToString();

    public object ConvertBack( object value , Type targetType , object parameter , CultureInfo culture ) {
        UserColor tmp;
        UserColor.TryParse( value as string , out tmp );
        return tmp;
    }
}
XAML
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="180" Width="200">

    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>

    <Window.Resources>
        <local:UserColorConverter x:Key="userColor"/>
    </Window.Resources>

    <StackPanel>
        <TextBox Text="{Binding SelectColor,Converter={StaticResource userColor}}"/>
    </StackPanel>
</Window>

問題点と解決策

UserColorのようなユーザ定義型が増える度にコンバータを定義するのが面倒すぎる。

  • TryParseメソッドで文字列から値に変換する。
  • ToStringメソッドで文字列表現を取得する。

ほとんどの場合、このパターンで事足りる。
そこで、このパターンに沿った汎用コンバータを実装してみる。

コンバータの実装
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq.Expressions;
using System.Windows.Data;
using System.Windows.Markup;

namespace WpfApplication1 {
    public sealed class TryParseConverter : IValueConverter {
        private readonly static Dictionary<Type , IValueConverter> converters = new Dictionary<Type , IValueConverter>();

        private static IValueConverter getConverter( Type targetType ) {
            IValueConverter converter;
            if( converters.TryGetValue( targetType , out converter ) )
                return converter;
            converter = Activator.CreateInstance( typeof(TryParseConverter<>).MakeGenericType( targetType ) ) as IValueConverter;
            if( converter == null )
                throw new InvalidCastException( string.Format( "型 {0}は、{1}を継承していません。" , targetType , nameof(IValueConverter) ) );
            converters.Add( targetType , converter );
            return converter;
        }

        public object Convert( object value , Type targetType , object parameter , CultureInfo culture )
            => value == null ? null : getConverter( value.GetType() ).Convert( value , targetType , parameter , culture );

        public object ConvertBack( object value , Type targetType , object parameter , CultureInfo culture )
            => getConverter( targetType ).ConvertBack( value , targetType , parameter , culture );
    }

    public sealed class TryParseConverter<T> : IValueConverter {
        private delegate bool TryParse( string input , out T value );
        private static readonly TryParse _tryParse;

        static TryParseConverter() {
            var tryParse = typeof(T).GetMethod( nameof(Int32.TryParse) , new[] { typeof(string) , typeof(T).MakeByRefType() } );
            if( tryParse == null )
                throw new TypeAccessException( "TryParseメソッドが見つかりません。" );
            var value = Expression.Parameter( typeof(T).MakeByRefType() );
            var input = Expression.Parameter( typeof(string) );
            _tryParse = Expression.Lambda<TryParse>( Expression.Call( tryParse , input , value ) , input , value ).Compile();
        }

        public object Convert( object value , Type targetType , object parameter , CultureInfo culture ) {
            // to string
            if( value == null )
                return null;
            if( value is IFormattable && parameter is string )
                return ( (IFormattable)value ).ToString( (string)parameter , null );
            else
                return value.ToString();
        }

        public object ConvertBack( object value , Type targetType , object parameter , CultureInfo culture ) {
            // to object
            if( value == null )
                return null;
            T tmp;
            _tryParse( value as string , out tmp );
            return tmp;
        }
    }
}
使用例
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="180" Width="200">

    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>

    <Window.Resources>
        <local:TryParseConverter x:Key="tryParse"/>
    </Window.Resources>

    <StackPanel>
        <TextBox Text="{Binding SelectColor,Converter={StaticResource tryParse}}"/>
    </StackPanel>
</Window>

static bool TryParse( string input , T out value ) が実装されており、ToStringで適切な文字列表現を
得られる型であれば、型ごとにコンバータを定義する必要が無くなる。

Temarin
単なる趣味プログラミングの人
http://pitacorebox.webcrow.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした