前回までの続き
View<->ViewModel<->Model間の通信について、前回はViewModel<->Modelをやりました。ので、今回はView<->ViewModelです。MVVMというか、WPFでしんどいのはViewです。Viewが絡むと、しんどそうだなあと思っておけば良いと思います。まあ別にWPFに限らずViewは大抵の分野でしんどいです。なのでMVVMやらMVCやらでViewを出来る限り追いだそうとしている感じです。
前回も述べましたが、データバインディングはView<->ViewModel間の通信ですが、今回はそれ以外の方法を述べます。まあ、より能動的な通信を扱います。
View<-ViewModel
ViewModelがViewを呼び出したい最もポピュラーなケースは画面遷移だと思います。こればかりはデータバインディングではどうしようもありません。で、どうするかですが、大きく2パターンあります。
- コードビハインドを使う
- メッセージ機構を使う
コードビハインドというのは、View(XAML)がコンパイルされたコントロールクラスの部分クラスにコードを直接書くことでした。このやり方は簡単ですが、ViewをXAMLで閉じる事が出来ません。つまり、「XAMLさえ見ておけばViewが何やってるのかわかる」という前提が崩れます。どうでしょう、規模が小さければ問題ありませんが、ある程度複雑になるとしんどそうですね。でも規模が小さかったり、ViewとViewModelの関係が強い、つまり一対一で対応することを前提として良いのなら、大丈夫かもしれません。
続いて、メッセージ機構はViewをXAMLで閉じさせるためのものです。メッセージ機構は少々面倒くさく、1回しか使わないなら大げさかもしれません。でもViewがXAMLで閉じている、つまり、Viewが操作的ではなく宣言的でいられることは嬉しい事です。とりあえずこの記事ではメッセージ機構を用いたView<-ViewModel通信を説明します。
メッセージ機構は、その名の通りViewModelからViewに向けてメッセージを投げるわけです。さて、Twitterクライアントを考えましょう。アプリを起動すると真っ先にTimeLineWindowが表示されます。が、認証が済んでいない場合は、ConfigWindowを表示してOAuth認証をする必要があります。お、画面遷移ですね。あ、コードは前回のものを引き継いでいます。また、前回同様必要な部分のみを書いてますが、前回書いた部分があるものとして見てください。
using Livet.Messaging;
class TimeLineViewModel
{
Authorizer authorizer = new Authorizer();
public void Initialize()
{
if (!authorizer.IsAuthorized)
{
var message = new InteractionMessage();
Messenger.Raise(message);
}
}
}
これでViewにメッセージを投げたわけですが、当然ながらこれだけでは画面遷移はされません。Viewがこのメッセージを解釈して画面遷移する必要があります。が、LivetにはViewに画面遷移をお願いする専用のメッセージがあります。今回はそれを使いましょう。
先ほどのmessage
変数を以下のように変更します。
var message = new TransitionMessage(typeof(Views.ConfigWindow), new ConfigViewModel(), TransitionMode.Modal);
まあなんとなくわかると思います。ConfigWindowをView、ConfigViewModelをViewModelとしてモーダルで表示します。が、これだけでは動きません。というかこれだけで動いたら怖すぎです。View一切絡んでないのにViewの挙動を変えられるわけが無いです。ので、View側でメッセージを受け取る用に書きます。あ、もちろんXAMLで、です。
TimeLineWindow.xaml
に<i:Interaction.Triggers> ...
みたいな部分があると重います。このタグの直接の子要素となるように以下を書いてください。つまりi:Interaction.Triggers
タグの直下に置けばよいです。
<l:InteractionMessageTrigger Messenger="{Binding Messenger}">
<l:TransitionInteractionMessageAction/>
</l:InteractionMessageTrigger>
AuthorizerクラスのIsAuthorzed
プロパティを取り敢えずfalse
を返すように定義し、実行してみてください。モーダルでウィンドウが表示されたかと思います。まあ画面遷移は正直どうでもよくて、重要なのはViewModelからViewへメッセージが送れたことです。
さて、メッセージ機構の特筆すべき点として、以下の2点があります。
- XAMLを見るだけで何が起きているかが(少なくとも何かが起きているかが)わかる
- ViewModelがViewオブジェクトを一切触っていない
XAMLを見ると~の部分は<<l:InteractionMessageTrigger ...
の部分ですね。正直言って今はこんなの見ても呪文にしか見えないと思いますが、まあ画面遷移という操作(コードでの記述)が行為の宣言(XAMLでの記述)に置き換わっているのは嬉しい点です。
また、ViewModelがViewオブジェクトを一切触っていないので、思わぬ事故があり得ません。思わぬ事故というのは、初回にXAMLはAltJSみたいなものだと言いましたが、Viewオブジェクトを触る行為はAltJSで言うところの生成済みJavaScriptコードを触るようなものです。XAMLで完結しないなら頑張ってXAMLを読み解く意味がなくなってしまいます。
メッセージ機構の仕組みとか、XAMLに書かれている意味とか、そもそも画面遷移を行うコードはどこだ?といった疑問はあるかと思いますが、今はスルーして先に進みましょう。
View->View
画面遷移を通じて何らかの値をやりとりしたい場合は珍しくないでしょう。Twitterクライアントの場合もそうです。ConfigWindowが認証を行った場合、その結果をTimeLineWindowに返してやる必要があります。つまり、View->Viewのような通信です。これはViewModelを利用することで可能です。
var config = new ConfigViewModel();
var message = new TransitionMessage(typeof(Views.ConfigWindow), config, TransitionMode.Modal);
Messenger.Raise(message);
Console.WriteLine(config.Result)
もちろんConfigViewModelではViewに渡したい値をプロパティに設定しておく必要があります。お互いにViewModelを挟んでいるので、より正確にはViewModel->ViewModelな通信と言えますね。
View->ViewModel
先ほどの逆パターンですね。ViewがViewModelへ通信したいケースもデータバインディングでは不可能なもの、つまりメソッドの呼び出し等が考えられます。Twitterクライアントの場合だと、ツイートボタンを押した場合にViewModelのメソッドが呼ばれて欲しいところです。これも先ほど同様コードビハインドを用いたやり方があり、メリット・デメリットも先ほど説明した通りです。
View->ViewModel通信において、コードビハインドを用いないやり方として、コマンド、ビヘイビア、アクションなどがあります。この辺りは後の回で詳しくやるつもりです。まあ、今回は全体の流れを俯瞰するのが目的なので、さくっとやります。ということで、最も簡単なアクションを使ってViewからViewModelのメソッドを呼び出してみましょう。
まずはTimeLineWindowに文字列を入力するためのTextBoxと、ツイートするためのButtonをツールボックスから追加します。次に、ツイート内容をデータバインディングで取得するために、TimeLineViewModelにpublic string Text { get; set; }
を追加します。TextBoxの変更を反映させるため、このText
プロパティはsetterを持つ必要が有ることに注意してください。また、変更通知プロパティではないのでText = "ほげ";
とかしてもViewには反映されません。最後に、TimeLineWindowのXAMLから<Text Text="TextBox" ...
を<Text Text="{Binding Text}" ...
に変更します。これでツイート内容のデータバインディングが出来ました。
さて、肝心のツイートボタンですが、まずはボタンが押されるたびに呼ばれるメソッドをViewModelに定義しましょう。とりあえずツイート内容をコンソールにでも表示してみましょうか。
class TimeLineViewModel : ViewModel
{
public string Text { get; set; }
public void Tweet()
{
Console.WriteLine(Text);
}
}
XAMLのButtonタグを以下のように変更してください。あ、前から特に注意書きなくやっちゃってましたがXAMLコードをこの記事で見せる時に...
とあるのは省略を意味しています。まあ、多分Marginとかあると思いますが、まあ、こんなのまでコピペしてもアレなんで。
<Button ...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="Tweet"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
これでボタンが押されるたびにViewModelのTweet
メソッドが呼ばれます。あ、先ほどのViewModelからの画面遷移メッセージのコードはコメントアウトなりなんなりしておきましょう。どうでしょう、VSの出力ウィンドウにツイート内容が表示されたと思います。あとはModel内でTwitter APIを呼び出して、ViewModelからModelのツイートメソッドを呼び出せば完了です。
Twitterクライアント
というわけで今までの知識を結集すればTwitterクライアント、と言えるかどか微妙な何かしら、は作れるんじゃあないでしょうか。で、実際に作ってみました。ただ、いくつかまだ説明していない機能を使用しているのと、Twitter API部分とかはこの記事の趣旨的にまるで関係ないです。
https://github.com/kokudori/Modoki
全ての部分を理解できる必要はないので、まずはView<->ViewModel<->Modelの通信の流れを確認してみてください。細かい部分はさておいて、大まかな流れをふわっとでも知れたら万々歳です。
あ、この記事ではView->ViewModel通信にアクションを使いましたが、サンプルではコマンドを使っています。ViewModelCommand
というやつです。ただ、まあ、今は同じようなものだと思って頂いて結構です。
あと、デスクトップなのでconsumer_keyとconsumer_secretを隠せません。ので、そのままベタ書いてますが、悪用とかはしないでください。お願いします。
まとめ
この3回まででイントロダクション終了です。世のWPF入門は最初に小難しいこと(依存関係プロパティとかとか)をやるっぽいんですが、このシリーズではそういうのを出来るだけ排除して、まずは流れを理解していただこうみたいなコンセプトでした。が、次回以降はもうちょっと突っ込んだ話になる気がします。
次回ですが、XAMLについての説明をします。正直言ってこのシリーズ最大の山場です。WPFにおいて最も難しいのはデータバインディングでもMVVMでもなく、XAMLです。単純に難しいのもそうですが、理不尽が服着てグーで殴ってくるみたいなところがあるので、まあ、頑張りましょう。