第5回 WPFの「データ・バインディング」を理解する:連載:WPF入門(3/3 ページ)
データとなるモデルと、表示を行うビューを結び付ける「データ・バインディング」と、データの表示をカスタマイズできる「データ・テンプレート」について解説。
●データ・テンプレート
文字列や数値などの単純な型だけでなく、任意のデータ型を表示するための仕組みとして、データ・テンプレートというものがある。前回で説明したコントロール・テンプレートと似ているが、名前どおり、コントロール・テンプレートがコントロールの表示方法をカスタマイズするものであるのに対して、データ・テンプレートはデータの表示方法をカスタマイズするものである。
○ContentControlクラス:単一のデータに対するデータ・テンプレートの適用
まず、単一の(コレクションではない)データに対するテンプレートについて説明しよう。単一データに対するテンプレート指定は、ContentControlクラス(Sytem.Windows.Control名前空間)のContentプロパティにデータを、ContentTemplateプロパティにデータ・テンプレートを渡すことによって行う*。
* ちなみに、Buttonクラスなどのコントロール類の多くはContentControlクラスを継承していて、このデータ・テンプレートの仕組みを利用可能である。
ここでは、標準のクラス・ライブラリで提供されているデータ型の中から、式ツリー(System.Linq.Expressions名前空間以下のクラス)を例にとって説明していく。まず、List 12に示すように、「(x, y) => x + y」という(ラムダ式の)式ツリーをDataContextプロパティに渡す。
System.Linq.Expressions.Expression<Func<int, int, int>> sample =
(x, y) => x + y;
this.DataContext = sample;
Dim sample As System.Linq.Expressions.Expression( _
Of Func(Of Integer, Integer, Integer)) = _
Function(x, y) x + y
Me.DataContext = sample
比較のために、テンプレート指定のない場合にどうなるかを見てみよう。List 13に示すように、Contentプロパティのみを指定する。ソース・プロパティとして指定しているBodyプロパティには、ラムダ式の本体部分(=「x + y」の部分)が格納されている。
……省略……
<StackPanel>
<ContentControl Content="{Binding Body}" />
</StackPanel>
……省略……
データは、<ContentControl>要素のContentプロパティに指定する。
この場合、ただ単に「(x + y)」という文字列だけが表示されるはずだ。これは、式ツリーをToStringメソッドで文字列化したものが表示されている。
それでは、データ・テンプレートを指定してみよう。
List 14に示すように、ContentTemplateプロパティに対して、<DataTemplate>要素を指定する。Figure 8に表示結果を示す。データ・テンプレートを指定しなかった場合もまとめて表示している。図中の上段が指定なし、下段が指定ありの場合の表示結果である。
……省略……
<Grid>
<ContentControl Content="{Binding Body}"
Grid.Row="1" Grid.Column="1">
<ContentControl.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NodeType}"
Background="LightBlue" />
<TextBlock Text=": " />
<TextBlock Text="{Binding Left}"
Background="LightBlue" />
<TextBlock Text=", " />
<TextBlock Text="{Binding Right}"
Background="LightBlue" />
</StackPanel>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
……省略……
データ・テンプレートは、<ContentControl>要素のContentTemplateプロパティに指定する。
○データ・テンプレートの自動適用
データ・テンプレートは、もちろんリソース化して複数の<ContentControl>要素間で共有できる。また、コントロール・テンプレートで自動適用ができたように(具体的には、<ControlTemplate>要素にTargetTypeプロパティを設定)、データ・テンプレートも<DataTemplate>要素のDataTypeプロパティを設定することで、指定した型に対してデータ・テンプレートを自動適用できる。
例えば、List 14の例は、List 15のように書き換えられる。
……省略……
<Grid xmlns:exp="clr-namespace:System.Linq.Expressions;assembly=System.Core">
<Grid.Resources>
<DataTemplate DataType="{x:Type exp:BinaryExpression}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NodeType}"
Background="LightBlue" />
<TextBlock Text=": " />
<TextBlock Text="{Binding Left}"
Background="LightBlue" />
<TextBlock Text=", " />
<TextBlock Text="{Binding Right}"
Background="LightBlue" />
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ContentControl Content="{Binding Body}" />
</Grid>
……省略……
データ・テンプレートをリソース化して<DataTemplate>要素のDataTypeプロパティを設定することで、指定した型に対して一括でデータ・テンプレートを自動適用できる。この例では、System.Coreアセンブリに含まれるBinaryExpression型(System.Linq.Expressions名前空間)のデータすべてに対して、<DataTemplate>要素で定義されたデータ・テンプレートが自動的に適用される。
データ・テンプレートの自動適用は、型の混在する階層的なデータに対してテンプレートを適用したいときに特に重宝することだろう。
例えば、式ツリーは、その名前どおり、階層的なデータ構造をしているので、List 15に例示したBinaryExpressionクラスなら、LeftプロパティやRightプロパティも式ツリーになっていて、BinaryExpressionクラスをはじめとするさまざまなクラスが格納されている。このとき、Leftプロパティなどを再度<ContentControl要素>のContentプロパティにバインディングしておけば、階層的なテンプレート適用が行われる。
式ツリーを構成するさまざまな型すべてに対してデータ・テンプレートを適用できるリソース・ファイルを、下記のURLからダウンロードできるようにしたので、興味がある場合にはダウンロードしてみていただきたい。リソース・ファイルの利用方法は、本連載第4回の「●外部リソースの取り込み」の項を参照してほしい。
- 式ツリー表示用のリソース・ファイル(ExpressionTreeTemplates.xaml)のダウンロード(右クリックして[対象をファイルに保存])
このデータ・テンプレート一式を使って、
(int x, int y) => (x + 3) * (y - 1)
というラムダ式から得られた式ツリー全体を表示すると、Figure 9のようになる。
Figure 9: 階層的なデータに対してデータ・テンプレートを適用した例
ここまでの実行例では「{Binding Body}」としてラムダ式の本体部分の式ツリー(=Bodyプロパティ)のみを表示していたが、この例では「{Binding}」(=「{Binding Path=.}」とも記述可能)としてラムダ式全体(=バインディング・ソース)の式ツリーを表示している。
○ItemsControlクラス:コレクションに対するデータ・テンプレートの適用
データ・ソースがコレクションの場合、ItemsControlクラス(Sytem.Windows.Control名前空間)のItemsSourceプロパティにデータを、ItemTemplateプロパティにデータ・テンプレートを渡すことによってデータ・バインディングを行う。ItemTemplateプロパティに指定したテンプレートは、コレクションの要素1つ1つに対して適用される。また、この場合にも、(単一のデータの場合と同様に)データ・テンプレートの自動適用は有効である。
List 12と同じデータ(「x + y」という式ツリー)に対して、List 16に示すようなXAMLコードを書くと、Figure 10のような表示結果が得られる。ソース・プロパティとして指定しているParametersプロパティには、ラムダ式の引数リストの部分(=「x, y」の部分)が格納されていて、これはParameterExpression型のリストになっている。各要素には、型に基づいてテンプレートが自動適用される。
……省略……
<Grid xmlns:exp="clr-namespace:System.Linq.Expressions;assembly=System.Core">
<Grid.Resources>
<DataTemplate DataType="{x:Type exp:ParameterExpression}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"
Background="LightPink" />
<TextBlock Text=":" />
<TextBlock Text="{Binding Type.Name}"
Background="LightPink" />
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ItemsControl ItemsSource="{Binding Parameters}" />
</Grid>
……省略……
データは<ItemsControl>要素のItemSourceプロパティに指定する。データ・テンプレートは、個々の<ItemsControl>要素のItemTemplateプロパティに指定することもできるが、この例では、データ・テンプレートをリソース化して<DataTemplate>要素のDataTypeプロパティを設定することで、指定した型に対して一括でデータ・テンプレートを自動的に適用している。
【コラム】C#コード内でデータ・バインディング
参考までに、データ・バインディングをC#コードだけで記述する方法に触れておこう。
「<TextBlock Text="{Binding Path=X, Mode=OneWay}" />」という記述と同様のデータ・バインディングをC#コード内で行うためには、List 17に示すような記述が必要になる。Bindingクラス(System.Windows.Data名前空間)のインスタンスを作成し、FrameworkElementクラスのSetBindingメソッドを通じて、データ・バインディングを行う。
var source = new { X = 10, Y = 20 };
var binding = new Binding();
binding.Source = source;
binding.Mode = BindingMode.OneWay;
binding.Path = new PropertyPath("X");
var target = new TextBlock();
target.SetBinding(TextBlock.TextProperty, binding);
次回はコマンドに関する説明を行う。また、MVVMパターンと呼ばれる、データ・バインディングとコマンドを利用して、ビューから状態を切り離す手法に関しても説明を行っていく。
「連載:WPF入門」
Copyright© Digital Advantage Corp. All Rights Reserved.