Model View ViewModelパターンを使ってると、ViewModelのアクションの後にViewをアニメーションさせたいということをやろうとすると、ViewModelからViewへ依存をさせないといけないのか・・・?とか、ちょっと悩んでしまいます。ViewModelからイベントを通知して、View側で、そのイベントをハンドリングするっていうのも、何だかイベントハンドラの登録処理を書いたり、イベントハンドラのコード書いたりするのめんどくさいしなぁ・・・と思ったりします。
そんな思いをもやもやと抱えたままExpression Blend 4のビヘイビア覗いてみたらよさげなのがありました。その名も「GoToStateAction」です。こいつは、イベントに応じて任意のViewStateに遷移するという代物です。こいつを使うと、ViewModelでイベントさえ公開してれば、それに応じて任意の状態にViewを遷移させるのがBlendからさくっと行えます。
ということでHello world的なサンプルを作ってみようと思います。SilverlightでもWPFでもどちらでもいいのですが、今回はWPFアプリケーションで作成してみました。Silverlightでも同じ要領でいけるはずです。WpfVMFeedbackという名前でBlendからWPFアプリケーションを作成します。最近のExpression BlendはMVVMパターンを支援するような感じで、ViewModelが指定されたUserControlというものがあったりします。折角なので、これを使用してHelloWorldViewという名前でUserControlを作成します。
新規作成をすると、以下のようなViewModelと対応するViewが作成されます。
using System; using System.Collections.Generic; using System.Text; using System.Windows.Data; using System.ComponentModel; namespace WpfVMFeedback { public class HelloWorldViewModel : INotifyPropertyChanged { public HelloWorldViewModel() { } #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String info) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); } } #endregion } }
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:local="clr-namespace:WpfVMFeedback" x:Class="WpfVMFeedback.HelloWorldView" d:DesignWidth="640" d:DesignHeight="480"> <UserControl.Resources> <local:HelloWorldViewModel x:Key="HelloWorldViewModelDataSource" /> </UserControl.Resources> <Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource HelloWorldViewModelDataSource}}"/> </UserControl>
LayoutRootのDataContextにリソースに登録されたViewModelをバインドしているといった作りです。特に変わった感じはない素直な作りになっています。次にVisual StudioでViewModelを作りこんでいきます。とりあえず今回は簡単にするために、メッセージプロパティにHello worldの文字を設定して、処理完了のイベントを通知するだけのシンプルなViewModelの実装にしました。
namespace WpfVMFeedback { using System; using System.ComponentModel; public class HelloWorldViewModel : INotifyPropertyChanged { private string message; public HelloWorldViewModel() { } public string Message { get { return this.message; } set { this.message = value; this.NotifyPropertyChanged("Message"); } } public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// Greetメソッドが終了したときに発生するイベント /// </summary> public event EventHandler GreetFinished; /// <summary> /// 今回のメインの処理 /// </summary> public void Greet() { this.Message = "Hello world"; // 処理が終わったことをイベントで通知しておく OnGreetFinished(); } protected virtual void OnGreetFinished() { var h = GreetFinished; if (h != null) { h(this, EventArgs.Empty); } } private void NotifyPropertyChanged(String info) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); } } } }
特別難しいことはしてません。それではBlendのほうに戻って、とりあえずボタンを押したらHello worldが表示されるところまで作ってみようと思います。
ButtonとTextBlockをさくっとHelloWorldViewに置きます。
そして、TextBlockのTextプロパティとViewModelのMessageプロパティをバインドします。これはBlendからGUIを使って簡単にできます。
次に、CallMethodActionビヘイビアをButtonに割り当てて、ButtonのClickイベント発生時にViewModelのGreetメソッドを呼び出すように設定します。
ここまでで、Buttonをクリックすると画面にHello worldという文字列を出すアプリをMVVMパターンでさっくりと作りました。一応HelloWorldViewをMainWindowに配置して実行した結果を以下に示します。
やっと本題です、Hello worldが表示されたあとに、何かアニメーションを実行したいと思います。とりあえず、一度っきりしかボタンは押せなくていいのでボタンが画面外にす〜〜っと移動して消えてしまうようにしたいと思います。状態ウィンドウから新しくViewStateを作成して、画面外にボタンがあるようにボタンを移動させます。
瞬間移動では面白くないので規定の切り替え動作を1秒に設定して1秒かけて画面の外へいってしまうようにします。
そして、ボタンにGoToStateActionビヘイビアを設定してTriggerを新規作成してViewModelのGreetFinishedイベントに設定して先ほど作成したViewStateに遷移するように設定します。
以上で完成です。実行してみると、ボタンをクリックしてHello worldと表示されると、ボタンが画面外へ逃げて行ってしまいます。ちょっととるのに苦労しましたが、ボタンが移動してる途中の画面は以下のようになります。
こんな感じで、ViewModelでイベントさえ公開してしまえば、あとはBlendを使ってイベントに応答したフィードバックを簡単にアニメーションさせることが出来ます。Blend素敵。