LoginSignup
1
3

More than 5 years have passed since last update.

ICommand や IValueConverter のパラメータで InvalidCastException が発生しちゃう問題

Posted at

使っているフレームワークにもよると思いますが、ICommand や IValueConverter で InvalidCastException に出くわすことがありました。

InvalidCastExceptionが発生するパターン

パラメータの値を XAML 上に直値で書いている場合です。

Command

public class Command<T> : ICommand
{
    public Command (Action<T> exec) => _exec = exec;
    private Action<T> _exec;
    public void Execute(object prm) => _exec((T)prm);
    // 一部省略
}
<!--
 こんな定義
 IncrementValueCommand = new Command(x => Value += x);
-->
<Button Command="{Binding IncrementValueCommand}" CommandParameter="1" />

Converter

public class AddConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object prm, CultureInfo culture)
    {
        return (double)value + (double)prm;
    }
    // 一部省略
}
<!--  Resource 定義は省略  -->
<Grid>
  <Border Name="bd1" />
  <!-- bd1 よりちょっと大きくしたい -->
  <Border Name="bd2"
          Width={Binding ElementName=bd1,
                         Path=ActuralWidth,
                         Converter={StaticResource addConverter},
                         ConverterParameter=10} />
</Grid>

何がだめか?

CommandParameter="1"ConverterParameter=10 の数字部分は string として扱われます。
なので、実際には以下のような引数でメソッドを呼び出したことになります。

IncrementValueCommand.Execute("1");
addConverter.Convert(bd1.ActualWidth, typeof(double), "10", ConverterCulture);

なぜ string 型になるのか?

そもそも XAML で書かれたプロパティは string 型として扱われていて、そこから各プロパティの型へと変換されていきます。
そこで使われるのが、TypeConverter クラスです。
例えば、次のような XAML の場合、大雑把にいうと下のような C# コードに変わります。

<Border Margin="1, 10, 1, 10" Height="100" />
new Border
{
    Margin = ThicknessConverter.ConvertFrom("1, 10, 1, 10"),
    Height = DoubleConverter.ConvertFrom("100"),
}

Margin は Thickness 型、 Height は double 型なためそれぞれのコンバータを引き当てることができますが、
ICommandSource.CommandParameter も Binding.ConverterParameter もどちらも object 型で定義されているため、変換されずそのまま文字列が入ってしまう のです。

解決案

こんな感じで TypeConverter を使ってやります。
ちなみに、TypeDescriptor.GetConverter で TypeConverter をとってくることができます。

Command

public class Command<T> : ICommand
{
    public Command (Action<T> exec) => _exec = exec;
    private Action<T> _exec;

    private static TypeConverter _typeConverter = TypeDescriptor.GetConverter(typeof(T));

    public void Execute(object prm)
    {
        var tPrm = (prm is T)
            ? (T)prm
            : (T)_typeConverter .ConvertFrom(prm);
        _exec(tPrm);
    }
    // 一部省略
}

Converter

public class AddConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object prm, CultureInfo culture)
    {
        var dPrm = (prm is double)
            ? (double)prm
            : (double)DoubleConverter.ConvertFrom(prm);
        return (double)value +  dPrm;
    }
    // 一部省略
}
1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3