C#におけるデスクトップアプリのWPFやUWPアプリのGUIでは画面のインターフェイスはXAMLという専用のマークアップ言語で作成し、実際の処理をするプログラム本体をC#で作成することになります。この時、XAMLによるユーザー・インターフェイスとC#によるプログラム本体を結び付ける仕組みをデータバインディングと呼びます。
もちろん、XAMLを使わずにすべてC#でプログラムを書くことも、データバインディングの仕組みを使わずにXAMLとC#を結び付けることも可能ではあるのですが、上手にデータバインディングを使ってXAMLのメリットを最大限活用しましょう。ここではデータバインディングの仕組みと使い方を説明していきます。
なお、WPFとWinRTやWinUIを用いたUWPアプリのGUIではデータバインディングの作法に若干の違いがありますので、ここでは今後主流になっていくと思われるWinUI 3.0のAPIをもとに説明していきます。
[toc]データバインディングについて
データバインディングの概要
データバインディングとは、データ・ソース(=プログラムの処理本体などのデータの提供元)をビュー(=XAMLコード上のUI要素)と結びつける仕組みです。
・ソース・プロパティ: データの提供元となる、バインディング・ソースのプロパティ。
・バインディング・ターゲット: データの反映先のオブジェクト。
・ターゲット・プロパティ: データの反映先となる、バインディング・ターゲットのプロパティ。
データバインディングの仕組み
データバインディングを実現するための仕組みは次のようになっていて、バインディング・ソースについての情報を格納したBindingオブジェクトをバインディング・ターゲットに登録するという流れを取ります。
- Bindingクラスのインスタンスを作成し、それにバインディング・ソースとソース・プロパティを設定する
- バインディング・ターゲットに先ほど作成したBindingクラスのインスタンスをSetBindingメソッドで登録する
それでは次のような、スライダーを動かすとその値がTextBlockに表示されるようなサンプルを用いて、データバインディングを実装してみましょう。
まず、バインディング・ソースとなるスライダーをSliderクラスで作成し、名前をsliderとします。次にバインディング・ターゲットとしてTextBlockクラスをtextBlockという名前で作成し、スライダーの値を表示させる場所を作成します。
これを実現させるためのC#コードは以下の通りです。
Binding bind = new Binding();
bind.Source = slider;
bind.Path = new PropertyPath("Value");
textBlock.SetBinding(TextBlock.TextProperty, bind);
Bindingオブジェクトを作成し、それにバインディング・ソースとしてSliderクラスのインスタンスを指定し、ソース・プロパティにPropertyPathクラスのインスタンスを登録します。その上で、バインディング・ターゲットとなるTextBlockのインスタンスのSetBindingメソッドで、今作成したBindingオブジェクトとそのターゲット・プロパティを指定しています。
次に、XAMLを使って先ほどと同じデータバインディングを実装してみましょう。
<Slider x:Name="slider"/>
<TextBlock x:Name="textBlock" Text="{x:Bind Path=slider.Value, Mode=OneWay}"/>
XAMLではターゲット・プロパティにソース・プロパティを登録するだけでデータバインディングを実装できます。この例でもデータバインディング部分はわずか1行で実装できており、非常にシンプルで分かりやすいコーディングが可能になります。XAMLではそもそもBindingクラスを全く意識する必要すらありません。
バインディング・ターゲットとターゲット・プロパティに指定可能なもの
バインディング・ターゲットとしてはWinUIのGUI要素を指定可能ですが、その中でターゲット・プロパティに指定可能なプロパティは依存関係プロパティである必要があります。ただし、基本的にXAMLでコード中に記載することのあるGUI要素はDependencyObjectクラスを継承していてそのプロパティの多くは依存関係プロパティになっているので、それらはすべてターゲット・プロパティとして設定可能です。
バインディング・ソースに指定可能なもの
データバインディングのバインディング・ソースに指定可能なものには次のようなものがあります。
- GUI要素 (依存関係オブジェクト)
- CLRオブジェクト
このほかにもXMLオブジェクトや動的オブジェクトなどもバインディング・ソースとして用いることができますが、ここでは基本となるGUI要素とCLRオブジェクトのデータバインディングについて説明していきます。
GUI要素のデータバインディング
ここではWPFやWinUIのGUI要素をバインディング・ソースとする場合を説明していきます。これらのGUI要素はすべてDependencyObjectクラスを継承されているので依存関係オブジェクトとなります。依存関係オブジェクトにおける依存関係プロパティには標準で値の変更を通知する仕組みも備えているので、ソース・プロパティにも依存関係プロパティを指定することで、効率よくデータバインディングを行うことが可能となります。
データバインディングではターゲット・プロパティは依存関係プロパティである必要があるので、ターゲット・プロパティに設定可能なプロパティはすべてソース・プロパティとしても設定可能ということになります。
XAMLでは、バインディング・ターゲットの要素中のターゲット・プロパティに{x:Bind}マークアップ拡張でバインディング・ソースやソース・プロパティを指定するだけでデータバインディングを実装することができます。なお、{x:Bind}マークアップ拡張はUWPアプリから導入されたもので、それ以前に用いられていた{Binding}マークアップ拡張と比べてコンパイル時に内容を解析できるなどの違いがあります。
基本書式
<(バインディング・ターゲット) (ターゲット・プロパティ)={x:Bind Path=(ソース・プロパティ), ...... }>
例
<TextBlock Text="{x:Bind Path=slider.Value, Mode=OneWay}"/>
{x:Bind}マークアップ拡張で指定可能なプロパティには次のようなものがあります。
Pathプロパティ
Pathプロパティにソース・プロパティのパスを指定します。これはBindingクラスのPathプロパティに相当します。GUI要素をバインディング・ソースとする場合は、ソース・プロパティにはそのままバインディング・ソースのインスタンスのプロパティを指定するだけで問題ありません。
Modeプロパティ
Modeプロパティにはデータバインディングの向きを指定します。これはBindingクラスのModeプロパティに相当し、ここに指定できるのはBindingMode列挙型で定義された「OneTime」「OneWay」「TwoWay」です。デフォルトではデータバインディングが生成されたときのみに、バインディング・ターゲットを更新する「OneTime」となっているので、常に「ソース・プロパティ → ターゲット・プロパティ」での方向で反映させるためには「OneWay」とし、常にソース・プロパティとターゲット・プロパティを一致させるためには「TwoWay」とする必要があります。
なお、従来の{Binding}マークアップ拡張では「TwoWay」がデフォルトとなっていたので注意が必要です。
UpdateSourceTriggerプロパティ
UpdateSourceTriggerプロパティには、Modeプロパティが「TwoWay」の場合にターゲット・プロパティの変更をソース・プロパティに反映させるタイミングを指定します。これはBindingクラスのUpdateSourceTriggerプロパティに相当し、ここに指定できるのはUpdateSourceTrigger列挙型で定義された「Default」「PropertyChanged」「Explicit」「LostFocus」です。
特に何も指定しなくてもターゲット・プロパティの値はソース・プロパティの値に応じてすぐに更新されますが、反対にソース・プロパティの値に応じてターゲット・プロパティの値が更新されるタイミングはここで指定する必要があります。変更された瞬間にソース・プロパティの値も変更する場合は「PropertyChanged」とし、ターゲット・プロパティのフォーカスを失ったときにソース・プロパティを更新する場合は「LostFocus」とします。
CLRオブジェクトのデータバインディング
次にCLRオブジェクトをバインディング・ソースとし、WPFやWinUIのGUI要素がバインディング・ターゲットとする場合を考えます。この場合はソース・プロパティの値が更新された際にそれをターゲット・プロパティに通知する仕組みを自分で実装する必要があります。なお、{x:Bind}マークアップ拡張に指定可能なプロパティはGUI要素のデータバインディングの際と全く変わりません。
一番最初に、スライダーの値をソース・プロパティとしてTextBlockのテキストをターゲット・プロパティとするデータバインディングをお示ししましたが、ここではソース・プロパティとなるCLRオブジェクトを別に作成して、スライダーの値とTextBlockのテキストの両方をターゲット・プロパティとして設定してみましょう。そうすることで、ソース・プロパティとなるCLRオブジェクトにC#コードから直接アクセスすることが可能になります。例えば以下の例では、「カウントアップ」ボタンをクリックしてソース・プロパティの値を1つ増やすことが可能です。
C#コードでSliderValueプロパティを作成し、それをソース・プロパティに指定します。XAMLコードからはPathにそのままプロパティ名を指定するだけでOKです。ModeプロパティやUpdateSourceTriggerプロパティなども必要に応じて指定しましょう。
XAMLコード (MainPage.xaml)
<StackPanel>
<Slider Value="{x:Bind Path=SliderValue, Mode=TwoWay}"/>
<TextBlock Text="{x:Bind Path=SliderValue, Mode=OneWay}"/>
<Button Content="カウントアップ" Click="Button_Click"/>
</StackPanel>
C#コード (MainPage.xaml.cs)
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private static readonly PropertyChangedEventArgs SliderPropertyChangedEventArgs = new PropertyChangedEventArgs(nameof(SliderValue));
//SliderValueプロパティ
private int sliderValue;
public int SliderValue
{
get { return sliderValue; }
set
{
if (sliderValue == value) return;
sliderValue = value;
PropertyChanged?.Invoke(this, SliderPropertyChangedEventArgs);
}
}
public MainPage()
{
this.InitializeComponent();
}
//「カウントアップ」ボタンをクリックしたときの動作
private void Button_Click(object sender, RoutedEventArgs e)
{
SliderValue++;
}
}
続いて、C#コードからSliderValueプロパティの値が変更されたときに通知する仕組みを実装しましょう。INotifyPropertyChangedインターフェイスを実装し、PropertyChangedイベントを作成します(3行目)。また、イベントを表すインスタンスも作成しておきましょう(4行目)。
なお、イベントに関しては以下の記事もご覧ください。
SliderValueプロパティの値を変更すると、15行目のNull条件演算子( ?.Invoke() )でPropertyChangedイベントがnullでなければ、それにイベントのインスタンス(PropertyChangedEventArgs)が渡されて呼び出されます。これで、プロパティの値の変更を検知してそれを通知する仕組みを作ることができました。
このようにしてCLRオブジェクトをソース・プロパティとすることで、27行目のようにC#コード上から直接そのCLRオブジェクトにアクセスすることが可能になります。
コメント