[WPF/xaml]Storyboardでアニメーションをつくる

**もくじ**
→https://qiita.com/tera1707/items/4fda73d86eded283ec4f

**Storyboard関連**
- [[WPF/xaml] xaml＋C#で当番決めのためのルーレットを作る](https://qiita.com/tera1707/items/2a94c262c93b93854688#_reference-8730f3e1be562848669f)
- [[WPF/xaml]Storyboardでアニメーションをつくる](https://qiita.com/tera1707/items/a7fcdd95fc3120ae3c8b)
- [[WPF/xaml]Storyboardでアニメーションをつくる2(TargetPropertyの階層的な指定)](https://qiita.com/tera1707/items/ee6d72bb68a8e2131082)


# やりたいこと
こんな感じのスイッチを作って、押したら中の四角の部分が「ぐいーん」と左右に動いて、スイッチの入り切りを表現するようなものを作りたい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/970f2dda-1563-ad58-0391-59f21c3fff5b.png)　⇔　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/8a090c83-5c97-728c-20d8-eec2c57c130d.png)



# サンプルコード
スイッチは`CheckBox`を使い、そのStyleをうまいことやってやることで実現する。

```MainWindow.xaml
<Window x:Class="WpfApp17.MainWindow"
        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"
        xmlns:local="clr-namespace:WpfApp17"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" Background="Gray">
    <Grid>
        <!-- チェックボックス本体 -->
        <CheckBox>
            <CheckBox.Style>
                <Style TargetType="CheckBox">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type CheckBox}">

                                <!-- 見た目の設定 -->
                                <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                                    <!-- 外側の枠 -->
                                    <Border Name="BackgroundBorder" CornerRadius="2" Height="28" Width="60" BorderBrush="White" BorderThickness="2"/>
                                    <!-- 中のスイッチ部分 -->
                                    <Rectangle x:Name="slider" Stroke="#FFFF0000" HorizontalAlignment="Left" Width="12" Height="18" StrokeThickness="0" RenderTransformOrigin="0.5,0.5" Margin="0" Fill="White">
                                        <Rectangle.RenderTransform>
                                            <TransformGroup>
                                                <TranslateTransform X="5" Y="0" />
                                            </TransformGroup>
                                        </Rectangle.RenderTransform>
                                    </Rectangle>
                                </Grid>

                                <!-- ストーリーボードの設定 -->
                                <ControlTemplate.Resources>
                                    <Storyboard x:Key="OnChecking">
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"  Storyboard.TargetName="slider" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)">
                                            <SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="43" />
                                        </DoubleAnimationUsingKeyFrames>
                                    </Storyboard>
                                    <Storyboard x:Key="OnUmChecking">
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"  Storyboard.TargetName="slider" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)">
                                            <SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="5" />
                                        </DoubleAnimationUsingKeyFrames>
                                    </Storyboard>
                                </ControlTemplate.Resources>

                                <!-- トリガーの設定 -->
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsChecked" Value="True">
                                        <!-- IsCheckedがTrueになるときのStoryboard開始指示 -->
                                        <Trigger.EnterActions>
                                            <BeginStoryboard Storyboard="{StaticResource OnChecking}" x:Name="OnChecking_BeginStoryboard" />
                                        </Trigger.EnterActions>
                                        <!-- IsCheckedがTrueから抜けるときのStoryboard開始指示 -->
                                        <Trigger.ExitActions>
                                            <BeginStoryboard Storyboard="{StaticResource OnUmChecking}" x:Name="OnUnchecking_BeginStoryboard" />
                                        </Trigger.ExitActions>
                                        
                                        <!-- その他プロパティの変化定義(チェック時) -->
                                        <Setter TargetName="slider" Property="Fill" Value="White" />
                                        <Setter TargetName="BackgroundBorder" Property="Background" Value="Orange" />
                                    </Trigger>
                                    
                                    <Trigger Property="IsChecked" Value="False">
                                        <!-- その他プロパティの変化定義(非チェック時) -->
                                        <Setter TargetName="slider" Property="Fill" Value="White" />
                                        <Setter TargetName="BackgroundBorder" Property="Background" Value="Gray" />
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </CheckBox.Style>
        </CheckBox>
    </Grid>
</Window>
```

# 説明
大きく分けて、4つの部分がある。

|部分|概要|
|:--|:--|
|★チェックボックス本体|ベースになる`CheckBox`を設置する|
|★見た目の設定|スイッチの見た目を、BorderやRectangleで作る|
|★ストーリーボードの設定|Storyboardを使って、どのコントロールのどのプロパティをどのように変化させる、ということを書く|
|★トリガーの設定|何をトリガーにして、ストーリーボードを動作させるかを書く|

# チェックボックス本体
`ControlTemplate`を作って、それをチェックボックスの`Style`の中の`Template`にセットする形でチェックボックスをカスタムする。

``` a.xaml
<CheckBox>
    <CheckBox.Style>
        <Style TargetType="CheckBox">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type CheckBox}">
                           ・
                           ・
```

# 見た目の設定
スイッチの見た目を、BorderやRectangleで作る。

```a.xaml
<!-- ★見た目の設定 -->
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
    <!-- 外側の枠 -->
    <Border Name="BackgroundBorder" CornerRadius="2" Height="28" Width="60" BorderBrush="White" BorderThickness="2"/>
    <!-- 中のスイッチ部分 -->
    <Rectangle x:Name="slider" Stroke="#FFFF0000" HorizontalAlignment="Left" Width="12" Height="18" StrokeThickness="0" Fill="White">
        <Rectangle.RenderTransform>
            <TransformGroup>
                <TranslateTransform X="5" Y="0" />
            </TransformGroup>
        </Rectangle.RenderTransform>
    </Rectangle>
</Grid>
```

ここで設置しているコントロールのプロパティを動かすことで、スイッチが左右に動くことを表現したいので、動かすものに名前を付けておく。(ここでは「BackgroundBorder」「slider」の2つの名前を付けている)

それぞれのコントロールのWidthやHeight(幅/高さ)、あとTranslateTransformのXやY(スイッチの上下の位置)は、適当な値(デザイナーを見ながら、ちょうどよさそうな値を手探りで見つけた感じ。)。

StoryBoardの方で、この`slider`のTranslateTransformのXを変化させるということをする。

# ストーリーボードの設定
`Storyboard`の中に、Animationをセットして、どういう動きをさせるか定義する。
**(19/06/06追記)下に、Storyboardについて追記しました。**

```a.xaml
<!-- ★ストーリーボードの設定 -->
<ControlTemplate.Resources>
    <Storyboard x:Key="OnChecking">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"  Storyboard.TargetName="slider" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="43" />
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
    <Storyboard x:Key="OnUmChecking">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"  Storyboard.TargetName="slider" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="5" />
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</ControlTemplate.Resources>
```

Storyboardには名前を付けておく。後でTriggerのところで、この名前を使って呼び出す。

### DoubleAnimationUsingKeyFramesについて

|DoubleAnimationUsingKeyFramesのプロパティ|概要|
|:--|:--|
|BeginTime|アニメーションが開始するタイミング。これを00:00:05とすると、Checkboxを押してから5秒後にアニメ開始する。|
|Storyboard.TargetName|アニメーションさせたいコントロールの名前。|
|Storyboard.TargetProperty|アニメーションさせたいコントロールの、何のプロパティを変化させるか。|

### SplineDoubleKeyFrameについて

|SplineDoubleKeyFrameのプロパティ|概要|
|:--|:--|
|Value|変化させたプロパティの値。|
|KeyTime|何秒間で、プロパティの値をValueで設定した値にもっていくか。00:00:00.2とすると、0.2秒でプロパティがValueの値まで変化する。|

# トリガーの設定
何をトリガーにして、ストーリーボードを動作させるかを書く。

```a.xaml
<ControlTemplate.Triggers>
    <Trigger Property="IsChecked" Value="True">
        <!-- IsCheckedがTrueになるときのStoryboard開始指示 -->
        <Trigger.EnterActions>
            <BeginStoryboard Storyboard="{StaticResource OnChecking}" x:Name="OnChecking_BeginStoryboard" />
        </Trigger.EnterActions>
        <!-- IsCheckedがTrueから抜けるときのStoryboard開始指示 -->
        <Trigger.ExitActions>
            <BeginStoryboard Storyboard="{StaticResource OnUmChecking}" x:Name="OnUnchecking_BeginStoryboard" />
        </Trigger.ExitActions>
        
        <!-- その他プロパティの変化定義(チェック時) -->
        <Setter TargetName="slider" Property="Fill" Value="White" />
        <Setter TargetName="BackgroundBorder" Property="Background" Value="Orange" />
    </Trigger>
                  ・
                  ・
</ControlTemplate.Triggers>
```

|Triggerにセットするもの|概要|
|:--|:--|
|Trigger.EnterActions|Triggerで監視しているプロパティがValueの値になるときに行うStoryboardを指定する|
|Trigger.ExitActions|Triggerで監視しているプロパティがValueの値から抜けるときに行うStoryboardを指定する|
|BeginStoryboard|`Storyboard`プロパティに、上で作ったStoryboardの名前を指定する|

# 備考
今回、`DoubleAnimationUsingKeyFrames`を使って、`TranslateTransform`の`X`というdoubleの値を変化させることでアニメーションするということをしたが、ほかにも色を変化させるとか、いろいろできるっぽい。要調査。

# 補足 Storyboardの使い方(19/06/06追記)

Storyboardの使い方の基本は、下記のような使い方をする。

```a.xaml
<!-- doubleの値を変化させるStoryboard -->
<Storyboard x:Key="OnChecking" RepeatBehavior="Forever">
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"  Storyboard.TargetName="slider" Storyboard.TargetProperty="RenderTransform.(TransformGroup.Children)[0].(TranslateTransform.X)">
        <DiscreteDoubleKeyFrame KeyTime="00:00:02" Value="43" /> <!-- 値の変化がリニアではなく、指定した時間に、急に指定した値になる -->
        <LinearDoubleKeyFrame KeyTime="00:00:04" Value="5" />    <!-- 値が現在値から目的値にリニアに変化していく -->
        <EasingDoubleKeyFrame KeyTime="00:00:06" Value="43" />   <!-- イージングができる(イージング関数を指定しなければLinearと同じ) -->
        <SplineDoubleKeyFrame KeyTime="00:00:08" Value="5" />    <!-- 変化の加速や減速ができる -->
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

<!-- 色を変化させるStoryboard -->
<Storyboard x:Key="OnCheckingForColor" RepeatBehavior="Forever">
    <ColorAnimationUsingKeyFrames  BeginTime="00:00:00"  Storyboard.TargetName="backColor" Storyboard.TargetProperty="Color">
        <DiscreteColorKeyFrame KeyTime="00:00:02" Value="Red"/>
        <LinearColorKeyFrame KeyTime="00:00:04" Value="Green"/>
        <EasingColorKeyFrame KeyTime="00:00:06" Value="Blue"/>
        <SplineColorKeyFrame KeyTime="00:00:08" Value="Yellow"/>
    </ColorAnimationUsingKeyFrames>
</Storyboard>
```

**■Storyboardクラス**
ここに、トリガーからの呼び出しのための名前や、繰り返しの設定などを書く。

**■xxxxxAnimationUsingKeyFramesクラス**
`AnimationTimeline`クラスを継承したものをここで使う。
`AnimationTimeline`を継承するクラスはたくさんあり、ここで**「どんなプロパティを変化させるか」**が決まる。
例えば`DoubleAnimationUsingKeyFrames`を使うなら、double型のプロパティを変化させる、となり、`ColorAnimationUsingKeyFrames`を使うなら色を変化させる、となる。

**■xxxxKeyFrameクラス**
プロパティの種類(型)と、そのプロパティをどのように変化させるか、でここに使うクラスが決まる。
例えば、double型のプロパティを、初期値から徐々に目的の値まで変化させたい、となると、`LinearDoubleKeyFrame`を使う。
Color型のプロパティを、指定した時間に、徐々にではなくいきなりその色に変えたい、となると、`DiscreteColorKeyFrame`を使う。

xxxxKeyFrameクラスは、プロパティの種類ごとに、4つずつ派生クラスがあるっぽい。
例えば、double型用のクラスであれば下記の4つ。

- DiscreteDoubleKeyFrame
- LinearDoubleKeyFrame
- EasingDoubleKeyFrame
- SplineDoubleKeyFrame

見た限り、Color用についても同じものがあった。おそらく他の種類についても同じだと思われる。


# 参考
**AnimationTimelineクラス**
「DoubleAnimationUsingKeyFrames」などの元になっているクラス。
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.media.animation.animationtimeline?view=netframework-4.8

Storyboardで変化させられるプロパティには、このページによるとこれだけある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/afb22566-03f6-d8c4-4478-5b8b6f008311.png)