
WPF の IValueConverter を実装するときの考え方と小ネタ。
MarkupExtension の実装と DependancyProperty の実装
IValueConverter も IMultiValueConverter でも ValueConverter はコンバーターにプロパティを実装したいときに MarkupExtension を使った実装も、DependancyProperty を使った実装も(一応)どちらでも可能です。
個人的なポイントとしては、MarkupExtension を使った実装をすると、IValueConverter のパラメータを追加する手法としてはシンプルで自然なものになります。この特徴として、XAML のスコープごとに異なる設定を持った Converter を使いやすい。柔軟なパラメータの代入が可能です。
<CheckBox IsEnabled="{Binding Sample, Converter={local:SampleConverter SampleParam=1}}" /> <CheckBox IsEnabled="{Binding Sample, Converter={local:SampleConverter SampleParam=3}}" />
ValueConverter を使った実装は、いくらか個性的な(または、トリッキーな)手法になる恐れがあり、あまり優位性はないと思いますが、ネット上にはこの設計をしているものもあります。
メリットは、引数の型を決めることができます。string 型ではなくて class や enum の値を初期設定しやすいと思います。
基本的に ValueConverter は、Binding しない。Binding したいときは、該当するパラメータを MultiBindingConverter にして引数として渡すほうがよい。
<local:SampleConverter Key="SampleConverter1" SampleParam="A" /> <!--(要:Freezable) --> <local:SampleConverter Key="SampleConverter1" SampleParam="{Binding A}" />
パラメータがないときの実装パターン
このときは ConverterParameter で固定値の(基本的には文字列)のパラメータを代入します。
<Label Content="{Binding Text1, Converter={StaticResource NoParamConverter}, ConverterParameter=Param1}" />
MarkupExtension の実装パターン
public class SampleValueConverter : MarkupExtension, IValueConverter { public string SampleParam { get; set; } = ""; public override object ProvideValue(IServiceProvider serviceProvider) { Debug.WriteLine($"SampleParam = {SampleParam}"); return this; // IValueConverter 自体を返す } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return SampleParam == "1"; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); }
ProvideValue の設定が IValueConverter の場合は、ちょっと違和感があると思います。他の例だとこんなのはわかりやすい。
ProvideValue を実装する意味は、コンバーター自身を拡張プロパティの書き方ができるように認めてほしいから。
<CheckBox IsEnabled="{Binding Sample, Converter={local:SampleConverter SampleParam=1}}" />
Resources に宣言するのではなくて、上記の書き方のように突然 Converter の中で SampleConverter を設定したいときは MarkupExtension が必要になる。
Resources に対して StaticResource として登録したデータに SampleParam=1 という引数の部分を書けるようにするわけではないので注意。(個人的に混同したことがある)
Freezable を使う
以下のようにして、Binding をすることもできる。(ただし、Freezable は動作がトリッキーなので注意したい)
Param の変化には対応できないので、Param のインスタンスが変わらなければ、Param の同じインスタンス内のパラメーターを追い続けることができる(はず)。
<local:ParamEx3Converter x:Key="ParamEx3Converter1" Param="{Binding Param}" />
public class ParamEx3Converter : Freezable, IValueConverter { public static readonly DependencyProperty ParamProperty = DependencyProperty.Register(nameof(Param), typeof(string), typeof(ParamEx3Converter), new PropertyMetadata("")); public string Param { get => (string)GetValue(ParamProperty); set => SetValue(ParamProperty, value); } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return $"{value} : {Param}"; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); protected override Freezable CreateInstanceCore() => new ParamEx3Converter(); }
Sample
GitHub に Converter の挙動を整理したサンプルを公開しています。
- パラメーターなしのとき
- MarkupExtension
- DependencyObject
- Freezable
最後にもう一度再掲載:
基本的に ValueConverter は、Binding しない。Binding したいときは、該当する Binding パラメータを MultiBindingConverter にして引数として渡すほうがよい。