バリューコンバータを使って、ラジオボタン(UI)とロジックの間で双方向データバインディングを行う方法を解説する。
対象:.NET 4以降
ラジオボタンの選択状態を列挙型や整数型のデータと双方向にバインドするとき、データの変更がラジオボタンの選択状態に正しく反映されなくて困ったことはないだろうか? そのような例と回避策は「.NET TIPS:WPF:ラジオボタンの選択をバインディングソースに反映させるには?[C#/VB]」で述べたが、UIコントロールの都合にロジックのコードを合わせるという不格好なものだった。何とかUIとロジックをきれいに分離できないだろうか? .NET 4以降ならば、バリューコンバータを使うことでそれが可能なのだ*1。本稿では、その方法を解説する。
なお、本稿のサンプルは「Windows desktop code samples:.NET Tips #1127」からダウンロードできる。
*1 .NET 3.5では、本稿の方法ではうまくいかない。「.NET TIPS:WPF:ラジオボタンの選択をバインディングソースに反映させるには?[C#/VB]」で述べた不具合が、やはり発生するのだ。別途公開のサンプル(WPF版)は.NET 3.5でもコンパイルできるように書いてあるので、プロジェクトのプロパティでバージョンを切り替えて試してみてほしい。
バリューコンバータとは、バインディングソース(=データ)とバインディングターゲット(UIコントロール)の間に「挟んで」、双方向にデータを変換する仕組みである。バリューコンバータは、IValueConverterインタフェース(WPFではSystem.Windows.Data名前空間/UWPではWindows.UI.Xaml.Data名前空間)を実装して作成する。
冒頭で述べた問題に対処するには、次のコードのようなバリューコンバータを作ればよい。ポイントは、ConvertBackメソッドにおいて、falseがtrueに変わったときにだけデータを返すようにすることだ。
public class SampleEnum2BooleanConverter : System.Windows.Data.IValueConverter
{
  // ConverterParameterをEnumに変換するメソッド
  private SampleEnum ConvertFromConverterParameter(object parameter)
  {
    string parameterString = parameter as string;
    return (SampleEnum)Enum.Parse(typeof(SampleEnum), parameterString);
  }
  #region IValueConverter メンバー
  // Enum → bool
  public object Convert(object value, Type targetType, object parameter,
                        System.Globalization.CultureInfo culture)
  {
    // XAMLに定義されたConverterParameterをEnumに変換する
    SampleEnum parameterValue = ConvertFromConverterParameter(parameter);
    // ConverterParameterとバインディングソースの値が等しいか?
    return parameterValue.Equals(value);
  }
  // bool → Enum
  public object ConvertBack(object value, Type targetType, object parameter,
                            System.Globalization.CultureInfo culture)
  {
    // true→falseの変化は無視する
    // ※こうすることで、選択されたラジオボタンだけをデータに反映させる
    if(!(bool)value)
      return System.Windows.DependencyProperty.UnsetValue;
    // ConverterParameterをEnumに変換して返す
    return ConvertFromConverterParameter(parameter);
  }
  #endregion
}
Public Class SampleEnum2BooleanConverter
  Implements System.Windows.Data.IValueConverter
  ' ConverterParameterをEnumに変換するメソッド
  Private Function ConvertFromConverterParameter(parameter As Object) As SampleEnum
    Dim parameterString As String = CType(parameter, String)
    Return CType([Enum].Parse(GetType(SampleEnum), parameterString), SampleEnum)
  End Function
#Region "IValueConverter メンバー"
  ' Enum → bool
  Public Function Convert(value As Object, targetType As Type, parameter As Object,
                          culture As Globalization.CultureInfo) As Object _
                  Implements IValueConverter.Convert
    ' XAMLに定義されたConverterParameterをEnumに変換する
    Dim parameterValue As SampleEnum = ConvertFromConverterParameter(parameter)
    ' ConverterParameterとバインディングソースの値が等しいか?
    Return parameterValue.Equals(value)
  End Function
  ' bool → Enum
  Public Function ConvertBack(value As Object, targetType As Type, parameter As Object,
                              culture As Globalization.CultureInfo) As Object _
                  Implements IValueConverter.ConvertBack
    ' true→falseの変化は無視する
    ' ※こうすることで、選択されたラジオボタンだけをデータに反映させる
    If (Not CType(value, Boolean)) Then
      Return System.Windows.DependencyProperty.UnsetValue
    End If
    ' ConverterParameterをEnumに変換して返す
    Return ConvertFromConverterParameter(parameter)
  End Function
#End Region
End Class
上のバリューコンバータを使うXAMLコードは、次の画像とソースのようになる。
 上のバリューコンバータを使う画面(VS 2012)
上のバリューコンバータを使う画面(VS 2012)<Window ……省略……
        xmlns:local="clr-namespace:……省略……"
        >
  <Window.Resources>
    ……省略……
    <!-- バリューコンバータ -->
    <local:SampleEnum2BooleanConverter x:Key="E2BConverter"/>
  </Window.Resources>
  <Grid>
    ……省略……
    <StackPanel x:Name="RadioButtosGroup" ……省略……>
      <RadioButton Tag="One" Content="選択肢1" 
                   IsChecked="{Binding Value, ConverterParameter=One,
                              Converter={StaticResource E2BConverter}}" />
      ……省略(選択肢2/選択肢3のラジオボタンも同様に記述)……
      <StackPanel Orientation="Horizontal">
        <TextBlock FontSize="24">現在値:</TextBlock>
        <TextBlock FontSize="24" Text="{Binding Value}" />
      </StackPanel>
    </StackPanel>
    <StackPanel Orientation="Horizontal" ……省略……>
      <Button Click="SelectButton_Click" Tag="1">1</Button>
      ……省略(選択肢2/選択肢3のボタンも同様に記述)……
    </StackPanel>
  </Grid>
</Window>
あとは、画面のコンストラクタでデータバインドし、ボタンのクリックイベントのハンドラでバインドしているデータを変更すればよい。その部分のコードおよびバインドするデータのコードについては、別途公開のサンプルをご覧いただきたい。データのコードは、「.NET TIPS:WPF:ラジオボタンの選択をバインディングソースに反映させるには?[C#/VB]」で示したデータと同様な「Value」プロパティを持っているが、それ以外のプロパティは持っておらず、とてもシンプルになっている。
実行すると、次の画像のようになる。ラジオボタンをクリックするとバインディングソースのデータに反映され、それは再びデータバインドによってボタンの上にあるテキストブロックの表示に反映される。また、下部のボタンをクリックするとそのイベントハンドラでデータが書き換えられ、その変化はデータバインドによってラジオボタンの選択状態に反映される。
 実行している様子(WPF版、Windows 10)
実行している様子(WPF版、Windows 10)ラジオボタンの選択状態とデータを双方向データバインディングするには、.NET 4以降であればバリューコンバータを使うとよい。そのバリューコンバータには、ラジオボタンの都合に合わせるための「細工」が必要だ。
利用可能バージョン:.NET Framework 4以降
カテゴリ:WPF 処理対象:データバインディング
カテゴリ:WPF/XAML 処理対象:RadioButtonコントロール
使用ライブラリ:RadioButtonコントロール(System.Windows.Controls名前空間)
使用ライブラリ:IValueConverterインターフェース(System.Windows.Data名前空間)
関連TIPS:WPF/UWP:ラジオボタンの選択をコードから切り替えるには?[C#/VB]
関連TIPS:WPF:ラジオボタンの選択をバインディングソースに反映させるには?[C#/VB]
Copyright© Digital Advantage Corp. All Rights Reserved.