2分でできるC#コードからWPFのアニメーションを操る方法
WPFでアニメーションといえば、XAMLで書くのが一般的ですが、たまにはコードからやりましょう。
今回はウィンドウにマウスをのせたら、Rectangleが左右にウリウリ動く感じにしてみます。
目次
とりあえずコピペで作りましょう。
とりあえず「2分で」というタイトルなので、動くものを作ってしまいましょう。
Visual Studioを起動して新しいプロジェクトを作ります。ここでは「WpfTest」と名づけましたがなんでもかまいません。
右ペインのソリューションエクスプローラーからMainWindow.xamlをダブルクリックしてデザイン画面を開きます。
今回はXAMLコードを直接貼り付けるので、エディタの下のほうにあるXAMLタブをクリックしてコード編集画面にしてください。
すべて選択して、▼のコードを貼り付けます。一行目の「WpfTest」は作成したプロジェクト名に変更してください。
1 2 3 4 5 6 7 8 |
<Window x:Class="WpfTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="100" Width="400" Name="window"> <Canvas Height="100" Width="400"> <Rectangle Name="rect" Width="100" Height="100" Fill="DodgerBlue" Canvas.Left="0" Canvas.Top="0"/> </Canvas> </Window> |
GUIデザインは以上です。次にコードビハインド(C#のコード)を書いていきます。
[F7]キーを押して、C#コードエディタを表示させてください。
1 2 3 |
public partial class MainWindow : Window { } |
▲の部分を削除し、そこに▼のコードを貼り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
public partial class MainWindow : Window { Storyboard storyboard = new Storyboard { RepeatBehavior = RepeatBehavior.Forever }; DoubleAnimationUsingKeyFrames animation = new DoubleAnimationUsingKeyFrames(); public MainWindow() { InitializeComponent(); storyboard.Children.Add(animation); for (int i = 0; i < 10; i++) { var frame = new DiscreteDoubleKeyFrame((i + 1) * 30, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(i * 100))); animation.KeyFrames.Add(frame); } var frame2 = new LinearDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(2000))); animation.KeyFrames.Add(frame2); Storyboard.SetTargetName(animation, "rect"); Storyboard.SetTargetProperty(animation, new PropertyPath("(Canvas.Left)")); var trigger1 = new EventTrigger { SourceName = "window", RoutedEvent = MouseEnterEvent, }; var beginsb = new BeginStoryboard { Storyboard = storyboard, Name = "hogehoge" }; RegisterName("hogehoge", beginsb); // WPF 名前スコープに登録 trigger1.Actions.Add(beginsb); Triggers.Add(trigger1); var trigger2 = new EventTrigger { SourceName = "window", RoutedEvent = MouseLeaveEvent, }; trigger2.Actions.Add(new StopStoryboard { BeginStoryboardName = "hogehoge" }); Triggers.Add(trigger2); } } |
ここまでできたら[F5]を押してデバッグ実行してみてください。エラーがでる場合はもう一度コードを確認して、やり直してみてください。
うまくいけば▼のようなこじんまりとしたウィンドウが表示されるはずです。
マウスをウィンドウの上にかざしてみてください。青い四角形が動き出すはずです。マウスを外すと元に戻ります。
この四角形は右側に移動するときは、10ステップでカクカク動き、左に戻るときは2秒かけてなめらか~に戻るようになっています。
最後までお読みいただきありがとうございました m(_ _)m
解説
さて、中身を詳しく知りたい方は以下をご覧ください。
XAMLコードのほうは実にシンプルです。一つ作りたてのウィンドウと違うのはLayoutRoot(Window直下のパネルコントロール)がGridでなく、Canvasになっているところです。今回は四角形の位置をLeft, Topで指定したかったのでCanvasを選びました。
1 2 3 4 5 6 7 8 |
<Window x:Class="WpfTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="100" Width="400" Name="window"> <Canvas Height="100" Width="400"> <Rectangle Name="rect" Width="100" Height="100" Fill="DodgerBlue" Canvas.Left="0" Canvas.Top="0"/> </Canvas> </Window> |
ストーリーボードの作成
StoryBoardクラスをインスタンス化します。
1 2 |
Storyboard storyboard = new Storyboard { RepeatBehavior = RepeatBehavior.Forever, AutoReverse = false }; |
AutoReverseは逆方向のアニメーションをするかどうかの設定です。通常はfalseでいいでしょう。
RepeatBehaviorは繰り返しの指定です。RepeatBehavior.Foreverを設定すると永久に繰り返します。回数を指定する場合はRepeatBehaviorのインスタンスを渡します。
1 2 |
Storyboard storyboard = new Storyboard { RepeatBehavior = new RepeatBehavior(10), AutoReverse = false }; |
アニメーションの作成
ストーリーボードに追加するアニメーションを作成します。今回は1つだけ作ります。また、今回はKeyFrameを自分で指定しますので、DoubleAnimationUsingKeyFramesクラスを使います。
なぜDouble~かというと、アニメーションさせるプロパティ(Canvas.Left)の型がdouble型だからです。ということで、DoubleAnimationUsingKeyFramesのインスタンスを作っておきます。
1 |
DoubleAnimationUsingKeyFrames animation = new DoubleAnimationUsingKeyFrames(); |
なお、このあたりのクラスの選び方は@ITの記事が参考になります。
StoryBoardへのAnimationの登録
StoryBoardは複数のAnimationを管理するものです。今回はAnimationは1つなので本当は必要ありませんが、増えることも考えてStoryBoardを使っています。
▼のようにAnimationをStoryBoardへ登録してあげる必要があります。今回、これ以降のコードはコンストラクタ内に書きましょう。
1 |
storyboard.Children.Add(animation); |
KeyFrameの作成とAnimationへの登録
Animationに登録するKeyFrameを作っていきます。KeyFrameとは「どの時刻にどんな値にするか」を表したものです。
ざっくり言うとKeyFrameの集まりがAnimationで、Animationの集まりがStoryBoardです。
KeyFrameには4種類あります。▼はdouble型のプロパティを操作するためのものなので、Doubleという名前になっていますが、int版やColor版なども用意されています。
- EasingDoubleKeyFrame
- DiscreteDoubleKeyFrame
- LinearDoubleKeyFrame
- SplineDoubleKeyFrame
これらの違いについては@ITの記事を参照していただくとして、今回は途中の補間は行わないDiscreteDoubleKeyFrameと補間を適当にやってくれるEasingDoubleKeyFrameを使います。EasingDoubleKeyFrameはイージング関数(補間関数)を設定しなければLinearDoubleKeyFrame(線形補間)と同じっぽいです。
キーフレームを作るには▼のようにします。
1 |
var frame = new DiscreteDoubleKeyFrame(値, 時刻); |
値には”Double”KeyFrameなのでdouble型の値を指定します。今回は四角形のX方向の位置(Canvas.Left)をアニメーションさせるので、ウィンドウ上のX方向の座標となるように値を設定します。
時刻にはアニメーションがはじまったときをゼロとして、この値に変化するときの時刻をKeyTime型で指定します。KeyTime型の値はFromTimeSpanというメソッドでTimeSpanから変換することができますので、通常はこの方法を使います。
▼はいくつかのタイミングを作る例です。固定時間で設定するにはTimeSpan.Parseが便利です。
1 2 3 |
KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(200)) // 200ミリ秒 KeyTime.FromTimeSpan(TimeSpan.FromSeconds(2)) // 2秒 KeyTime.FromTimeSpan(TimeSpan.Parse("0:1:2.345")) // 1分2秒345ミリ秒 |
作成したKeyFrameはアニメーションに登録します。Animation.KeyFrames.Add(keyFrame)みたいな感じで登録できます。KeyFrameを追加している部分の抜粋です。
1 2 3 4 5 6 7 |
for (int i = 0; i < 10; i++) { var frame = new DiscreteDoubleKeyFrame((i + 1) * 30, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(i * 100))); animation.KeyFrames.Add(frame); } var frame2 = new LinearDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(2000))); animation.KeyFrames.Add(frame2); |
というわけで今回の▲のコードは「右に30pxずつ100ミリ秒ごと、とびとびに移動したあと」、「2秒で左端(0)までスムースに戻る」という動きになっています。
アニメーションする対象の要素とプロパティを設定する
次にアニメーション対象となる要素(ここではrectという名前のRectangle)とアニメーションさせるプロパティ(ここではCanvas.Left)を設定します。
StoryBoardの静的メソッドを使って下記のようにします。
1 2 |
Storyboard.SetTargetName(animation, "rect"); Storyboard.SetTargetProperty(animation, new PropertyPath("(Canvas.Left)")); |
ここで注意するのは2行目のPropertyPathです。アニメーション対象のパスを記述するわけですが、これが結構厄介です。Blendを使っていると勝手に設定してくれますが、手動の場合必死に考えるしかありません。
問題となるのは、Leftプロパティが本来Rectangleには存在しないプロパティであるということです。これはCanvasに配置されているからこそ設定できる添付プロパティです。
このため、Canvasの添付プロパティであることを示すためにカッコで括って(Canvas.Left)と記述します。このカッコがミソです。カッコがない場合、「要素のCanvasプロパティのLeftプロパティ」と解釈されてしまうようです。
対象のプロパティが要素の直接のプロパティ、たとえばWidthプロパティの場合はnew PropertyPath(“Width”)でOKです。
イベントトリガーの設定
あともう少しです。
最後にマウスがウィンドウにのったときと離したときのイベントに、StoryBoardが連動するようにします。
まず、EventTriggerのインスタンスを作成します。
1 2 3 4 5 |
var trigger1 = new EventTrigger { SourceName = "window", RoutedEvent = MouseEnterEvent, }; |
SourceNameにイベント発生元の要素の名前、RoutedEventにトリガーさせるイベントを設定します。
次にストーリーボードを起動するアクションBeginStoryboardのインスタンスを作成します。
1 |
var beginsb = new BeginStoryboard { Storyboard = storyboard, Name = "hogehoge" }; |
Storyboardに起動するストーリーボード、NameにこのBeginStoryboardの名前を適当につけます。
次にこの名前でWPF名前スコープにアクションを登録します。これをしておかないと後で設定するStopStoryboardからストーリーボードを止めることができません。
名前スコープの詳細については「WPF名前スコープに手動で名前を登録する」を参照してください。
1 |
RegisterName("hogehoge", beginsb); |
これでOKです。あとはBeginStoryboardをEventTriggerのActionsに登録し、EventTriggerをWindowのTriggersに登録します。
1 2 |
trigger1.Actions.Add(beginsb); Triggers.Add(trigger1); |
アクションとトリガーはこんな感じの階層構造になっています。
-Window.Triggers -> EventTrigger.Actions -> BeginStoryboard
同様にして、ストーリーボードを止めるためのStopStoryboardをMouseLeaveEventに対して登録します。
1 2 3 4 5 6 7 |
var trigger2 = new EventTrigger { SourceName = "window", RoutedEvent = MouseLeaveEvent, }; trigger2.Actions.Add(new StopStoryboard { BeginStoryboardName = "hogehoge" }); Triggers.Add(trigger2); |
BeginStoryboardNameに先ほど登録したBeginStoryboardの名前を書きます。これでマウスがウィンドウの外にでたとき(MouseLeave)にStopStoryboardが実行され、ストーリーボードが停止します。
なお、StopStoryboardの場合、ストーリーボードが完全に停止して、アニメーション前の状態に戻ってしまいますが、PauseStoryboard(一時停止)を使うと、途中で停止させることもできます。この場合、再開するときはResumeStoryboardを使います。