WPFでコントロールをアニメーションさせてみる

Expression Blend を使えばアニメーションは簡単にできるんだけど、アニメーション中は Width プロパティとかに Auto を指定できないので微妙にめんどくさい(ちなみにコードだと Auto は Double.NaN で指定できる)。
なので、細かい操作は Visual Studio を使ってコードを書いた方が楽みたい。

そんなわけで、以下アニメーションの覚え書き。

1つのコントロールの1つのプロパティをアニメーション

  1. System.Windows.Media.Animation.DoubleAnimation クラスみたいな Animation クラスを生成する。コンストラクタで開始値・目標値・アニメーション時間を指定しておく。 Animation クラスは、変化させる型に応じたものを選択すること。特殊なのは Enum で、 ObjectAnimationUsingKeyFrames クラスを使用する。
  2. 変化させるコントロールの System.Windows.Media.Animation.Animatable#BeginAnimation メソッドでアニメーションを開始する。引数は、変化させるプロパティを表す依存プロパティと、上で生成した Animation クラス。

1つのコントロールの2つ以上のプロパティをアニメーション

アプローチとしては2つ。

  • Animation クラスを複数用意して、BeginAnimation を必要なだけ呼ぶ。
  • System.Windows.Media.Animation.StoryBoard クラスを使う。

MS的には後者が推奨なようですな。具体的な方法はこんな感じ。

  1. Animation クラスを作成。
  2. StoryBoard#SetTargetProperty で、上記で生成した Animation クラスと、変化させるプロパティを表す依存プロパティを指定する。Animatable#BeginAnimation とは引数の順番が入れ替わっていて、依存プロパティを指定するのも PropertyPath 経由になっていてわかりにくい・・・。なんで依存プロパティじゃ駄目なんだろう?
  3. 変化させるすべてのプロパティに対応する Animation クラスを生成する。
  4. StoryBoard クラスを生成して、Children プロパティに生成した Animation クラスを追加する。
  5. 変化させるコントロールの Animatable#BeginStoryboard メソッドでアニメーションを開始する。引数は上記で生成した StoryBoard クラス。

2つ以上のコントロールをアニメーション

これがサンプルが見つからなくて、かなりはまった。方法としては、1つのコントロールの2つ以上のプロパティをアニメーションとほとんど同じ。違いは StoryBoard#SetTargetName で、変化させるコントロールを指定するところ。オブジェクトを指定するんじゃなくて、文字列で名前を指定するのが嫌な感じ。

  1. Animation クラスを作成。
  2. StoryBoard#SetTargetProperty で、上記で生成した Animation クラスと、変化させるプロパティを表す依存プロパティを指定する。
  3. StoryBoard#SetTargetName で、上記で生成した Animation クラスと、変化させるコントロール名を指定する。
  4. 変化させるすべてのコントロール・プロパティに対応する Animation クラスを生成する。
  5. StoryBoard クラスを生成して、Children プロパティに生成した Animation クラスを追加する。
  6. Animatable#BeginStoryboard メソッドでアニメーションを開始する。引数は上記で生成した StoryBoard クラス。メソッドを呼ぶインスタンスは何でもいいみたい。

サンプルコード

WindowにCanvasを置いて、その上にボタンを3つ(left 、 center 、 right)を置いた場合。

using System;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;

namespace SampleWPF {
    public partial class Window1 {
        public Window1() {
            this.InitializeComponent();
        }
        private void left_Click(object sender, RoutedEventArgs e) {
            //1つのコントロールの1つのプロパティをアニメーション
            Duration d = new Duration(TimeSpan.FromMilliseconds(100));
            DoubleAnimation a = new DoubleAnimation(300, d);
            left.BeginAnimation(Canvas.WidthProperty, a);
        }
        private void center_Click(object sender, RoutedEventArgs e) {
            //1つのコントロールの2つ以上のプロパティをアニメーション
            Storyboard s = new Storyboard();
            Duration d = new Duration(TimeSpan.FromMilliseconds(100));

            DoubleAnimation a1 = new DoubleAnimation(50, d);
            Storyboard.SetTargetProperty(a1, new PropertyPath(Canvas.WidthProperty));
            s.Children.Add(a1);

            DoubleAnimation b1 = new DoubleAnimation(50, d);
            Storyboard.SetTargetProperty(b1, new PropertyPath(Canvas.HeightProperty));
            s.Children.Add(b1);

            center.BeginStoryboard(s);
        }
        private void right_Click(object sender, RoutedEventArgs e) {
            //2つ以上のコントロールをアニメーション
            Storyboard s = new Storyboard();
            Duration d = new Duration(TimeSpan.FromMilliseconds(100));

            DoubleAnimation a1 = new DoubleAnimation(100, d);
            Storyboard.SetTargetProperty(a1, new PropertyPath(Canvas.WidthProperty));
            Storyboard.SetTargetName(a1, "left");
            s.Children.Add(a1);

            DoubleAnimation b1 = new DoubleAnimation(100, d);
            Storyboard.SetTargetProperty(b1, new PropertyPath(Canvas.HeightProperty));
            Storyboard.SetTargetName(b1, "left");
            s.Children.Add(b1);

            DoubleAnimation a2 = new DoubleAnimation(500, d);
            Storyboard.SetTargetProperty(a2, new PropertyPath(Canvas.WidthProperty));
            Storyboard.SetTargetName(a2, "right");
            s.Children.Add(a2);

            DoubleAnimation b2 = new DoubleAnimation(500, d);
            Storyboard.SetTargetProperty(b2, new PropertyPath(Canvas.HeightProperty));
            Storyboard.SetTargetName(b2, "right");
            s.Children.Add(b2);

            this.BeginStoryboard(s);
        }
    }
}

WPF雑感

ここしばらく WPF をいじってるんだけど、情報が少なくてさっぱり進まない。GUI フレームワークが大幅に変更されたから、当然といえば当然なんだけど、より一層理解を妨げてくれるのが依存プロパティ。こいつのおかげでインスタンスメンバとクラスメンバに情報が分散しているため、思ったようにコーディングできない。
まあ依存プロパティとかは、動的言語の影響なんだろうけど。確かに必要ないプロパティを定義せずに済むのはメリットと言える。でも C# では無理やり感が漂っていて、あまり好きになれそうにない。DLR とかで少しはましになるのかな?