单独的动画有效,情节提要无效。为什么?

Separate animations work, Storyboard doesn't. Why?

编辑 1:为了满足“Complete, Minimal And Verifiable”示例要求

TL:DR; 故事板根本没有动画。为什么?

我正在尝试创建一个情节提要,它将动画化渐变内所有渐变停止点的偏移,将它们从左移到右。

我确定这只是一个愚蠢的语法错误或参数错误,或者是我在某个地方出现的错误,但我找不到它。

这是 XAML :

<Window
    x:Class="GradientShifting.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:GradientShiftDerping"
    mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"
    AllowsTransparency="True" WindowStyle="None">
    <Window.Background>
        <LinearGradientBrush EndPoint="1,1" StartPoint="0,0">
            <GradientStop Color="Black" Offset="0"/>
            <GradientStop Color="White" Offset="1"/>
        </LinearGradientBrush>
    </Window.Background>
</Window>

这是背后的代码:

using System;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace GradientShifting {
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {
        private Storyboard _sbGradientShifter = new Storyboard( );          
        public MainWindow( ) {
            InitializeComponent( );
            this.Loaded += new RoutedEventHandler(
                ( S, E ) => this.SetupGradientShift( ) );
        }

    private void SetupGradientShift( ){
        GradientBrush BackBrush = this.Background as GradientBrush;
        if ( BackBrush != null ) {
            /* Ordering by offset is important because
               the last color in the gradient requires
               special consideration. */
            DoubleAnimationUsingKeyFrames DAUKF;
            GradientStopCollection GSC = new GradientStopCollection(
                BackBrush.GradientStops.OrderBy( GS => GS.Offset ) );
            foreach( GradientStop GS in GSC ){
                DAUKF = new DoubleAnimationUsingKeyFrames( ) {
                    KeyFrames = new DoubleKeyFrameCollection( ){
                        new LinearDoubleKeyFrame(
                            1.0D, KeyTime.FromPercent( 1.0D )
                }, Duration = TimeSpan.FromSeconds( 3 )
            };

            //Something I am doing from here...
            this._sbGradientShifter.Children.Add( DAUKF );

            Storyboard.SetTarget( DAUKF, GS );

            Storyboard.SetTargetProperty(
                DAUKF, new PropertyPath( GradientStop.OffsetProperty ) );
        }
        this._sbGradientShifter.Begin( this ); //THIS DOES NOTHING.         
    }
}

所以,再一次 - 此代码不起作用。我已经能够通过调用 GradientStop.BeginAnimation 启动情节提要中包含的动画,但是 Storyboard.Begin 不起作用。

出于某种原因,Storyboard.SetTarget 仅适用于 FrameworkElementFrameworkContentElement。做你想做的事,你可以自己开始单独的动画,就像你在 "hack" 中所做的那样(IMO,这是一种非常合理的制作动画的方式)。

或者您可以为所有目标注册名称,例如:

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

如果您决定直接调用动画,您真的不需要将它们保存在单独的列表中。只需在创建它们后立即在第一个循环中启动它们。

如果您需要更多协调,例如暂停动画、使用名称范围、高级时序或 XAML 中的动画,故事板非常有用。但在你的情况下,简单的时间表似乎就足够了。

, 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? 中所述,了解更多详细信息。

您可以按照 Eli 的回答中的说明动态生成名称。其他替代方案包括在 XAML 中指定名称,然后在代码隐藏中引用它们,或者只是在 XAML 中声明整个内容。在所有情况下,您都必须使用 Storyboard.TargetName 属性 而不是 Target 属性.

如果您想在 XAML 中指定名称,您可以通过多种方式在代码隐藏中使用它们:您可以明确地对名称进行硬编码,或者您可以将它们查找为你需要他们。如果您只需要处理一个动画并且知道名称不会改变,那么前者将是合适的。如果你想将通用算法应用于多个场景,后者将是合适的。

硬编码:

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);
}

运行时查找:

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);
    }
}

无论哪种方式,您都需要在 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>

但就我个人而言,我认为实际上在 XAML 中制作整个动画并且将代码隐藏在其中更好:

<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>