trsing’s diary

勉強、読んだ本、仕事で調べたこととかのメモ。

Storyboardを切り替える(Code Behind)

目次

  1. WPFのCustomControlでアニメーション-概要
  2. LineにLinearGradientBrushを適用したものをCustomControlとして作る
  3. Storyboardを作成する(XAML)
  4. Storyboardを切り替える(XAML)
  5. Storyboardを切り替える(Code Behind)←ここ
  6. Storyboardを作成する(Code Behind)

目的

Code Behindでプロパティによって動作するStoryboardを切り替える

作成

.cs

  • 依存関係プロパティAnimationTypeが変更されたときに使用するStoryboardを切り替える。
        public bool AnimationType
        {
            get { return (bool)GetValue(AnimationTypeProperty); }
            set { SetValue(AnimationTypeProperty, value); }
        }

        public static readonly DependencyProperty AnimationTypeProperty =
            DependencyProperty.Register(nameof(AnimationType), typeof(bool), typeof(This), new PropertyMetadata(default(bool), AnimationTypeChanged));

        public static void AnimationTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((This)d).UpdateStoryboard();
        }
        private Storyboard storyBoard = default;
        private void UpdateStoryboard()
        {
            if (!(this.GetTemplateChild("gradientLine") is Line line)) return;
            storyBoard?.Remove(line);
            if (AnimationType) storyBoard = this.Template?.Resources["gradientAnimationTrue"] as Storyboard;
            else storyBoard = this.Template?.Resources["gradientAnimationFalse"] as Storyboard;
            storyBoard?.Begin(line, true);
        }
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            UpdateStoryboard();
        }

Generic.xaml

  • <ControleTemplate.Triggers>を削除
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:GradientLineTest.Controls"
    >

    <Style TargetType="{x:Type local:GradientLineControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:GradientLineControl}">
                    <ControlTemplate.Resources>
                        <Storyboard x:Key="gradientAnimationTrue" RepeatBehavior="Forever" AutoReverse="True">
                                <DoubleAnimation Storyboard.TargetName="gradientLine"
                                 Storyboard.TargetProperty="(Line.Stroke).(LinearGradientBrush.GradientStops)[1].(GradientStop.Offset)"
                                 From="0" To="1"/>
                        </Storyboard>
                        <Storyboard x:Key="gradientAnimationFalse" RepeatBehavior="Forever" AutoReverse="True">
                            <DoubleAnimation Storyboard.TargetName="gradientLine"
                                 Storyboard.TargetProperty="(Line.Stroke).(LinearGradientBrush.GradientStops)[0].(GradientStop.Offset)"
                                 From="0" To="1"/>
                            <DoubleAnimation Storyboard.TargetName="gradientLine"
                                 Storyboard.TargetProperty="(Line.Stroke).(LinearGradientBrush.GradientStops)[1].(GradientStop.Offset)"
                                 From="0" To="1"/>
                        </Storyboard>
                    </ControlTemplate.Resources>
                    <Line x:Name="gradientLine"
                          X1="{TemplateBinding X1}" Y1="{TemplateBinding Y1}"
                          X2="{TemplateBinding X2}" Y2="{TemplateBinding Y2}"
                          StrokeThickness="10"
                          >
                        <Line.Stroke>
                            <LinearGradientBrush StartPoint="0.0,0.5" EndPoint="1.0,0.5">
                                <GradientStop Color="{Binding LeftColor,RelativeSource={RelativeSource TemplatedParent}}" Offset="0.0"/>
                                <GradientStop Color="{Binding RightColor,RelativeSource={RelativeSource TemplatedParent}}" Offset="1"/>
                            </LinearGradientBrush>
                        </Line.Stroke>
                    </Line>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

注意点

namescopeについて

ControlTemplate中のリソースなどの取得は通常とは異なる。これはControlTemplateは独自のnamescopeを持つため。

今回はthis.GetTemplateChild("gradientLine")を使ってるけどthis.Template.FindName("gradientLine", this)でもよい。

Storyboardの取得

storyboardを<Line.Resources>に書いた場合は line.Resources["gradientAnimationTrue"] で取得できる。

storyboardを<Style.Resources>に書いた場合 this.FindResource("gradientAnimationFalse")で取得できる。

Storyboardを操作するタイミングについて

テンプレート読み込みが完了した後に行う。OnApplyTemplateを利用すると都合が良い。

参考

Templates in WPF have a self-contained namescope. This is because templates are re-used, and any name defined in a template cannot remain unique when multiple instances of a control each instantiate its template.

https://docs.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement.gettemplatechild?redirectedfrom=MSDN&view=net-5.0#System_Windows_FrameworkElement_GetTemplateChild_System_String_

OnApplyTemplate is called after the template is completely generated and attached to the logical tree.

https://docs.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement.applytemplate?view=net-5.0#System_Windows_FrameworkElement_ApplyTemplate

その他

Code BehindでStoryboardを操作できるようにしておくとSetSpeedRatioでアニメーションの動作速度を柔軟に設定できる。XAMLからでも<SetStoryboardSpeedRatio>で設定できるけどBindingしようとすると例外'SetStoryboardSpeedRatio' の 'SpeedRatio' プロパティで 'Binding' を設定することはできません。'Binding' は、DependencyObject の DependencyProperty でのみ設定できます。'が出る。