Storyboardのアニメーションの対象コントローラーを動的に変更する

こんにちは

WPFのStoryboardを使い始めてすぐの頃は、このやり方がわからず
無駄なことをしていたので、メモ的に残しておこうと思います。
(こんなの当たり前やんって事かもしれませんが)

細かい説明は後にして、↓こんな動きをさせたいときの話です。
(gifにしたらちょっと画面がちらついてしまいました・・・)

![にゃんこ動画](http://lab.aquaring.co.jp/contents/wpf_anime_nyanko.gif)

・にゃんこ達の画像はそれぞれ別のImageに張り付けてある
・アニメーションの内容は皆同じ
・アニメーションとしては
 　左右にプルプル小さく位置を移動させ、上に上がってから小さく左右に傾いてから戻るというもの
・分かりにくいですがボタンを押したときにそれぞれのにゃんこをアニメーションさせてます

このようなことをやるときに、私は初め書き方が分からなくて、無駄な書き方をしていたのです
というお話です。

今回のソース
[github aquaring/WpfTestCode](https://github.com/aquaring/WpfTestCode)

#私はWPFでアニメーションをよく使ってます
世の中でどれぐらい使われているのかわからないのですが
私はリアルイベントなどで使うアプリを作るのにWPFをよく使っています。

　どんなものをWPFで作るかというと
　　KinectとかWebカメラとかを配置しておいて人がそこに来て、そのイベントに合った
　　画像と合成して写真を撮るとかそんなリアルイベントで使うアプリとかです。
　　その場合に、イベントのモチーフになるキャラクターとか画像とかを画面上でアニメーション
　　させたりという部分でWPFでを使ったりしてます。WPFを使うのは画面周りを作るのが圧倒的に早いからです。
　　OpenGLとかDirectXとか使ってぐりぐり描画するようなものでないようなときに使います。
　　(そういう時はC++でがっつり書くかofを使っちゃいます)

WPFで2D的なアニメーションを使おうとした場合には選択肢は2つありますよね。
・タイマーとかで毎フレーム描画していく(ちまちま書く)
・Storyboardを使う

複雑なアニメーションにする場合はStoryboard一択と思うのですが
XAMLを手書きするのは無理なので、「Blend」を使ってタイムラインでアニメーションを作成しますよね。

#前はやり方が分からなくて困った
BlendでStoryboardを作ってXAMLにするのですが例えば対象のImageが複数あって表示しているものは
違うのですが、アニメーションの動きは全く同じというときにStoryboardの対象のコントローラーを切り替えたかったのですが(初期開始位置はそれぞれ異なる等も含め)
そのやり方がわからず
　・XAML化されたStoryboardを参考にC#で動的にStoryboardを作成して対象のコントローラーを割り当てたり
　・動きは同じなのに、それぞれのコントローラー毎に複数のStoryboardをXAMLで複製したり
　・タイマーを使って自前で位置移動・大きさ変更・回転などをしてアニメーションさせたり
などをしていました・・・



#このようにすれば良い
* Blendで一つのImageを対象にStoryboardを作る→XAMLに反映される
* `<Window.Triggers>`に起動時にStoryboardをBeginさせるのが自動的に入るので削除
* Blendで作ったStoryboardの対象になったImageコントローラーだけに以下のような記述が追加されるので、他のImageコントローラのXAMLにも同じように「`<Image.RenderTransform>`」と「RenderTransformOrigin」を追加

```csharp:MainWindows.xaml
<Image x:Name="BonchanImage" HorizontalAlignment="Left" Height="106" Margin="38,227,0,0" VerticalAlignment="Top" Width="93" Source="image/Bonchan.jpg" RenderTransformOrigin="0.5,0.5">
    <Image.RenderTransform>
        <TransformGroup>
            <ScaleTransform/>
            <SkewTransform/>
            <RotateTransform/>
            <TranslateTransform/>
        </TransformGroup>
    </Image.RenderTransform>
</Image>
```
これは、アニメーションで位置変更・回転のTransformを行っているのでImage生成時にRenderTransformを作る必要があるからです。

* C#でボタンを押した時の処理を書く

```csharp:MainWindows.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    // Storyboardでアニメーションさせるイメージコントローラーを格納する変数
    Image targetImage = BonchanImage;

    // 押されたボタンの名前を取得
    String buttonName = ((Button)sender).Name;

    // 押されたボタンによって、対象のイメージコントローラーを切り替える
    if      (buttonName == "BonButton")     targetImage = BonchanImage;
    else if (buttonName == "NoruchiButton") targetImage = NorucchiImage;
    else if (buttonName == "RossyButton")   targetImage = RossyImage;
    else if (buttonName == "ShiroButton")   targetImage = ShirochanImage;
    else if (buttonName == "BamiButton")    targetImage = BamichanImage;
    else if (buttonName == "ChakoButton")   targetImage = ChakochanImage;
    else if (buttonName == "FukuButton")    targetImage = FukuchanImage;
    else                                    targetImage = BonchanImage;

    // Storyboardを取得
    Storyboard puppetMoveStoryboard = (Storyboard)this.Resources["PuppetMoveStoryboard"];

    // Storyboardのアニメーション子要素であるDoubleAnimationの対象コントローラー(Storyboard.TargetName)を
    // 上で決めたImageコントローラーに変更する
    foreach (var child in puppetMoveStoryboard.Children)
    {
        Storyboard.SetTarget(child, targetImage);
    }

    // アニメーションの開始
    puppetMoveStoryboard.Begin();
}
```
このStoryboradのアニメーションは「DoubleAnimationUsingKeyFrames」というものを3つ(左右にプルプルの位置移動、上への位置移動、小さく左右に傾く)使っていてそれぞれにターゲットとなるコントローラーが設定されているので、それをすべてforearchで回してSetTargetで対象を変更しています。

#そして
これでアニメーションのパターンだけBlendでStoryboardを作ってXAMLにして
C#の方で対象となるコントローラーを指定してStoryboardを実行することができるようになりました。

初期位置、移動量・回転量などの変化量もC#から設定できるのでベースだけXAMLに作っておいて
動的にプログラムでアレンジするということも可能になります。