かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

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素敵。