ラジオボタンで列挙型の値を選択してもらう場合に、データコンバーターを使って、各ボタンが持つブール値と列挙型の値との間で変換を行う方法を説明する。
powered by Insider.NET
WindowsランタイムアプリでUIとロジックを分離する設計をしているとき、ラジオボタンの扱いで困ってしまったことはないだろうか? 例えば、ラジオボタンで列挙型の値を選択する場合である(次の画像を参照)。バインディングソースには列挙型の値だけを持ちたい。しかし、バインドしたいラジオボタンのプロパティは、チェックされているかどうかのブール値である。このミスマッチを解決するために、本稿ではデータコンバーターを工夫する方法を紹介しよう。なお、本稿のサンプルは「Windows Store app samples:MetroTips #110」からダウンロードできる。
ユニバーサルプロジェクトを使ってWindows 8.1/Windows Phone 8.1用のユニバーサルWindowsアプリを開発するには、以下の開発環境が必要である。本稿では、無償のVisual Studio Community 2013 with Update 4を使っている。
*1 SLAT対応ハードウエアは、Windows Phone 8.1エミュレーターの実行に必要だ。ただし未対応でも、ソースコードのビルドと実機でのデバッグは可能だ。SLAT対応のチェック方法はMSDNブログの「Windows Phone SDK 8.0 ダウンロードポイント と Second Level Address Translation (SLAT) 対応PCかどうかを判定する方法」を参照。なお、SLAT対応ハードウエアであっても、VM上ではエミュレーターが動作しないことがあるのでご注意願いたい。
*2 事前には「Windows 8.1 Update 1」と呼ばれていたアップデート。スタート画面の右上に検索ボタンが(環境によっては電源ボタンも)表示されるようになるので、適用済みかどうかは簡単に見分けられる。ちなみに公式呼称は「the Windows RT 8.1, Windows 8.1, and Windows Server 2012 R2 update that is dated April, 2014」というようである。
*3 Windows Phone 8.1エミュレーターを使用しないのであれば、32bit版のWindows 8.1でもよい。
*4 マイクロソフトのダウンロードページから誰でも入手できる(このURLはUpdate 5のもの)。
*5 本稿に掲載したコードを試すだけなら、無償のExpressエディションやCommunityエディションで構わない。Visual Studio Express 2013 for Windows Update 5(製品版)はマイクロソフトのページから無償で入手できる。Expressエディションはターゲットプラットフォームごとに製品が分かれていて紛らわしいが、Windowsランタイムアプリの開発には「for Windows」を使う(「for Windows Desktop」はデスクトップで動作するアプリ用)。また、Visual Studio Community 2013 with Update 5(製品版)もマイクロソフトのページ(ページ左側のメニューで[Visual Studio 2013]を選ぶ)から無償で入手できる。なお、英語版がインストールされた場合には、Microsoft Visual Studio 2013 Language Packの日本語版を追加インストールし、[オプション]ダイアログで言語を切り替える。
Visual Studio 2013 Update 2(Update 3/4/5も)では、残念なことにVB用のユニバーサルプロジェクトのテンプレートは含まれていない*6。そのため、本稿で紹介するVBのコードはユニバーサルプロジェクトではなく、PCL(ポータブルクラスライブラリ)を使ったプロジェクトのものである。
*6 VB用のユニバーサルプロジェクトは、2015年の夏にリリースされるといわれているVisual Studio 2015(開発コード「Visual Studio 14」)からの提供となるようだ(Visual Studio 2015の本体は7月20日のリリースだが、ストアアプリ用の最新SDKのリリースは7月29日になるといわれている)。プレビュー版で、すでに共有プロジェクトは利用可能になっている。「特集:次期Visual Studioの全貌を探る:Visual Basic 14の新機能ベスト10〜もう「VBだから」とは言わせない!」参照。
ラジオボタンにバインドするデータとして、次のようなコードを考えてみよう。バインドしたいプロパティは、「UserData」クラスの「FavoriteColor」プロパティだ。それは「FavoriteColor」列挙型の値を持っている。「UserData」クラスはINotifyPropertyChangedインターフェース(System.ComponentModel名前空間)を実装しているので、データの変化をUIに通知できる。
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MetroTips110CS
{
// 列挙型:好きな色
public enum FavoriteColor
{
None,
Red,
Green,
Blue,
}
// ユーザーの特性データ
public class UserData : INotifyPropertyChanged
{
// INotifyPropertyChangedインターフェースの実装
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
// データバインド可能なプロパティ:好きな色
private FavoriteColor _favouriteColor;
public FavoriteColor FavoriteColor
{
get { return _favouriteColor; }
set
{
if (_favouriteColor == value)
return;
if (value != FavoriteColor.None)
{
// 有効な色がセットされたときは、いったんNoneにして通知を出す
// (ラジオボタンの動きを正しくするため)
_favouriteColor = FavoriteColor.None;
NotifyPropertyChanged();
}
_favouriteColor = value;
NotifyPropertyChanged();
}
}
}
}
' 列挙体:好きな色
Public Enum FavoriteColor
None
Red
Green
Blue
End Enum
' ユーザーの特性データ
Public Class UserData
Implements INotifyPropertyChanged
' INotifyPropertyChangedインターフェースの実装
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) _
Implements INotifyPropertyChanged.PropertyChanged
Public Sub NotifyPropertyChanged(
<CallerMemberName> Optional propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
' データバインド可能なプロパティ:好きな色
Private _favouriteColor As FavoriteColor
Public Property FavoriteColor As FavoriteColor
Get
Return _favouriteColor
End Get
Set(value As FavoriteColor)
If (_favouriteColor = value) Then
Return
End If
If (value <> FavoriteColor.None) Then
' 有効な色がセットされたときは、いったんNoneにして通知を出す
' (ラジオボタンの動きを正しくするため)
_favouriteColor = FavoriteColor.None
NotifyPropertyChanged()
End If
_favouriteColor = value
NotifyPropertyChanged()
End Set
End Property
End Class
この「UserData」クラスの「FavoriteColor」プロパティを、ラジオボタンのIsCheckedプロパティにバインドしたいのである(次の図)。テキストボックスにバインドしてプロパティの値を表示することや、ボタンのクリックイベントでプロパティを書き換えることは、問題なくできるだろう。しかし、ラジオボタンのIsCheckedプロパティに双方向バインドすることは可能なのだろうか?
なお参考までに付け加えておくと、このようなバインディングを実現するには、「UserData」クラスに「IsRedChecked」/「IsGreenChecked」/「IsBlueChecked」といったブール型のプロパティを実装することでも可能だ。ただし、かなり実装が面倒になる。また、ビュー(=画面)を抽象化したデータというには、ちょっと具体的に過ぎるだろう(上のコードでFavoriteColorプロパティのセッター内でラジオボタンの都合に合わせて実装した部分を嫌だと思えるならなおさらだ)。
上の図のようなデータバインディングを実現するには、列挙型の値とブール値とを変換するデータコンバーターを作ればよい。
例えば、プロパティ値がFavoriteColor.Redのとき、[赤]のラジオボタンに対してはTrueを、他のラジオボタンに対してはFalseを返すようなデータコンバーターが実現できればよいのである。それには、XAMLコードでバインディングを記述するときに指定できるConverterParameterオプションを利用する。XAMLコードでConverterParameterオプションに指定した値は、データコンバーターの変換メソッドに第3引数parameterとして渡される。このパラメーターに「Red」/「Green」/「Blue」のいずれかの文字列を渡すことにすると、そのようなデータコンバーター「EnumToBoolConverter」クラスは次のコードのように書ける。
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace MetroTips110CS
{
public class EnumToBoolConverter : IValueConverter
{
// プロパティ値→ブール値へ変換する
public object Convert(object value, Type targetType, object parameter, string language)
{
// XAMLでのデータバインドの記述にあるパラメーターを文字列として取り出す
// そこには、Enumのいずれかの値が入っているはずである
string param = parameter as string;
if (param == null)
return DependencyProperty.UnsetValue;
// パラメーターを列挙型に変換する(例:"Red"→Enum.Red)
object paramValue = Enum.Parse(typeof(FavoriteColor), param);
// バインディングソースの値(value)とパラメーターが等しかったらtrueを返す
return paramValue.Equals(value);
}
// ブール値→プロパティ値へ逆変換する
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
string param = parameter as string;
if (parameter == null)
return DependencyProperty.UnsetValue;
// バインディングソースに書き戻すときは、パラメーター文字列に見合った列挙型を返せばよい
return Enum.Parse(typeof(FavoriteColor), param);
}
}
}
Public Class EnumToBoolConverter
Implements IValueConverter
' プロパティ値→ブール値へ変換する
Public Function Convert(value As Object, targetType As Type, parameter As Object, language As String) As Object Implements IValueConverter.Convert
' XAMLでのデータバインドの記述にあるパラメーターを文字列として取り出す
' そこには、Enumのいずれかの値が入っているはずである
Dim param As String = parameter
If (param Is Nothing) Then
Return DependencyProperty.UnsetValue
End If
' パラメーターを列挙型に変換する(例:"Red"→Enum.Red)
Dim paramValue As Object = FavoriteColor.Parse(GetType(FavoriteColor), param)
' バインディングソースの値(value)とパラメーターが等しかったらtrueを返す
Return paramValue.Equals(value)
End Function
' ブール値→プロパティ値へ逆変換する
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, language As String) As Object Implements IValueConverter.ConvertBack
Dim param As String = parameter
If (parameter Is Nothing) Then
Return DependencyProperty.UnsetValue
End If
' バインディングソースに書き戻すときは、パラメーター文字列に見合った列挙型を返せばよい
Return FavoriteColor.Parse(GetType(FavoriteColor), param)
End Function
End Class
以上で作成した「UserData」クラスと「EnumToBoolConverter」クラスを使うと、XAMLコードのデータバインディングする部分は次のコードのように書ける。
<Grid ……省略……>
<Grid.Resources>
<local:UserData x:Key="userData" />
<local:EnumToBoolConverter x:Key="E2B" />
</Grid.Resources>
<StackPanel DataContext="{StaticResource userData}" ……省略……>
<TextBlock FontSize="24" FontFamily="Yu Gothic">好きな色は?</TextBlock>
<RadioButton
IsChecked="{Binding Path=FavoriteColor, Converter={StaticResource E2B},
ConverterParameter=Red, Mode=TwoWay}"
Foreground="Red">赤</RadioButton>
<RadioButton
IsChecked="{Binding Path=FavoriteColor,Converter={StaticResource E2B},
ConverterParameter=Green, Mode=TwoWay}"
Foreground="Green">緑</RadioButton>
<RadioButton
IsChecked="{Binding Path=FavoriteColor,Converter={StaticResource E2B},
ConverterParameter=Blue, Mode=TwoWay}"
Foreground="Blue">青</RadioButton>
……省略……
</StackPanel>
</Grid>
以上で完成である。これで、図「実現したいデータバインディング」で示した動きが実現できたはずだ。
列挙型などのプロパティはラジオボタンの選択状態に直接バインドできない。また、ラジオボタンの選択状態をコードから変えようとすると意外に厄介だ。その解決策として、本稿ではデータコンバーターを使う方法を解説した。
Copyright© Digital Advantage Corp. All Rights Reserved.