第2回 データの表示と入力に必要な知識連載 WPF/Silverlight UIフレームワーク入門(2/5 ページ)

» 2009年05月19日 00時00分 公開
[八巻雄哉グレープシティ株式会社]

DataContextを使ったデータ・バインディング

 上記の例では、2つのBindingオブジェクトのSourceプロパティに対して、同じPersonオブジェクトを設定していた。このような1つのデータ・オブジェクトを複数のBindingオブジェクトのソースとして使用する構成は、WPF UIフレームワークのバインディングにおいて一般的なパターンとなっている。

 しかし、Bindingオブジェクトの数が多くなればなるほど、同じソースを各BindingオブジェクトのSourceプロパティに設定するという作業は面倒である。このような場合を想定し、ほぼすべてのUI要素(=FrameworkElmentクラス、およびその派生クラス)にはDataContextというプロパティが用意されている。

 実は、BindingオブジェクトはターゲットのDataContextプロパティに格納されている値をソースとして参照するというのが既定の動作となっており、Sourceプロパティに値が設定された場合には、この動作がオーバーライドされる。

 そしてこのDataContextプロパティは、親要素から子要素へ値が継承されるという性質を持っている。これにより、上位の親要素でDataContextプロパティにソースを一度設定するだけで、それを共通のソースとして子要素で利用することができる。以下にDataContextプロパティを使用する場合の構成図を示す。

DataContextプロパティを使用した一方向のバインディング
StackPanelオブジェクトで設定しているDataContextプロパティの値は、その子要素であるTextBlockオブジェクトに継承される。

 上位要素であるStackPanelのDataContextプロパティの設定は、その下位要素である2つのTextBlock要素のDataContextプロパティに継承される。BindingオブジェクトにはSourceプロパティが設定されていないため、TextBlock要素のDataContextプロパティの値であるPersonオブジェクトがソースとして使用される。

 これを実現するには、コードビハインド部分のコードを下記のように変更すればよい。

private void MainPanel_Loaded(object sender, RoutedEventArgs e)
{
  MainPanel.DataContext =
    new Person() { Height = 1.7, Weight = 60 };

  Binding BindingHeight = new Binding("Height");
  BindingHeight.Mode = BindingMode.OneWay;
  TextBlock1.SetBinding(TextBlock.TextProperty, BindingHeight);

  Binding BindingWeight = new Binding("Weight");
  BindingWeight.Mode = BindingMode.OneWay;
 TextBlock2.SetBinding(TextBlock.TextProperty, BindingWeight);
}

Private Sub MainPanel_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
  MainPanel.DataContext = _
    New Person() With {.Height = 1.7, .Weight = 60}

  Dim BindingHeight As New Binding("Height")
  BindingHeight.Mode = BindingMode.OneWay
  TextBlock1.SetBinding(TextBlock.TextProperty, BindingHeight)

  Dim BindingWeight As New Binding("Weight")
  BindingWeight.Mode = BindingMode.OneWay
  TextBlock2.SetBinding(TextBlock.TextProperty, BindingWeight)
End Sub

