Separate animations work, Storyboard doesn't. Why?

前端 未结 2 1532
无人共我
无人共我 2021-01-16 03:02

EDIT 1 : In order to satisfy "Complete, Minimal And Verifiable" Example Requirement

TL:DR; Storyboard doesn\'t animate at all. Why?

<
相关标签:
2条回答
  • 2021-01-16 03:26

    As noted in the other answer, this is an undocumented (as far as I know) limitation of WPF. Call it a bug. See previous posts such as Storyboard targetting multiple objects, using SetTarget method, doesn't work and Why don't these animations work when I'm using a storyboard? for additional details.

    You can generate names dynamically as noted in Eli's answer. Other alternatives include specifying the names in XAML and then referencing them in the code-behind, or just declaring the entire thing in XAML. In all cases, you'll have to use the Storyboard.TargetName property instead of the Target property.

    If you want to specify the names in XAML, there are a couple of ways you can use them in code-behind: you can hard-code the names explicitly, or you can look them up as you need them. The former would be appropriate if you had to deal with just the one animation and knew the names would not change. The latter would be appropriate if you want to apply a general-purpose algorithm to multiple scenarios.

    Hard-coded:

    private void SetupGradientShift()
    {
        string[] names = { "stop1", "stop2" };
    
        foreach (string name in names)
        {
            DoubleAnimationUsingKeyFrames daukf =
                new DoubleAnimationUsingKeyFrames
                {
                    KeyFrames =
                        new DoubleKeyFrameCollection
                        {
                            new LinearDoubleKeyFrame(1.0, KeyTime.FromPercent(1.0))
                        },
                    Duration = TimeSpan.FromSeconds(3)
                };
    
            this._sbGradientShifter.Children.Add(daukf);
            Storyboard.SetTargetName(daukf, name);
            Storyboard.SetTargetProperty(
                daukf, new PropertyPath(GradientStop.OffsetProperty));
        }
    
        this._sbGradientShifter.Begin(this);
    }
    

    Look-up at runtime:

    private void SetupGradientShift()
    {
        GradientBrush BackBrush = this.Background as GradientBrush;
        if (BackBrush != null)
        {
            INameScopeDictionary nameScope = (INameScopeDictionary)NameScope.GetNameScope(this);
    
            foreach (GradientStop gradientStop in BackBrush.GradientStops.OrderBy(stop => stop.Offset))
            {
                DoubleAnimationUsingKeyFrames daukf =
                    new DoubleAnimationUsingKeyFrames
                    {
                        KeyFrames =
                            new DoubleKeyFrameCollection
                            {
                                new LinearDoubleKeyFrame(1.0, KeyTime.FromPercent(1.0))
                            },
                        Duration = TimeSpan.FromSeconds(3)
                    };
    
                this._sbGradientShifter.Children.Add(daukf);
    
                string name = nameScope.First(kvp => kvp.Value == gradientStop).Key;
    
                Storyboard.SetTargetName(daukf, name);
                Storyboard.SetTargetProperty(
                    daukf, new PropertyPath(GradientStop.OffsetProperty));
            }
    
            this._sbGradientShifter.Begin(this);
        }
    }
    

    Either way, you would need to declare the name in XAML:

    <Window.Background>
      <LinearGradientBrush EndPoint="1,1" StartPoint="0,0">
        <GradientStop x:Name="stop1" Color="Black" Offset="0"/>
        <GradientStop x:Name="stop2" Color="White" Offset="1"/>
      </LinearGradientBrush>
    </Window.Background>
    

    But personally, I think it actually would be better to just do the entire animation in XAML and leave code-behind out of it:

    <Window x:Class="TestSO38537640AnimateCodeBehind.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525"
            AllowsTransparency="True" WindowStyle="None">
    
      <Window.Resources>
        <Storyboard x:Key="storyboard1">
          <DoubleAnimationUsingKeyFrames Storyboard.TargetName="stop1"
                                         Storyboard.TargetProperty="Offset"
                                         Duration="0:0:3">
            <LinearDoubleKeyFrame Value="1" KeyTime="100%"/>
          </DoubleAnimationUsingKeyFrames>
          <DoubleAnimationUsingKeyFrames Storyboard.TargetName="stop2"
                                         Storyboard.TargetProperty="Offset"
                                         Duration="0:0:3">
            <LinearDoubleKeyFrame Value="1" KeyTime="100%"/>
          </DoubleAnimationUsingKeyFrames>
        </Storyboard>
      </Window.Resources>
    
      <Window.Background>
        <LinearGradientBrush EndPoint="1,1" StartPoint="0,0">
          <GradientStop x:Name="stop1" Color="Black" Offset="0"/>
          <GradientStop x:Name="stop2" Color="White" Offset="1"/>
        </LinearGradientBrush>
      </Window.Background>
    
      <Window.Triggers>
        <EventTrigger RoutedEvent="Loaded">
          <BeginStoryboard Storyboard="{StaticResource storyboard1}"/>
        </EventTrigger>
      </Window.Triggers>
    </Window>
    
    0 讨论(0)
  • 2021-01-16 03:48

    For some reason, Storyboard.SetTarget only works with FrameworkElements or FrameworkContentElements. To do what you want, you can either start the individual animations yourself as you have in your "hack" (a perfectly reasonable way of doing animations, IMO).

    Or you can register names for all your targets, e.g.:

    foreach (var gs in gsc)
    {
        var name = "GS_" + Guid.NewGuid().ToString("N");
        RegisterName(name, gs);
        Storyboard.SetTargetName(caukf, name);
    }
    

    If you decide to invoke the animations directly, you really don't need to save them in a separate list. Just start them immediately in the first loop as soon as they are created.

    Storyboards are great if you need more coordination, such as pausing animations, using name scopes, advanced timing or animate from XAML. But in your case it seems simple Timelines would be adequate.

    0 讨论(0)
提交回复
热议问题