(1/3)データとなるモデルと、表示を行うビューを結び付ける「データ・バインディング」と、データの表示をカスタマイズできる「データ・テンプレート」について解説。今回および次回の2回に渡り、ビューとモデルの疎結合を実現するための仕組みとして、データ・バインディングとコマンドという2つの機能について説明する。今回は、これらの機能の背景にあるGUIアプリケーションに対する要件と、データ・バインディングについて説明を行っていく。■GUIアプリケーションに対する要件WPFのデータ・バインディングやコマンドといった仕組みを説明する前に、そもそもGUIアプリケーションに対して、どのような要件があるのかを整理してみよう。ここでは、実装上で満たすべき要件として「ビューとモデルの疎結合」と、GUIアプリケーションに求められる機能(の中で、今回はデータ・バインディングに関係する部分)を紹介する。●ビューとモデルの疎結合GUIアプリケーション開発においてよくいわれる言葉として、「ビュー(view)とモデル(model)を疎結合にしろ」というものがある。アプリケーションの見た目にかかわる部分(=ビュー)と、見た目と関係なく成り立つロジックや表示したいデータ(=モデル)を分けるのが良いとされる。オブジェクト指向言語の場合には、ビューとモデルを別クラスとして表現し、互いのクラスに参照関係を作らないことを指す(そうすることで、再利用性やテスト可能性が向上する)。「モデル」というと意味が広すぎるが、要は、業務を分析し、モデル化して得られたデータ(=ドメイン・モデル)を指す言葉だ。interface)であろうと)表示や操作の方法にかかわらず変わらない部分を独立させ、その部分を指して「モデル」と呼んでいると思えばいいだろう。●GUIアプリケーションに求められる機能データ・バインディングと関連してGUIアプリケーションに求められる機能としては、以下のようなものがある。通常、モデルの持っているデータをビュー上に表示する方法は1通りではない。同じデータを、テーブルに表示したい場合もあれば、折れ線グラフなどで表示したい場合もあるだろう。テーブルとグラフを同時に表示したうえで、テーブル上での値の変更が即座にグラフ側に反映されてほしい場合も少なくないだろう。Studioを例にとって1つ具体例を挙げるなら、XAMLエディタがまさにこのような挙動になっている。1に示すように、このXAMLエディタは、マウスを使って視覚的にデザインするための「デザイン」画面(画像上部)と、XAMLコードを文字ベースで入力する「XAML」画面(画像下部)を持っている。どちらか片方への変更があった場合、もう一方に即座に変更結果が反映される。同じデータを複数のコントロール上に表示する例(XAMLエディタ)このような機能を実現するためには、データの変更を通知するような仕組みが必要で、このためにWPFは「データ・バインディング」という仕組みを持っている(また、次回説明する「コマンド」も、ビューとモデルの疎結合を保つために有用である)。次節以降では、このデータ・バインディングについて説明していく。■データ・バインディングWPFでは、データ・ソース(=モデルなどの、データの提供元)をビュー(=WPFの場合はXAMLコード)上のUI要素と簡単に結び付けるために、データ・バインディングという仕組みを提供している。「バインディング」(binding:結合)や「結び付ける」という言い方をしているが、これは、値を一度だけ代入するのではなく、「1カ所で値が変化するたびに、ほかの個所にもその変化が即座に伝搬される」ということを指している。データ・バインディングの利用方法アプリケーションの要件によっては、モデルのインスタンスをそのままDataContextプロパティに渡すだけでよい。モデルが最初からWPF向けに作られている場合や、後述するINotifyPropertyChangedインターフェイスなどの仕組みを必要としない単純な要件の場合などである。実際には、このような場合に該当することはあまりなく、WPF向けにモデルをラッピングしたもの(=ビュー・モデルと呼ぶ)を作って、これをDataContextプロパティに渡すことになる。●データ・バインディングの使い方それでは、データ・バインディングの具体例を見てみよう。1に示すように、XAMLコード中にBindingマークアップ拡張を使って「このプロパティの値をここに表示する」という印を入れておく。X}」と書くことで、「Textプロパティの値とXプロパティの値を結び付ける」という意味になる。次に、FrameworkElementクラス(System.Windows名前空間)(Windowクラスもこのクラスを継承している)のDataContextプロパティにデータ・ソースを渡す。2に示すように、分離コード中のビュー・クラス(Windowクラスを継承)のコンストラクタでDataContextプロパティに匿名クラスを使って渡す(ただし、実際には、データの差し替えやテストのしやすさを考えると、ビュー・クラスの外でデータ・ソースを渡す方が好ましい)。1の表示結果このように、単にデータを表示するだけなら何も特殊な実装は必要ない(後述するようなINotifyPropertyChangedインターフェイスの実装は不要)。同名のプロパティさえ持っていればよく、具体的な型が何かは問われない。従って、この例のように匿名クラスであっても構わない(ただし、匿名クラスが利用できるのはアプリケーションが完全信頼で動作している場合のみ)。ここで、DataContextプロパティの値は包含継承(親UI要素から値が引き継がれる)されていて、ビュー・クラスのDataContextプロパティに渡したデータ・ソースは、<TextBlock>要素のDataContextプロパティにも引き継がれている。X}」というマークアップ拡張記述の実体としては、そのUI要素のDataContextプロパティに格納されているオブジェクトのXプロパティを参照することになる。4に示すように用語を定める。データの提供元のオブジェクト。データの提供元となる、バインディング・ソースのプロパティ。データの反映先のオブジェクト。データの反映先となる、バインディング・ターゲットのプロパティ。データ・バインディングのソース/ターゲットに関する用語まず、WPFのデータ・バインディングにおいて、ターゲット・プロパティは依存関係プロパティでなければならない。4で初めてターゲット・プロパティにできるようになったものもある)。一方で、バインディング・ソースには以下のようなものが利用可能である。通常のCLRオブジェクトのプロパティやインデクサをソース・プロパティにできる(アクセス修飾子は「public」である必要がある。フィールドは利用できない)。プロパティ値の取得や更新はリフレクションを介して行われる。ICustomTypeDescripterインターフェイスを実装する場合や、TypeDescriptionProviderクラスを使って型情報をWPFに対して登録している場合(いずれのクラスもSystem.ComponentModel名前空間)には、これらを介して値の取得・更新が行われる。値の変更を通知するためには、INotifyPropertyChangedインターフェイス(System.ComponentModel名前空間)を実装する必要がある。ソース・プロパティに依存関係プロパティを用いる場合、パフォーマンスもよく、標準で値の変更を通知する仕組みも備えている。依存関係プロパティを利用するためには、バインディング・ソースが依存関係オブジェクトである(DependencyObjectを継承している)必要がある(その結果、ほかのクラスを継承できなくなる)という問題もある。Bindingマークアップ拡張の指定の仕方によっては(SourceプロパティとXPathプロパティを利用)、XMLオブジェクトを直接バインディング・ソースにすることができる。4からは、IDynamicMetaObjectProviderインターフェイスを実装する動的オブジェクトもバインディング・ソースに指定可能になった。IronPythonやIronRubyなどの動的言語を用いて作成したオブジェクトを、そのままバインディング・ソースにできる。実際のところ、CLRオブジェクトをバインディング・ソースとして利用する場合が多いだろう。そこで重要になるのがINotifyPropertyChangedインターフェイスを利用した値の変更通知である。このINotifyPropertyChangedインターフェイスの実装方法については、次回で説明する。それでは、次のページからはBindingマークアップ拡張の書き方について詳しく説明していこう。メールマガジンをご購読ください。申し込みページへITmediaはアイティメディア株式会社の登録商標です。