DataContextプロパティを利用してソースの設定を行うコード(上:C#、下:VB)
太字部分で、StackPanelのDataContextプロパティにデータ・オブジェクトを設定している。
また、BindingHeightオブジェクト/BindingWeightオブジェクトのSourceプロパティを設定していた行を削除している。

 TextBlockの親要素であるStackPanelのDataContextプロパティを設定することで、個々のBindingオブジェクトのSourceプロパティの設定が不要となる。実行結果については先ほどと同様である。

XAMLコードでバインディング

 ここまで紹介してきたサンプルでは、コードビハインドのC#/VBコードから、Bindingオブジェクトによるターゲットとソースの関連付けを行っていた。しかしながら、バインディングはXAMLコードでも記述が可能であり、実は、そちらの方が一般的な方法である。

 では、XAMLによるBindingオブジェクトの記述方法を見ていこう。

<StackPanel x:Name="MainPanel" Loaded="StackPanel_Loaded">
  <TextBlock>
    <TextBlock.Text>
      <Binding Path="Height" Mode="OneWay"/>
    </TextBlock.Text>
  </TextBlock>
  <TextBlock>
    <TextBlock.Text>
      <Binding Path="Weight" Mode="OneWay"/>
    </TextBlock.Text>
  </TextBlock>
</StackPanel>

プロパティ要素構文を使ったBindingオブジェクトの記述(XAML)
太字部分が追記されたコード。

private void MainPanel_Loaded(object sender, RoutedEventArgs e)
{
  MainPanel.DataContext =
    new Person() { Height = 1.7, Weight = 60 };
}

Private Sub MainPanel_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
  MainPanel.DataContext = _
    New Person() With {.Height = 1.7, .Weight = 60}
End Sub

DataContextプロパティにソースを設定するコード(上:C#、下:VB)
Bindingオブジェクトの記述が削除された。

 XAMLによる実装を見て、先ほどのC#/VBによる実装と異なっていることに気が付いただろうか。第1回で解説した「XAML構文」を思い出してほしい。XAMLコードで要素を宣言することは、オブジェクトを生成するということと等価である。属性として設定したプロパティ値も、文字列からStringオブジェクトが生成されるか、変換されてほかのオブジェクトが生成される。

 そのようなルールに従えば、上記のXAMLコードの場合、TextBlock要素のTextプロパティには、新たに生成されたBindingオブジェクトが設定されることになる。しかしながら、TextプロパティはString型であるため、Stringオブジェクトか、それに変換可能なオブジェクト以外を設定することはできないはずだ(BindingオブジェクトはString型には変換できない)。また、実際にTextプロパティの値として表示されるのは、実行時にBindingオブジェクトによって取得されるPersonオブジェクトの各プロパティ(Height/Weight)の値でなければいけない。

 このような通常の解釈とは異なる動作が必要な場合のために、XAMLには「マークアップ拡張」という機能が用意されており、その代表例が<Binding>要素である。あえて一般的ではないコードによるBindingオブジェクトの実装を先に紹介したのは、このマークアップ拡張が一般的なXAMLのルールから外れている(ルールが拡張されている)ことを理解してほしかったためだ。

マークアップ拡張機能

 通常の解釈と異なる動作とは、「実行時評価」と「既存オブジェクトの参照」だ。

 もう一度バインディングの動作を思い出してほしい。バインディングは、2つの異なるオブジェクトのプロパティ値を同期させる機能だ。ターゲットのプロパティに設定される値はソースの値に応じて実行時に変化し、その値は当然ながら(<Binding>要素によって)新しく生成されたBindingオブジェクトではない。つまり、バインディングには「実行時評価」と「既存オブジェクトの参照」という2つの特殊な動作が必要なのである。

 さらに、このような動作のサポートのほかに、マークアップ拡張が持っている機能がもう1つ存在する。それは、冗長になりがちなプロパティ要素構文を改善するための「マークアップ拡張構文」だ。

 先ほどのXAMLコードを、マークアップ拡張構文を使用した記述に書き換えた場合、下記のようになる。

<StackPanel x:Name="MainPanel" Loaded="StackPanel_Loaded">
  <TextBlock Text="{Binding Path=Height, Mode=OneWay}"/>
  <TextBlock Text="{Binding Path=Weight, Mode=OneWay}"/>
</StackPanel>

マークアップ拡張構文を使った記述(XAML)

 マークアップ拡張構文は、カーリーブラケット( { と } )を使って記述することで、通常の属性構文によるプロパティ値の設定と区別される。また、各プロパティは「プロパティ名=値」のような形式で記述し、カンマを使って区切ることで複数のプロパティを設定できる。加えて、マークアップ拡張構文では、コンストラクタに引数を渡すことができる。

 以下のように、最初のトークンに等号が含まれない場合、それはコンストラクタ引数として扱われる。

<StackPanel x:Name="MainPanel" Loaded="StackPanel_Loaded">
  <TextBlock Text="{Binding Height, Mode=OneWay}"/>
  <TextBlock Text="{Binding Weight, Mode=OneWay}"/>
</StackPanel>

引数付きコンストラクタを使ったBindingオブジェクトの記述(XAML)
「Height」や「Weight」がコンストラクタ引数として扱われる。

 上記のように記述した場合、コンストラクタ引数としてそれぞれHeight、Weightが渡されるため、Pathプロパティの記述は不要となる。

 なお、すべてのマークアップ拡張が引数付きのコンストラクタを持っている保証はないし、コンストラクタ引数と等価な意味を持つプロパティを持っている保証もない。必ずコンストラクタ引数を指定しなければならないマークアップ拡張もあるので、注意が必要である。

 さて、ここまで紹介してきたサンプルでは、ソースからターゲットへの一方向のみのバインディングであった。単にデータを表示するだけであれば一方向で済むのだが、画面上でデータの入力もしたいということになれば、双方向のバインディングが必要になる。そこで次に双方向バインディングを説明する。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。