单独的动画有效,情节提要无效。为什么?
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
仅适用于 FrameworkElement
或 FrameworkContentElement
。做你想做的事,你可以自己开始单独的动画,就像你在 "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>
编辑 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
仅适用于 FrameworkElement
或 FrameworkContentElement
。做你想做的事,你可以自己开始单独的动画,就像你在 "hack" 中所做的那样(IMO,这是一种非常合理的制作动画的方式)。
或者您可以为所有目标注册名称,例如:
foreach (var gs in gsc)
{
var name = "GS_" + Guid.NewGuid().ToString("N");
RegisterName(name, gs);
Storyboard.SetTargetName(caukf, name);
}
如果您决定直接调用动画,您真的不需要将它们保存在单独的列表中。只需在创建它们后立即在第一个循环中启动它们。
如果您需要更多协调,例如暂停动画、使用名称范围、高级时序或 XAML 中的动画,故事板非常有用。但在你的情况下,简单的时间表似乎就足够了。
如
您可以按照 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>