LOB開発といえば企業が利用する業務システムで、Windows FormとSQLを利用して、あっという間にデータベースと連係して動くアプリケーションを作成できる技術者は多いと思います。
そんなところに出てきたWPF(Windows Presentation Foundation)。
とても優れた技術ですが、これをどうLOB開発として利用するかは少し敷居があるのかと感じています。
『WPF ViewModelでLOB開発』シリーズでは、WPFの恩恵を十分に受けつつ、より簡単に開発するための情報を展開します。
WPFテクノロジー
技術詳細についてはMSDN か 荒井省三さんのBlog のWPF編を読むことをお勧めします。
ここではVisualStudioを利用しながら動かすことに視点を合わせたいと思います。
ViewModel
ビューモデルと呼び、この連載タイトルの一部にも登場しています。
Model-View-ViewModel というデザイン パターンの中で、画面の状態を持つオブジェクトです。
Wikiを読むと難しく早くも挫折しそうですが、手を動かしながら確認したいと思います。
Visual Studioを起動して SimpleApp というWPFアプリケーションを作成
自動でxamlが二つ生成されています。
- App.xaml
- System.Windows.Application クラスから派生し、WPFアプリを起動する役目を持ちます。
- MainWindowo.xaml
- メイン画面で、Model-View-ViewModel でいうところの View になります。 今回は中央にSampleTextBoxという名前を付けたテキストボックスを配置しました。
1
|
|
ここに MainWindow の状態を持つための ViewModel を追加します。
Textという文字列型のプロパティを持ち、コンストラクタで初期化しています。
1 2 3 4 5 6 7 8 |
|
次に MainWindow という ビュー が ビューモデル を利用してデータを表示するための設定を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
DataContext というプロパティ名が表すように、ここに設定したオブジェクトがデータを表すということになります。
これで ViewModel の設定は完了しました。
ここからさらに各コントロール(ここではテキストボックスだけですが)の データ バインディング を行います。
1
|
|
バインディングの方法は様々でXaml(ザムル)といわれる画面を構成するXML上で
<TextBox Name="SampleTextBox" Text="{Binding Path=Text}"/>
と記述することも可能ですが、ここでは意図してコード上でバインディングしています。
TextBox.TextProperty は依存関係プロパティといい、このバインディングの仕組みはWPF プロパティ システムと呼ばれます。 Bindingクラスのコンストラクタ パラメーターは ViewModel のプロパティ名を設定しています。 この状態でF5キーを押下してデバック実行すると・・・
表示されましたね!
フレキシブル レイアウトで、テキストボックスは画面を大きくすると画面と一緒に大きくなります。
画面での入力内容が、DataContext に設定したオブジェクトのプロパティに自動設定されるなどの一連のバインディングの仕組みが用意されており、 この仕組みを利用するのがModel-View-ViewModel デザイン パターンです。
注目するのはMainWindowViewModelクラスで、UIから独立した素のクラスです。
従来の画面とモデルを紐付けるよなグル(接着剤)コードがViewModel上には出てこなくなりました。
LOB開発基盤 - ViewModel
さて、ここまでで発生した「おまじないコード」は・・・
- WindowのDataContextプロパティへの設定
- TextBox.TextPropertyという依存関係プロパティを利用したバインディング作業
になります。
まだまだ記述していないおまじないコードもあり、このままではLOB開発に展開できません。
これらのおまじないコードを基底クラスと一連の機能に隠ぺいすることで、最終的にはViewModelで簡単に画面遷移できるようなソリューションになれれば、WPFの恩恵を受けながらLOB開発を進められるかも?!
基盤はこれからのシリーズで少しずつ機能を固めていくので、まずは準備しましょう。 まずは INotifyPropertyChanged インターフェイスを実装したViewModelのベースクラスを準備します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
ソースコードは荒井省三さんのBlogで紹介されているものを拝借しました。 ポイントは二つで
- INotifyPropertyChanged インターフェイス
- CallerMemberName 属性
になります。
INotifyPropertyChanged インターフェイスは、データ バインディングの仕組みの一つで、何かの処理を実行してViewModelのプロパティに値をセットした場合、それをView側に通知する機能です。
この仕組みで、画面上のデータが再描画されます。
プロパティのsetで呼び出すようにします。
CallerMemberName 属性は.Net4.5で新しく出てきた機能で、呼び出し元のメンバー名を自動設定してくれる機能です。
少し前までは、プロパティのsetで OnPropertyChanged("●×プロパティ名")
みたいにコーディングしていたのですが、その文字を記述する必要がなくなりました。
.Net4.0以前では利用できませんが、必須な属性ではなく、文字列でプロパティ名をコーディングしていた世界から少しだけタイプセーフになりましたというところです。
こういうおまじないコードをViewModelの基底クラスに準備しておくことにします。
次にバインディングの作業です。 先ほどタイプセーフと呼びましたが、なるべくコンパイラのチェックが働くようなコーディングだとソースコードの量が増えたときに便利です。 そこで、バインディングのときにコーディングした
1
|
|
ここの”Text”というプロパティ名もタイプセーフにセットできるようにしたいと思います。
これはWPFとは関係のない、ただの技術情報なのですが、ラムダというコーディングの記述方法があります。
LINQが利用されるようになってきて、このラムダにおける型の推論によるコーディング方法も浸透してきたと思います。
1 2 |
|
C#の経験があるといってもラムダを初めてみる人は、 c => c.City == "London"
の記述を見るとびっくりするかもしれませんが、VisualStudioでインテリセンスが働くので、すぐに慣れると思います。
このラムダを利用して、タイプセーフにViewModelのプロパティ名を取得するような簡単なユーティリティを準備します。 こういうのはすでに世の中に出回っているので、探せば出てきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
あとは MainWindowViewModel のベースクラスを BizViewModel として
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
さっきのユーティリティを利用すると
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
これでコンパイラでチェックがかかるタイプセーフなWPFの簡単な基盤ができました。 イベント系などは次回紹介するとして、今はViewおよびViewModelの役割を設定していきたいと思います。
LOB開発基盤 - View
次にViewについて少し掘り下げていきます。 今はWindowのXamlに直接記述しています。
1 2 3 4 5 6 7 8 |
|
Windowタグの中には一つのコンテンツのみを配置できます。
今は <Grid>
が配置されています。コードで記述すると
1 2 3 4 5 6 7 8 9 10 |
|
なので、コンテンツは一つのみとなります。 ただし、このコンテンツがどのように描画されるかはWPFの拡張機能によって変わってきます。
試しにデバック実行し、WPFビジュアライザーで確認してみると
MainWindowの中にあるContentPresenterにGridが配置されています。
別の機会に詳しく紹介しますが、WPFのStyleが適用されている結果このような表示になります。
少し前までAero.NormalColor.xamlなどスタイルがMSDNからダウンロードできたのですが、リンクが見つかりませんでした。 現在のデバック実行した環境でWindowクラスに適用されているスタイルは、以下のようになっているのだと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
TemplateというWPFの外観をカスタマイズできる機能があり、ここにコンテンツを表示する際はBorderを用意して、AdornerDecoratorを用意してその中にContentPresenterを・・・という風に定義されています。
話を戻すと、Windowに表示されるコンテンツはContentPresenterを利用して表示されるようになっています。
MSDNのContentPresenterをみると、コンテンツがどのようなロジックで表示するかの記述があります。 たとえば、コンテンツに文字列を設定するとTextBlockが自動で作成されて、表示されるようになります。
F5キーでデバック実行
任意のクラスの場合はToStringの結果が表示されるのですが、その場合に何を表示すべきかをカスタマイズする機能があります。
試しにContentTemplateSelectorプロパティに設定して動作確認してみましょう。
戻り値のDataTemplateはビジュアル要素(つまりView)をあらわし、引数のitemは任意のデータをあらわします。
この仕組みの面白いところは、データがビジュアル要素を決定するというところです。
つまり、ビジュアル要素はデータの属性として表現すると、仕組みを上手に利用できそうだとわかります。
さっそくオブジェクトとしてそれを表現してみたいと思います。
ビジュアル要素はデータの属性として扱うので
1 2 3 4 5 6 7 8 |
|
この属性はデータ(BizViewModelから派生した任意のViewModel)に付与することになります。
次にビジュアル要素(ビュー)用の基底クラスを用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
上記二つを利用して、BizViewModelに肉付けします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
|
先ほどDataTemplateSelectorを派生した動作確認用のサンプルを作成しましたが、正しくビジュアル要素を返すDataTemplateSelectorを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
ここまででのLOB開発用の基盤は以下のようになっています。
これを早速使って画面を表示したいと思います。
LOB開発基盤 - はじめの一歩
コンテンツもViewModelも何もない空のMainWindowのみの状態です。
ここにViewを追加します。
名前はMainViewで、WPFのユーザーコントロールで追加します。
VisualStudioにはItemTemplatesフォルダがあって、これを利用すると便利なのですが、それは次回に紹介します。
MainViewクラスのベースクラスをBizViewに変更します。
この作業に今は手間がかかりますが、ItemTemplateを紹介するまでの我慢です。
MainView.xamlに先ほど作成した基盤の名前空間をインポートxmlns:uen="clr-namespace:uEN.UI;assembly=uEN"
して、メインのタグを<uen:BizView
に変更します。
合わせてMainView.xaml.csのベースクラスもBizView
に変更します。
このViewに先ほどと同じようにテキストボックスを配置します。
次にMainViewModelを追加します。
1 2 3 4 5 6 7 8 9 |
|
このViewModelをMainViewのBuildBindingでデータバインディングします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
今は空のMainWindowとMainView/MainViewModelがある状態です。
最後にMainWindowにコンテンツとセレクターを設定します。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
以上で準備が完了です。
デバック実行すると・・・
期待した結果が表示されました!
WPFの「おまじないコード」などを基盤に入れていくと、それを利用するLOB開発は楽になると思います。
たとえば
- BizViewModelにShowメソッドを用意し、WindowをNewするコードを入れれば・・・
- コマンドのバインディングのラッパーを・・・
- ロギングが・・・
思いつくままをオレオレ実装してもいいですし、Application Architecture Guideなどを読み解いて、しかるべき処理を準備していくのも一つかもしれません。
次回は今までの作業で手間だったものの自動化を考えてItemTemplatesの紹介と、もう少し踏み込んでボタンを押して動くところまでもっていければと思います。
今回のサンプルはコチラ