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

かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

MVVMパターンでViewModelからスムーズにViewをアニメーションさせる方法

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 BlendMVVMパターンを支援するような感じで、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素敵。