こんにちは、働くC#プログラマーのさんさめです。
WPFにおいてBindingは
「データとビューの疎結合化」
「コード記述の省力化」
などなど非常に重要な役割を果たします。
(後者は慣れないと恩恵を感じにくいですが)
その一方で、
何が起きているのか分かりにくいため、
一度Bindingでハマってしまうと
「まずどこから調べればいいのか分からない」
ということになりがちです。
そこで、私自身の知識の整理も兼ねて
Bindingについてまとめることにしました。
今回はBinding入門編第3回です。
第1,2回は以下をご覧ください。
この記事は、以下のような人に向けて書いています。
- データの見せ方を工夫しようとして
ViewModelのプロパティが大量になった - 毎回同じような変換処理をViewModelで書いてる
- IsEnabledとIsReadOnlyに必要なbool値が
逆なせいで困ってる
本記事では、上記の悩みを解決する
「Converter」という概念に絞って解説します。

ViewModelだけじゃなく、
Viewで表示変換できることを
知ってもらうのが目標です
Converterはバインディングで得られた値を、別の値に変換できる概念
Converter(コンバーター)とは、
IValueConverterインターフェースを実装したクラスのことです。
コンバーターの名の通り、
Bindingによって取得した値を、
全く別の値に変換してViewに流すことができます。
例えば、ViewModelではbool型のプロパティを、
Visibilityプロパティにバインディングしたいと思っても、
そのままではバインディングできません。

そのため、コンバーターを使わないで
ViewModelだけで問題解決しようと思うと、
その都度ViewModelにプロパティを増やす必要があります。

しかし、この方法では、
もしVisibilityに使いたいboolが2個、3個と現れた場合に、
毎回ViewModelにプロパティを足さなくてはいけません。

記述量も多いし、IsChecked変わった時に
追加のPropertyChanged呼ばなきゃだし、
バグった時に見るべき個所も増えちゃう…
そんな時に、
コンバーターという概念が役に立ちます。
例えば、標準で用意されている
BooleanToVisibilityConverterを使えば、
Bindingしているbool値が
trueの時にVisible、falseの時にCollapsed
と変換してくれます。
コンバーターの使うためには、
以下の手順を踏みます。
- あらかじめWindow.Resourcesなどに
使いたいConverterを含めておく - 変換込みのBindingをしたい箇所で、
1.で作ったConvereterを指定する
最小サンプルが以下です。
<Window x:Class="BoolToVisibleConverterSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="160" Width="240">
<!-- 1.Resourcesに含めておく -->
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisible"/>
</Window.Resources>
<!-- Resourceの中にあるConverterを名前指定して使う -->
<Button Visibility="{Binding IsChecked, Converter={StaticResource BoolToVisible}}"/>
</Window>
注目すべきは以下です。
<Button Visibility="{Binding IsChecked, Converter={StaticResource BoolToVisible}}"/>
Converterプロパティに値をセットすることで、
バインディング時に使用したいコンバーターを指定しています。
これにより、ViewModelにIsCheckedVisibility
みたいな専用のプロパティを用意する必要が無くなりました。
bool値をVisibilityに変換するためのコンバーターの
詳細については、
【WPF】boolを任意のVisibilityプロパティにBindingする方法
にまとめていますので、
気になる方はご覧ください。
このように、
コンバーターを使うと
ViewModelに専用のプロパティを増やすことなく
Viewで欲しい値に加工することができます。
もう一つ例を見てみましょう。
bool値を反転してBindingしてくれるコンバーター
意外とありがちなのが、
ViewModelが公開しているbool値と、
実際にViewで欲しい値が逆になってしまうケースです。
例えば、
ViewModelでは、
現在値を編集しても良いか否か
というboolを公開していて、
編集して良い場合にtrueを返しているとします。

しかし、これを
ViewModelでIsReadOnlyプロパティに
バインディングしようとすると、
値が逆であるべきことに気づきます。
IsReadOnlyプロパティがtrueの場合、
そのコントロールは読み取り専用になってしまうからです。

このとき、ViewModel側にわざわざ
「反転したbool値を返すプロパティ」
を用意するのは、
コンバーターを知ってしまった今は、
ちょっと気が引けますよね。
やはり数が多くなってきたときに大変ですし、
ViewModel同士でやり取りする時も、
何を使えばいいのか分からず
いろんな箇所でいろんな使われ方を
されてしまうでしょう。
こんな時に、bool値を反転してくれる
コンバーターがあれば、
Viewだけで解決できそうです。
しかし、bool値を反転するコンバータは、
標準では存在しません。
なので、自作してみましょう。
自作の手順はこうです。
- IValueConverterインタフェースを継承したクラスを作る
- バインディング先から流れてきた値を変換する、
Convertメソッドを作る - 逆にViewからバインディング先に戻す時に値を変換する、
ConvertBackメソッドを作る - 後は標準のコンバータを使うときと同様にxamlで使う
さっそく実装してみたサンプルが以下になります。
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace ConverterSample
{
// 1. IValueConverterを継承する
public class InverseBoolConverter : IValueConverter
{
// 2.Convertメソッドを実装
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// 流れてきた値がboolじゃない時は不正値として変換
// お好みで例外を投げても良い
if (!(value is bool b)) { return DependencyProperty.UnsetValue; }
// 流れてきたbool値を変換してreturnする
return !b;
}
// 3.ConvertBackメソッドを実装
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// ただの反転なのでBinding元に書き戻すときも全く同様の処理で良い
if (!(value is bool b)) { return DependencyProperty.UnsetValue; }
return !b;
}
}
}
手数としてはそんなに多くないですね。
ConvertとConvertBackの中に、
任意のデータ変換ロジックを書けば終わりです。
第3引数のparameterは
Binding記述時にConverterParameterを
設定することで流れてきます。
Viewから何かを渡してそれに応じて
変換ロジックを変更したい場合は使います。
…正直あまり使いませんが。
自作したコンバーターを利用する場合は、
次のようになります。
標準のコンバーターを使うときとほぼ一緒です。
<Window x:Class="ConverterSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ConverterSample"
Title="MainWindow" Height="160" Width="240">
<Window.Resources>
<local:InverseBoolConverter x:Key="InveresBool"/>
</Window.Resources>
<TextBox IsReadOnly="{Binding CanEdit, Converter={StaticResource InveresBool}}"/>
</Window>
これで、
ViewModelにbool値を反転しただけのプロパティを
用意する必要がなくなりました。

快適快適!
同じ変換処理毎回書いてるなと思ったらコンバーターを活用しよう
コンバーターの基本的な解説は以上です。
コード記述を減らすための
非常に強力な仕組みであることを
感じられましたでしょうか?
WPFに不慣れな時は、
なかなかコンバータを使おうという発想が出ませんが、
コンバーターを使うと
とてもコードがスッキリするかもしれません。
「なんか似たような変換処理毎回書いてるな~」
と思ったらコンバーターが使えないか、
検討してみることをおススメします。
まとめ
まとめです。
- コンバータを使うとバインディングで取得した値を
別の値に変換できる - うまく活用すれば、
ViewModelに大量のプロパティを書かずとも、
Viewで欲しい値を取得できる - 似た変換処理がVMに沢山出てきたら、
コンバーターの自作を検討してみる価値アリ
最後までお読みいただき、ありがとうございました。
シリーズ記事
Binding入門はシリーズ記事となっております。
全ての記事に以下からアクセスできます
コメント