読者です 読者をやめる 読者になる 読者になる

亀岡的プログラマ日記

京都のベッドタウン、亀岡よりだらだらとお送りいたします。

Livetを使ってすっきりWPFアプリを作る ②サンプルプロジェクトからLivetの機能を理解する(メッセージ基本編)

WPF C# Livet

さて、前回(d:id:posaunehm:20111201:1322750644)はさっくりとLivetの概要だけ説明しましたので、ここでLivetの機能を具体的にチェックしてみましょう。・・・といってもLivetはそこそこ大きなライブラリーです(Prismなんかに比べるとかなりすっきりとはしていますが・・・。
というわけで、ダウンロードページに付属しているLivetのサンプルプロジェクトを読みといてみて、そこから主要な機能を理解しておきたいと思います。


まず、プロジェクトの構造はこんな感じ。


f:id:posaunehm:20111203171410j:image


View、ViewModel、Modelがそれぞれ2つずつとなっています。

では、起動してみましょう。


f:id:posaunehm:20111203171412j:image


とまあこんな感じの外観をしています。追加・削除・終了ボタンと、メンバー一覧を表示するリストボックスからなっています。リストボックス内にチェックボックスやボタンが簡単に入れられるのは、最早WPFの常識ですよね。

では実際にメンバーの追加を行なってみましょう。追加ボタンをポチッとな。


f:id:posaunehm:20111203171413j:image


モーダルダイアログが現れました。ここで色々編集して登録を行うわけですね。ちなみに、日付ダイアログにはちゃんとエラー通知の仕組みが施してあります。このあたりは教科書的なWPFの組み方がしてあり、非常に良いサンプルです。
編集画面も、上と同じ画面が立ち上がり、今度はリストにあるものが書き換わります。


次に削除を見てみましょう。チェックボックスをポチポチやると削除ボタンがEnableになるので、削除ボタンをポチッとな。


f:id:posaunehm:20111203171414j:image


おっと、メッセージボックスで確認してくれますね。OKで、削除です。


あとは終了ボタンで、これは見たまんま、プログラムを終了します。これはメッセージボックス等はなし。


さて、ざっと見ると普通のアプリです。ですが、MVVM的には実装で悩む所が入っています。こんなごくごく普通のアプリの中に実装の悩みの種が入ってしまうというのがMVVMの痛いところなのです。
まず困るところは、「モーダルダイアログの表示」や「メッセージボックスの表示」、「アプリケーションの終了」といったView依存の処理がボタンクリック=コマンド通知で実行される、というところです。
コマンド通知で実行されるということは処理本体はViewModelにあります。ですがViewModelからViewは逆方向の参照ですし直接いじるのはキモチワルイです(コンストラクタで子に親を渡してインターフェース経由でごにょごにょするような、「親のすねかじり」モデルになってしまいます)。MessageBoxくらいSystem.Windowsに参照持てばViewModelから呼び出しできてしまいますが、やっぱり責務の切り分けからすると若干の罪悪感を感じます。


それでは、Livetはどうなってますかいね。とりあえず、呼び出されるMainViewのコードビハインドを見てみましょう。

namespace LivetSampleProject.Views
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}


はい、見事にコンストラクタのみのビハインドコードレスなパターンになってます。ふつくしい・・・。では呼び出し側は何をしているのかというと・・・

        private void EditNew()
        {
            //Viewに画面遷移用メッセージを送信しています。
            //Viewは対応するメッセージキーを持つInteractionTransitionMessageTriggerでこのメッセージを受信します。
            Messenger.Raise(new TransitionMessage(new MemberViewModel(new Member(_model),this), "Transition"));
        }


はい、出てきました。これがかの有名なMessengerですね。所謂メッセンジャーパターンをサポートしているわけです。メッセンジャーパターンについては、d:id:griefworker:20110218:mvvm_messenger とかは必読です。


もう少し詳しく見てみましょう。
コード中にあるMessengerというのはクラスのインスタンスプロパティではありません。


f:id:posaunehm:20111203171415j:image


ご覧のように、ViewModel基本クラスの静的メンバーとなっています。(2011/12/04:修正)


ん?ViewModel基本クラス?


そう、LivetにはViewModel基本クラスが実装されています。(まあこれは誰もが最初にMVVMライブラリを作ってやることではありますが)
なのでViewModelの実装は例外なくこんな感じになります。

    public class MainWindowViewModel : ViewModel
    {


ViewModel基本クラスの機能について詳細はちょっと後に回して、Messenger機構に限定して解説を進めます。
ViewModel基本クラスの静的プロパティとして定義されている、ということは、全てのViewModelが同じ一つのMessengerを参照していることになりますね。普通Observerパターンなどで通知機構を作るときには、通知元をコレクションに登録してどうだこうだ・・・ということをやりますが、これでも同等のことが実現できてしまいます。(もちろんやり過ぎるとグローバル変数とおなじになっちゃうのでそれはそれでNGです。フレームワークという形で提供されているのがありがたいですよね。)

ご本人からの指摘により修正・追記:2011/12/04

勘違いしてました・・・。静的じゃないです。
f:id:posaunehm:20111204011132j:image
メタデータビューでもわかるように、どう見てもViewModel毎です。本当に(ry
しかしだからといって話は全然変わらないのが実装の妙なわけで。結局MessengerがViewModel毎で全体として複数になろうとも、こちらとしては通知機構の登録やらを意識しなくてもいいのは変わりません。何も考えずに親クラスのMessengerインスタンスを呼ぶだけで、手軽にメッセンジャー機構は実装できます。

なるほど、これでViewModel側の通知を管理する方法はなんとなく理解できます。では通知の中身はどうなっているでしょう。


f:id:posaunehm:20111203231422j:image


これは通知を発火するに過ぎないわけですね。秘密は中のメッセージ自体に有りそうです。ちなみに「同期」と明示するからには「非同期」もあります。


f:id:posaunehm:20111203231424j:image


まだまだ同期は追加のコードが必要なので(早く来い来いwait-async!)、メソッドレベルで切り分けられるって有難い!

ではメッセージ本体はというと、、、


f:id:posaunehm:20111203231423j:image


TransitionMessageという方のクラスにViewModelを渡しています。それから、stringkeyというパラメータで文字列型のキーを渡しているようですね。ここから察するに、View側で何をするかはメッセージクラスの種別とその文字列キーが深く関わっていそうです。


それではView側の実装を探してみましょう。といってもコードビハインドに無いわけですから、あるとすれば当然XAML側のハズ。そうすると、有ります有ります。

        <l:InteractionMessageTrigger MessageKey="Transition" Messenger="{Binding Messenger}">
            <l:TransitionInteractionMessageAction WindowType="{x:Type v:DetailWindow}" Mode="Modal"/>
        </l:InteractionMessageTrigger>


InteractionMessageTriggerというトリガで、まずViewModel側で受けたメッセージを受け取り、さらにMessageKeyによって文字列フィルタリングをしているのが分かります。さらにその中にTransitionInteractionMessageActionというビヘイビアが定義してあり、指定されたウィンドウ(ここではDetailWindow)をモーダルで出す、というような事が記述されていることがわかりますね。つまり、ViewModel側で通知された変更は、コードビハインドを全く介さずXAML側で受信からそれに対するアクションまで定義できているわけです。



えらく長くなってしまいましたので、この辺で。次は残る2つの通知、メッセージボックスとウィンドウクローズがどう扱われているかを確認してみましょう。実はこれら2つは今日説明したメッセージとはまた違う性質を持つのが、Livetの奥深いところです。