[C#/WPF] Storyboardの動かし方(Startのさせ方) あれこれ

# StoryBoard(style)関連記事

https://tera1707.com/entry/2022/02/06/144447#StyleTemplate

# やりたいこと


WPFアプリで簡単なアニメーションを実装したいときに`<Storyboard>`をよく使うが、その書き方、特に動かし方(アニメーションのStartのさせ方)にいろいろあり、覚えられないので一旦まとめておきたい。
(Storyboardの**「Startのさせ方」**のみまとめる。Storyboard自体の使い方やできることは今回はやらない)

# ■ 書き方別/目的別 Storyboardの書き方

どういう書き方をしたいか、どういうときにStoryboardでアニメ―ションしたいかで、サンプルとなるコードをまとめた。

### ★Storyboardを書きたい場所

- **対象コントロールの中に直接書く**
    - [①対象コントロールの中に直接書く](https://qiita.com/tera1707/items/437c3a6747802a196f55#%E5%AF%BE%E8%B1%A1%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%AB%E3%81%AE%E4%B8%AD%E3%81%AB%E7%9B%B4%E6%8E%A5%E6%9B%B8%E3%81%8F)
- **`Resources`の中にまとめて書く**
    - [②Resourcesの中にまとめて書く](https://qiita.com/tera1707/items/437c3a6747802a196f55#resources%E3%81%AE%E4%B8%AD%E3%81%AB%E3%81%BE%E3%81%A8%E3%82%81%E3%81%A6%E6%9B%B8%E3%81%8F)
    - [③styleの中に書く／コントロールのプロパティ(IsMouseOver等)の変化でStart](https://qiita.com/tera1707/items/437c3a6747802a196f55#style%E3%81%AE%E4%B8%AD%E3%81%AB%E6%9B%B8%E3%81%8F%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%AB%E3%81%AE%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3ismouseover%E7%AD%89%E3%81%AE%E5%A4%89%E5%8C%96%E3%81%A7start)
    - [④ViewModel等のプロパティの変化でStart](https://qiita.com/tera1707/items/437c3a6747802a196f55#viewmodel%E7%AD%89%E3%81%AE%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%AE%E5%A4%89%E5%8C%96%E3%81%A7start)
    - [⑤ViewModel等のプロパティの変化でStart（別解）](https://qiita.com/tera1707/items/437c3a6747802a196f55#viewmodel%E7%AD%89%E3%81%AE%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%AE%E5%A4%89%E5%8C%96%E3%81%A7start%E5%88%A5%E8%A7%A3)
    - [⑥c#コードの中からStart](https://qiita.com/tera1707/items/437c3a6747802a196f55#c%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E4%B8%AD%E3%81%8B%E3%82%89start)

### ★目的別
- **単純な使い方(アプリ起動したらずっと動かしておきたい等)**
    - [①対象コントロールの中に直接書く](https://qiita.com/tera1707/items/437c3a6747802a196f55#%E5%AF%BE%E8%B1%A1%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%AB%E3%81%AE%E4%B8%AD%E3%81%AB%E7%9B%B4%E6%8E%A5%E6%9B%B8%E3%81%8F)
    - [②Resourcesの中にまとめて書く](https://qiita.com/tera1707/items/437c3a6747802a196f55#resources%E3%81%AE%E4%B8%AD%E3%81%AB%E3%81%BE%E3%81%A8%E3%82%81%E3%81%A6%E6%9B%B8%E3%81%8F)
- **対象Controlのイベント(MouseDown等)発生時に動かしたい(xamlのみ)**
    - [③styleの中に書く／コントロールのプロパティ(IsMouseOver等)の変化でStart](https://qiita.com/tera1707/items/437c3a6747802a196f55#style%E3%81%AE%E4%B8%AD%E3%81%AB%E6%9B%B8%E3%81%8F%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%AB%E3%81%AE%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3ismouseover%E7%AD%89%E3%81%AE%E5%A4%89%E5%8C%96%E3%81%A7start)
- **ViewModel等のプロパティの変化時に動かしたい(xamlのみ)**
    - [④ViewModel等のプロパティの変化でStart](https://qiita.com/tera1707/items/437c3a6747802a196f55#viewmodel%E7%AD%89%E3%81%AE%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%AE%E5%A4%89%E5%8C%96%E3%81%A7start)
    - [⑤ViewModel等のプロパティの変化でStart（別解）](https://qiita.com/tera1707/items/437c3a6747802a196f55#viewmodel%E7%AD%89%E3%81%AE%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%AE%E5%A4%89%E5%8C%96%E3%81%A7start%E5%88%A5%E8%A7%A3)
- **コードの中から動かしたい(xaml+cs)**
    - [⑥c#コードの中からStart](https://qiita.com/tera1707/items/437c3a6747802a196f55#c%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E4%B8%AD%E3%81%8B%E3%82%89start)


# ■①対象コントロールの中に直接書く

``` MainWindow.xaml
<Window x:Class="AnimationTest.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:AnimationTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="300">
    <Rectangle x:Name="MyRect" Stroke="Red" StrokeThickness="5" Width="100" Height="100" RenderTransformOrigin="0.5 0.5">
        <Rectangle.RenderTransform>
            <TransformGroup>
                <RotateTransform Angle="0"/>
            </TransformGroup>
        </Rectangle.RenderTransform>

        <Rectangle.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard RepeatBehavior="Forever" Timeline.DesiredFrameRate="30">
                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="MyRect" 
                                           Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)">
                            <LinearDoubleKeyFrame KeyTime="00:00:01.5" Value="360"/>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Rectangle.Triggers>
    </Rectangle>
</Window>

```

# ■②Resourcesの中にまとめて書く


``` MainWindow.xaml
<Window x:Class="AnimationTest.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:AnimationTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="300">
    <Window.Resources>
        <Storyboard x:Key="MyRoundingAnimation" RepeatBehavior="Forever">
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
                                           Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)">
                <LinearDoubleKeyFrame KeyTime="00:00:00.5" Value="360"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </Window.Resources>

    <Rectangle x:Name="MyRect" Stroke="Red" StrokeThickness="5" Width="100" Height="100" RenderTransformOrigin="0.5 0.5">
        <Rectangle.RenderTransform>
            <TransformGroup>
                <RotateTransform Angle="0"/>
            </TransformGroup>
        </Rectangle.RenderTransform>

        <Rectangle.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard Storyboard="{StaticResource MyRoundingAnimation}" />
            </EventTrigger>
        </Rectangle.Triggers>
    </Rectangle>
</Window>

```

# ■③styleの中に書く／コントロールのプロパティ(IsMouseOver等)の変化でStart


``` MainWindow.xaml
<Window x:Class="AnimationTest.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:AnimationTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="300"
        Name="root">
    <Window.Resources>
        <Storyboard x:Key="MyRoundingAnimation" RepeatBehavior="1">
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
                                           Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)">
                <LinearDoubleKeyFrame KeyTime="00:00:00.5" Value="360"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </Window.Resources>

    <Grid>
        <Rectangle Stroke="Red" StrokeThickness="3" RenderTransformOrigin="0.5 0.5" Fill="Aqua" Margin="50">
            <Rectangle.RenderTransform>
                <TransformGroup>
                    <RotateTransform Angle="0" />
                </TransformGroup>
            </Rectangle.RenderTransform>

            <Rectangle.Style>
                <Style TargetType="Rectangle">
                    <Style.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard Storyboard="{StaticResource MyRoundingAnimation}" Name="bsb"/>
                            </Trigger.EnterActions>
                            <Trigger.ExitActions>
                                <StopStoryboard BeginStoryboardName="bsb"/>
                            </Trigger.ExitActions>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </Rectangle.Style>
        </Rectangle>
    </Grid>
</Window>

```

※
`<Style.Triggers>`の中で、`<Trigger Property="IsMouseOver" Value="True">`を書くことはできたが、
`<RectAngle.Triggers>`の中に`<Trigger Property="IsMouseOver" Value="True">`を書くことはできなかった。
(Triggersの中はEventTriggerでないといけない、と怒られる)

# ■④ViewModel等のプロパティの変化でStart


``` MainWindow.xaml
<Window x:Class="AnimationTest.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:AnimationTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="300"
        Name="root">
    <Window.Resources>
        <Storyboard x:Key="MyRoundingAnimation" RepeatBehavior="1">
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
                                           Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)">
                <LinearDoubleKeyFrame KeyTime="00:00:00.5" Value="360"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </Window.Resources>

    <Grid>
        <!-- このチェックボックスがプロパティをONOFFする -->
        <CheckBox IsChecked="{Binding Chk, ElementName=root}"/>
        <Rectangle Stroke="Red" StrokeThickness="3" RenderTransformOrigin="0.5 0.5" Margin="50" >
            <Rectangle.RenderTransform>
                <TransformGroup>
                    <RotateTransform Angle="0" />
                </TransformGroup>
            </Rectangle.RenderTransform>

            <Rectangle.Style>
                <Style TargetType="Rectangle">
                    <Style.Triggers>
                        <!-- プロパティのONOFFでStoryBoardを動かしたり止めたりする -->
                        <DataTrigger Binding="{Binding Chk, ElementName=root}" Value="True">
                            <DataTrigger.EnterActions>
                                <BeginStoryboard Storyboard="{StaticResource MyRoundingAnimation}" Name="bsb"/>
                            </DataTrigger.EnterActions>
                            <DataTrigger.ExitActions>
                                <StopStoryboard BeginStoryboardName="bsb"/>
                            </DataTrigger.ExitActions>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Rectangle.Style>
        </Rectangle>
    </Grid>
</Window>

```

# ■⑤ViewModel等のプロパティの変化でStart（別解）

Blend SDK for WPFに同梱されているBehaviorというのを使う。
参考：https://blog.okazuki.jp/category/XAML?page=1419259848

下記で行った準備が必要。(参照の追加)
https://qiita.com/tera1707/items/7ecde6e97a19437cbf72#eventtrigger%E3%82%92%E4%BD%BF%E3%81%86

``` MainWindow.xaml
<Window x:Class="AnimationTest.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:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:local="clr-namespace:AnimationTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="300"
        Name="root">
    <Window.Resources>
        <Storyboard x:Key="MyRoundingAnimation" RepeatBehavior="Forever">
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="MyRect"
                                           Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)">
                <LinearDoubleKeyFrame KeyTime="00:00:00.5" Value="360"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </Window.Resources>

    <!-- プロパティのONOFFでStoryboardを制御 -->
    <i:Interaction.Triggers>
        <ei:DataTrigger Binding="{Binding Chk, ElementName=root}" Value="true">
            <ei:DataTrigger.Actions>
                <ei:ControlStoryboardAction ControlStoryboardOption="Play" Storyboard="{StaticResource MyRoundingAnimation}" />
            </ei:DataTrigger.Actions>
        </ei:DataTrigger>
        <ei:DataTrigger Binding="{Binding Chk, ElementName=root}" Value="false">
            <ei:DataTrigger.Actions>
                <ei:ControlStoryboardAction ControlStoryboardOption="Stop" Storyboard="{StaticResource MyRoundingAnimation}" />
            </ei:DataTrigger.Actions>
        </ei:DataTrigger>
    </i:Interaction.Triggers>

    <Grid>
        <!-- このチェックボックスがプロパティをONOFFする -->
        <CheckBox IsChecked="{Binding Chk, ElementName=root}"/>

        <Rectangle x:Name="MyRect" Stroke="Red" StrokeThickness="3" RenderTransformOrigin="0.5 0.5" Margin="50" >
            <Rectangle.RenderTransform>
                <TransformGroup>
                    <RotateTransform Angle="0" />
                </TransformGroup>
            </Rectangle.RenderTransform>
        </Rectangle>
    </Grid>
</Window>

```



# ■⑥c#コードの中からStart


``` MainWindow.xaml
<Window x:Class="AnimationTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="300"
        Name="root">
    <Window.Resources>
        <Storyboard x:Key="MyRoundingAnimation" RepeatBehavior="Forever">
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="MyRect"
                                           Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)">
                <LinearDoubleKeyFrame KeyTime="00:00:00.5" Value="360"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </Window.Resources>
    
    <Grid>
        <ToggleButton Content="ボタン" HorizontalAlignment="Left" VerticalAlignment="Top" Click="ToggleButton_Click"/>
        <Rectangle x:Name="MyRect" Stroke="Red" StrokeThickness="3" RenderTransformOrigin="0.5 0.5" Margin="50" >
            <Rectangle.RenderTransform>
                <TransformGroup>
                    <RotateTransform Angle="0" />
                </TransformGroup>
            </Rectangle.RenderTransform>
        </Rectangle>
    </Grid>
</Window>
```

``` MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Media.Animation;

namespace AnimationTest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private void ToggleButton_Click(object sender, RoutedEventArgs e)
        {
            // StoryBoardを検索
            var sb = FindResource("MyRoundingAnimation") as Storyboard;

            // トグルボタンを押すたびにstart/stopする
            if (sender is ToggleButton tb)
            {
                if (tb.IsChecked.Value) sb.Begin();
                else                    sb.Stop();
            }
        }
    }
}
```