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

  • Storyboardを作成する
        private void UpdateStoryboard()
        {
            if (!(GetLineObject() is Line line)) return;
            gradientAnimationTrue?.Remove(line);
            gradientAnimationFalse?.Remove(line);
            if (AnimationType) gradientAnimationTrue?.Begin(line, true);
            else gradientAnimationFalse?.Begin(line, true);
        }

        private Storyboard gradientAnimationTrue = default;
        private Storyboard gradientAnimationFalse = default;
        private void CreateStoryboard()
        {
            var line = GetLineObject();
            var targetOffset = "(Line.Stroke).(LinearGradientBrush.GradientStops)[{0}].(GradientStop.Offset)";
            var duration = new Duration(TimeSpan.FromSeconds(1));

            var dat = new DoubleAnimation();
            dat.Duration = duration;
            dat.From = 0;
            dat.By = 1;
            Storyboard.SetTarget(line, dat);
            Storyboard.SetTargetProperty(dat, new PropertyPath(string.Format(targetOffset, 1)));
            gradientAnimationTrue = new Storyboard() { AutoReverse = true, RepeatBehavior = RepeatBehavior.Forever };
            gradientAnimationTrue.Children.Add(dat);

            var daf0 = new DoubleAnimation(0,1,duration);
            Storyboard.SetTarget(line, daf0);
            Storyboard.SetTargetProperty(daf0, new PropertyPath(string.Format(targetOffset, 0)));
            var daf1 = new DoubleAnimation(0, 1, duration);
            Storyboard.SetTarget(line, daf1);
            Storyboard.SetTargetProperty(daf1, new PropertyPath(string.Format(targetOffset, 1)));
            gradientAnimationFalse = new Storyboard() { AutoReverse = true, RepeatBehavior = RepeatBehavior.Forever };
            gradientAnimationFalse.Children.Add(daf0);
            gradientAnimationFalse.Children.Add(daf1);
        }
        private Line GetLineObject() => this.GetTemplateChild("gradientLine") as Line;
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            CreateStoryboard();
            UpdateStoryboard();
        }

Generic.xaml

  • Storyboardを削除する
<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}">
                    <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>

注意点

SetTargetで指定できるのはFrameworkElement/FrameworkContentElementっぽい?(line.strokeとかを指定してもエラーは出ないけどアニメーションが動作しなかった)

SetTargetNameではFrameworkElement, FrameworkContentElement, Freezableを指定できる。

その他

targetpropertyについて

省略する場合

var targetOffset = "Stroke.GradientStops[{0}].Offset";

PropertyPath(String, Object[])使う場合

            var targetPatht = "(0).(1)[{0}].(2)";
            var dps = new DependencyProperty[]{
                Line.StrokeProperty,
                LinearGradientBrush.GradientStopsProperty,
                GradientStop.OffsetProperty
            };
            Storyboard.SetTargetProperty(dat, new PropertyPath(string.Format(targetPatht, 1), dps));

targetについて

Storyboard.SetTargetName(dat, "gradientLine");でもよい。 他適当に名前を付けてれば適当に使える。

    <GradientStop x:Name="gs1" Color="{Binding RightColor,RelativeSource={RelativeSource TemplatedParent}}" Offset="1"/>

とすれば

    Storyboard.SetTargetName(dat, "gs1");
    Storyboard.SetTargetProperty(dat, new PropertyPath(GradientStop.OffsetProperty));

とできる。

参考

The object must be a FrameworkElement, FrameworkContentElement, or Freezable.

https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.animation.storyboard.targetname?view=net-5.0

おまけ

LinearGradientBrushもCode Behindで作れば柔軟な操作ができる。線長に合わせてGradientStopを増やすとか。

        private void CreateLinearGradientBrush()
        {
            var gradientStops = new GradientStopCollection()
            {
                new GradientStop(LeftColor,0),
                new GradientStop(RightColor,1),
            };
            GetLineObject().Stroke = new LinearGradientBrush(gradientStops);
        }
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            CreateLinearGradientBrush();
            CreateStoryboard();
            UpdateStoryboard();
        }

作成したオブジェクトはthis.RegisterNameで登録しておくと便利。 ただしNameScope.SetNameScope(this,new NameScope());で独自のNameScopeを作成しておくこと。 作成しておかないとCustomControlを複数配置した場合、名前が被ってエラーになる。これはrootのNamescopeに登録されるため。たぶん。