演示图板概述
更新:2007 年 11 月
本主题演示如何使用 Storyboard 对象来组织和应用动画,还描述了如何交互操作 Storyboard 对象,并介绍了间接属性目标语法。
先决条件
要了解本主题,您应当熟悉不同的动画类型及其基本功能。有关动画的简介,请参见动画概述。您还应当了解如何使用附加属性。有关附加属性的更多信息,请参见附加属性概述。
什么是 Storyboard?
动画不是唯一有用的时间线类型。还提供了其他时间线类来帮助您组织时间线集合,并将时间线应用于属性。容器时间线派生自 TimelineGroup 类,包括 ParallelTimeline 和 Storyboard。
Storyboard 是一种为其所包含的时间线提供目标信息的容器时间线。Storyboard 可以包含任意类型的 Timeline,包括其他容器时间线和动画。可以使用 Storyboard 对象将影响各种对象和属性的时间线组合成一个时间线树,以便于组织和控制复杂计时行为。例如,假设您需要一个执行以下三个操作的按钮。
当用户选择该按钮时,该按钮增大并更改颜色。
当单击该按钮时,该按钮缩小并恢复其原始大小。
当该按钮变成禁用时,缩小且不透明度缩减到 50%。
在此例中,您可以向同一对象应用多组动画,并且可以播放多次,具体取决于按钮的状态。可以使用 Storyboard 对象来组织动画并将它们成组地应用于一个或多个对象。
在什么情况下使用 Storyboard?
可以使用 Storyboard 对可动画处理的类的依赖项属性进行动画处理(有关如何使类成为可动画处理的类的更多信息,请参见动画概述)。不过,由于图板演示是框架级别的功能,该对象必须属于 FrameworkElement 或 FrameworkContentElement 的 NameScope。
例如,您可以使用 Storyboard 执行以下操作:
对用于绘制按钮(一种 FrameworkElement)背景的 SolidColorBrush(非框架元素)进行动画处理,
对用于绘制使用 Image (FrameworkElement) 显示的 GeometryDrawing(非框架元素)的填充的 SolidColorBrush(非框架元素)进行动画处理,
在代码中,如果 SolidColorBrush 向 FrameworkElement 注册其名称,则对包含该 FrameworkElement 的类所声明的 SolidColorBrush 进行动画处理。
但是,不能使用 Storyboard 来对未向 FrameworkElement 或 FrameworkContentElement 注册其名称或未用于设置 FrameworkElement 或 FrameworkContentElement 的属性的 SolidColorBrush 进行动画处理。
如何使用 Storyboard 应用动画
若要使用 Storyboard 来组织和应用动画,可以将动画添加为 Storyboard 的子时间线。Storyboard 类提供 Storyboard.TargetName 和 Storyboard.TargetProperty 附加属性。您可以在动画上设置这些属性以指定其目标对象和属性。
若要将动画应用于其目标,您可以使用触发器操作或方法来开始 Storyboard。在 XAML 中,将 BeginStoryboard 对象与 EventTrigger、Trigger 或 DataTrigger 一起使用。在代码中,还可以使用 Begin 方法。
下表列出了支持每个 Storyboard 开始技术的不同方面:基于实例、样式、控件模板和数据模板。“基于实例”指的是直接将动画或演示图板应用于对象实例(而不是在样式、控件模板或数据模板中应用)的技术。
开始演示图板时使用… |
基于实例 |
样式 |
控件模板 |
数据模板 |
示例 |
---|---|---|---|---|---|
Yes |
Yes |
Yes |
Yes |
||
No |
Yes |
Yes |
Yes |
||
No |
Yes |
Yes |
Yes |
||
Begin 方法 |
Yes |
No |
No |
No |
下面的示例使用 Storyboard 对 Rectangle 元素的 Width 和用于绘制该 Rectangle 的 SolidColorBrush 的 Color 进行动画处理。
<!-- This example shows how to animate with a storyboard.-->
<Page xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Microsoft.Samples.Animation.StoryboardsExample"
WindowTitle="Storyboards Example">
<StackPanel Margin="20">
<Rectangle Name="MyRectangle"
Width="100"
Height="100">
<Rectangle.Fill>
<SolidColorBrush x:Name="MySolidColorBrush" Color="Blue" />
</Rectangle.Fill>
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="MyRectangle"
Storyboard.TargetProperty="Width"
From="100" To="200" Duration="0:0:1" />
<ColorAnimation
Storyboard.TargetName="MySolidColorBrush"
Storyboard.TargetProperty="Color"
From="Blue" To="Red" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</StackPanel>
</Page>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Data;
using System.Windows.Shapes;
using System.Windows.Input;
namespace Microsoft.Samples.Animation
{
public class StoryboardsExample : Page
{
public StoryboardsExample()
{
this.WindowTitle = "Storyboards Example";
StackPanel myStackPanel = new StackPanel();
myStackPanel.Margin = new Thickness(20);
Rectangle myRectangle = new Rectangle();
myRectangle.Name = "MyRectangle";
// Create a name scope for the page.
NameScope.SetNameScope(this, new NameScope());
this.RegisterName(myRectangle.Name, myRectangle);
myRectangle.Width = 100;
myRectangle.Height = 100;
SolidColorBrush mySolidColorBrush = new SolidColorBrush(Colors.Blue);
this.RegisterName("MySolidColorBrush", mySolidColorBrush);
myRectangle.Fill = mySolidColorBrush;
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = 100;
myDoubleAnimation.To = 200;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard.SetTargetName(myDoubleAnimation, myRectangle.Name);
Storyboard.SetTargetProperty(myDoubleAnimation,
new PropertyPath(Rectangle.WidthProperty));
ColorAnimation myColorAnimation = new ColorAnimation();
myColorAnimation.From = Colors.Blue;
myColorAnimation.To = Colors.Red;
myColorAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard.SetTargetName(myColorAnimation, "MySolidColorBrush");
Storyboard.SetTargetProperty(myColorAnimation,
new PropertyPath(SolidColorBrush.ColorProperty));
Storyboard myStoryboard = new Storyboard();
myStoryboard.Children.Add(myDoubleAnimation);
myStoryboard.Children.Add(myColorAnimation);
myRectangle.MouseEnter += delegate(object sender, MouseEventArgs e)
{
myStoryboard.Begin(this);
};
myStackPanel.Children.Add(myRectangle);
this.Content = myStackPanel;
}
}
}
以下几节更详细地描述了 TargetName 和 TargetProperty 附加属性。
以框架元素、框架内容元素和 Freezable 元素为目标
上面一节提到对于动画,要查找其目标,必须知道目标的名称和要进行动画处理的属性。指定要进行动画处理的属性非常直接:只需用要进行动画处理的属性的名称来设置 Storyboard.TargetProperty 即可。 通过在动画上设置 Storyboard.TargetName 属性来指定具有要进行动画处理的属性的对象的名称。
为了使 TargetName 属性起作用,目标对象必须有名称。在 XAML 中为 FrameworkElement 或 FrameworkContentElement 分配名称不同于为 Freezable 对象分配名称。
框架元素是从 FrameworkElement 类继承的类。框架元素的示例包括 Window、DockPanel、Button 和 Rectangle。从本质上而言,所有窗口、面板和控件都是元素。框架内容元素是从 FrameworkContentElement 类继承的类。框架内容元素的示例包括 FlowDocument 和 Paragraph。如果您不确定某类型是否是框架元素或框架内容元素,请查看它是否具有 Name 属性。如果它具有 Name 属性,则可能是框架元素或框架内容元素。若要进一步确定,请检查其类型页面的“继承层次结构”部分。
若要在 XAML 中以框架元素或框架内容元素为目标,请设置其 Name 属性。在代码中,还需要使用 RegisterName 方法向创建了 NameScope 的元素注册该元素名称。
下面的示例摘自上一个示例,它为一个 Rectangle(一种 FrameworkElement)分配名称 MyRectangle。
<Rectangle Name="MyRectangle"
Width="100"
Height="100">
Rectangle myRectangle = new Rectangle();
myRectangle.Name = "MyRectangle";
// Create a name scope for the page.
NameScope.SetNameScope(this, new NameScope());
this.RegisterName(myRectangle.Name, myRectangle);
在该元素具有名称后,可以对其属性进行动画处理。
<DoubleAnimation
Storyboard.TargetName="MyRectangle"
Storyboard.TargetProperty="Width"
From="100" To="200" Duration="0:0:1" />
Storyboard.SetTargetName(myDoubleAnimation, myRectangle.Name);
Storyboard.SetTargetProperty(myDoubleAnimation,
new PropertyPath(Rectangle.WidthProperty));
Freezable 类型是从 Freezable 类继承的类。Freezable 的示例包括 SolidColorBrush、RotateTransform 和 GradientStop。
若要在 XAML 中以 Freezable 作为动画的目标,可以使用 x:Name 属性 为其分配名称。在代码中,可以使用 RegisterName 方法向已创建了 NameScope 的元素注册其名称。
下面的示例为 Freezable 对象分配名称。
<SolidColorBrush x:Name="MySolidColorBrush" Color="Blue" />
SolidColorBrush mySolidColorBrush = new SolidColorBrush(Colors.Blue);
this.RegisterName("MySolidColorBrush", mySolidColorBrush);
然后动画可以以该对象为目标。
<ColorAnimation
Storyboard.TargetName="MySolidColorBrush"
Storyboard.TargetProperty="Color"
From="Blue" To="Red" Duration="0:0:1" />
Storyboard.SetTargetName(myColorAnimation, "MySolidColorBrush");
Storyboard.SetTargetProperty(myColorAnimation,
new PropertyPath(SolidColorBrush.ColorProperty));
Storyboard 对象使用名称范围解析 TargetName 属性。有关 WPF 名称范围的更多信息,请参见 WPF 名称范围。如果忽略 TargetName 属性,动画则以定义该属性时所在的元素为目标(如果存在样式,则以带样式的元素为目标)。
有时,不能为 Freezable 对象分配名称。例如,如果 Freezable 声明为资源,或者用于设置样式中的属性值,则不能为该对象分配名称。由于它没有名称,因此不能直接以它为目标 - 但是可以间接以它为目标。下面几节描述如何使用间接目标。
间接目标
有时动画不能直接以 Freezable 为目标,例如当 Freezable 声明为资源或者用于设置样式中的属性值时。在这些情况下,即使不能直接将 Freezable 对象作为目标,您仍可以对其进行动画处理。您可以为 TargetName 属性分配 Freezable“所属”的元素的名称,而不是使用 Freezable 的名称来设置该属性。例如,SolidColorBrush 用于设置矩形元素的 Fill 属于该矩形。若要对该画笔进行动画处理,您需要使用一个属性链来设置动画的 TargetProperty,这些属性起始于使用 Freezable 设置的框架元素或框架内容元素的属性,结束于要进行动画处理的 Freezable 属性。
<ColorAnimation
Storyboard.TargetName="Rectangle01"
Storyboard.TargetProperty="Fill.Color"
From="Blue" To="AliceBlue" Duration="0:0:1" />
DependencyProperty[] propertyChain =
new DependencyProperty[]
{Rectangle.FillProperty, SolidColorBrush.ColorProperty};
string thePath = "(0).(1)";
PropertyPath myPropertyPath = new PropertyPath(thePath, propertyChain);
Storyboard.SetTargetProperty(myColorAnimation, myPropertyPath);
请注意,如果 Freezable 被冻结,则将进行克隆,并且将对该克隆进行动画处理。如果发生这种情况,原始对象的 HasAnimatedProperties 属性会继续返回 false,因为实际上并不会对原始对象进行动画处理。有关克隆的更多信息,请参见 Freezable 对象概述。
另请注意,当使用间接属性目标时,可能会以不存在的对象为目标。例如,您可能假设使用 SolidColorBrush 设置了特定按钮的 Background 并尝试对其颜色进行动画处理,而实际上该按钮的 Background 是使用 LinearGradientBrush 设置的。在这种情况下,不会引发异常;由于 LinearGradientBrush 不对 Color 属性的更改做出反应,因此动画不会具有可视效果。
下面几节更详细地描述了间接属性目标语法。
在 XAML 中间接以 Freezable 的属性为目标
若要在 XAML 中以 Freezable 的属性为目标,请使用以下语法。
ElementPropertyName.FreezablePropertyName |
其中
ElementPropertyName 是使用 Freezable 设置的 FrameworkElement 的属性,
FreezablePropertyName 是要进行动画处理的 Freezable 的属性。
下面的代码演示如何对用于设置矩形元素的 Fill 的 SolidColorBrush 的 Color 进行
动画处理。
<Rectangle
Name="Rectangle01"
Height="100"
Width="100"
Fill="{StaticResource MySolidColorBrushResource}">
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.MouseEnter">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetName="Rectangle01"
Storyboard.TargetProperty="Fill.Color"
From="Blue" To="AliceBlue" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
有时,您需要以集合或数组中包含的 Freezable 为目标。
若要以集合中包含的 Freezable 为目标,可以使用以下路径语法。
ElementPropertyName.Children[CollectionIndex].FreezablePropertyName |
其中 CollectionIndex 是对象在对象数组或集合中的索引。
例如,假设矩形具有应用于其 RenderTransform 属性的 TransformGroup 资源,您希望对它包含的变换之一进行动画处理。
<TransformGroup x:Key="MyTransformGroupResource"
x:Shared="False">
<ScaleTransform />
<RotateTransform />
</TransformGroup>
下面的代码演示如何对上例中演示的 RotateTransform 的 Angle 属性进行动画处理。
<Rectangle
Name="Rectangle02"
Height="100"
Width="100"
Fill="Blue"
RenderTransform="{StaticResource MyTransformGroupResource}">
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="Rectangle02"
Storyboard.TargetProperty="RenderTransform.Children[1].Angle"
From="0" To="360" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
在代码中间接以 Freezable 的属性为目标
在代码中,创建一个 PropertyPath 对象。当创建 PropertyPath 时,请指定 Path 和 PathParameters。
若要创建 PathParameters,需要创建 DependencyProperty 类型的数组,其中包含依赖项属性标识符字段的列表。第一个标识符字段用于使用 Freezable 设置的 FrameworkElement 或 FrameworkContentElement 的属性。下一个标识符字段表示目标 Freezable 的属性。将其看作一个属性链,该属性链将 Freezable 连接到 FrameworkElement 对象。
下面的示例是一个依赖项属性链,这些属性以用于设置矩形元素的 Fill 的 SolidColorBrush 的 Color 为目标。
DependencyProperty[] propertyChain =
new DependencyProperty[]
{Rectangle.FillProperty, SolidColorBrush.ColorProperty};
您还需要指定 Path。Path 是一个 String,用于告知 Path 如何解释其 PathParameters。它使用以下语法。
(OwnerPropertyArrayIndex).(FreezablePropertyArrayIndex) |
位置
OwnerPropertyArrayIndex 是 DependencyProperty 数组的索引,该数组包含使用 Freezable 设置的 FrameworkElement 对象属性的标识符,
FreezablePropertyArrayIndex 是 DependencyProperty 数组的索引,该数组包含目标属性的标识符。
下面的示例演示上例中定义的 PathParameters 所附带的 Path。
DependencyProperty[] propertyChain =
new DependencyProperty[]
{Rectangle.FillProperty, SolidColorBrush.ColorProperty};
string thePath = "(0).(1)";
下面的示例将上例中的代码进行合并,以便对用于设置矩形元素的 Fill 的 SolidColorBrush 的 Color 进行动画处理。
// Create a name scope for the page.
NameScope.SetNameScope(this, new NameScope());
Rectangle rectangle01 = new Rectangle();
rectangle01.Name = "Rectangle01";
this.RegisterName(rectangle01.Name, rectangle01);
rectangle01.Width = 100;
rectangle01.Height = 100;
rectangle01.Fill =
(SolidColorBrush)this.Resources["MySolidColorBrushResource"];
ColorAnimation myColorAnimation = new ColorAnimation();
myColorAnimation.From = Colors.Blue;
myColorAnimation.To = Colors.AliceBlue;
myColorAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard.SetTargetName(myColorAnimation, rectangle01.Name);
DependencyProperty[] propertyChain =
new DependencyProperty[]
{Rectangle.FillProperty, SolidColorBrush.ColorProperty};
string thePath = "(0).(1)";
PropertyPath myPropertyPath = new PropertyPath(thePath, propertyChain);
Storyboard.SetTargetProperty(myColorAnimation, myPropertyPath);
Storyboard myStoryboard = new Storyboard();
myStoryboard.Children.Add(myColorAnimation);
BeginStoryboard myBeginStoryboard = new BeginStoryboard();
myBeginStoryboard.Storyboard = myStoryboard;
EventTrigger myMouseEnterTrigger = new EventTrigger();
myMouseEnterTrigger.RoutedEvent = Rectangle.MouseEnterEvent;
myMouseEnterTrigger.Actions.Add(myBeginStoryboard);
rectangle01.Triggers.Add(myMouseEnterTrigger);
有时,您需要以集合或数组中包含的 Freezable 为目标。例如,假设矩形具有应用于其 RenderTransform 属性的 TransformGroup 资源,您希望对它包含的变换之一进行动画处理。
<TransformGroup x:Key="MyTransformGroupResource"
x:Shared="False">
<ScaleTransform />
<RotateTransform />
</TransformGroup>
若要以集合中包含的 Freezable 为目标,可以使用以下路径语法。
(OwnerPropertyArrayIndex).(CollectionChildrenPropertyArrayIndex)[CollectionIndex].(FreezablePropertyArrayIndex) |
其中 CollectionIndex 是对象在对象数组或集合中的索引。
若要以 RotateTransform(TransformGroup 中的第二个变换)的 Angle 属性为目标,请使用以下 Path 和 PathParameters。
DependencyProperty[] propertyChain =
new DependencyProperty[]
{
Rectangle.RenderTransformProperty,
TransformGroup.ChildrenProperty,
RotateTransform.AngleProperty
};
string thePath = "(0).(1)[1].(2)";
PropertyPath myPropertyPath = new PropertyPath(thePath, propertyChain);
Storyboard.SetTargetProperty(myDoubleAnimation, myPropertyPath);
下面的示例演示用于对 TransformGroup 中包含的 RotateTransform 的 Angle 进行动画处理的完整代码。
Rectangle rectangle02 = new Rectangle();
rectangle02.Name = "Rectangle02";
this.RegisterName(rectangle02.Name, rectangle02);
rectangle02.Width = 100;
rectangle02.Height = 100;
rectangle02.Fill = Brushes.Blue;
rectangle02.RenderTransform =
(TransformGroup)this.Resources["MyTransformGroupResource"];
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = 0;
myDoubleAnimation.To = 360;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard.SetTargetName(myDoubleAnimation, rectangle02.Name);
DependencyProperty[] propertyChain =
new DependencyProperty[]
{
Rectangle.RenderTransformProperty,
TransformGroup.ChildrenProperty,
RotateTransform.AngleProperty
};
string thePath = "(0).(1)[1].(2)";
PropertyPath myPropertyPath = new PropertyPath(thePath, propertyChain);
Storyboard.SetTargetProperty(myDoubleAnimation, myPropertyPath);
Storyboard myStoryboard = new Storyboard();
myStoryboard.Children.Add(myDoubleAnimation);
BeginStoryboard myBeginStoryboard = new BeginStoryboard();
myBeginStoryboard.Storyboard = myStoryboard;
EventTrigger myMouseEnterTrigger = new EventTrigger();
myMouseEnterTrigger.RoutedEvent = Rectangle.MouseEnterEvent;
myMouseEnterTrigger.Actions.Add(myBeginStoryboard);
rectangle02.Triggers.Add(myMouseEnterTrigger);
间接以 Freezable 为目标并将其作为开始点
上面几节描述了如何通过从 FrameworkElement 或 FrameworkContentElement 开始为 Freezable 子属性创建属性链,来间接以 Freezable 为目标。您还可以使用 Freezable 作为开始点,并间接以其 Freezable 子属性之一为目标。如果使用 Freezable 作为间接目标的开始点,则存在一个附加限制:起始 Freezable 以及它与子属性间接目标之间的每个 Freezable 都不能冻结。
在 XAML 中以交互方式控制 Storyboard
若要在 可扩展应用程序标记语言 (XAML) 中启动 Storyboard,请使用 BeginStoryboard 触发器操作。BeginStoryboard 会将动画分发到要进行动画处理的对象和属性,然后启动 Storyboard (有关此过程的详细信息,请参见动画和计时系统概述)。如果通过指定 BeginStoryboard 的 Name 属性为它提供一个名称,则它将成为可控制的 Storyboard。然后,可以在 Storyboard 启动后以交互方式对它进行控制。下面列出与事件触发器一起用来控制 Storyboard 的可控制 Storyboard 操作。
PauseStoryboard:暂停 Storyboard。
ResumeStoryboard:继续暂停的 Storyboard。
SetStoryboardSpeedRatio:更改 Storyboard 速度。
SkipStoryboardToFill:使 Storyboard 前进到其填充期的末尾(如果有填充期)。
StopStoryboard:停止 Storyboard。
RemoveStoryboard:移除 Storyboard。
在下面的示例中,使用可控制的 Storyboard 操作来以交互方式控制 Storyboard。
<Page
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Microsoft.SDK.Animation.ControllableStoryboardExample"
WindowTitle="Fading Rectangle Example">
<StackPanel Margin="10">
<Rectangle
Name="MyRectangle"
Width="100"
Height="100"
Fill="Blue">
</Rectangle>
<Button Name="BeginButton">Begin</Button>
<Button Name="PauseButton">Pause</Button>
<Button Name="ResumeButton">Resume</Button>
<Button Name="SkipToFillButton">Skip To Fill</Button>
<Button Name="StopButton">Stop</Button>
<StackPanel.Triggers>
<EventTrigger RoutedEvent="Button.Click" SourceName="BeginButton">
<BeginStoryboard Name="MyBeginStoryboard">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="MyRectangle"
Storyboard.TargetProperty="(Rectangle.Opacity)"
From="1.0" To="0.0" Duration="0:0:5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Button.Click" SourceName="PauseButton">
<PauseStoryboard BeginStoryboardName="MyBeginStoryboard" />
</EventTrigger>
<EventTrigger RoutedEvent="Button.Click" SourceName="ResumeButton">
<ResumeStoryboard BeginStoryboardName="MyBeginStoryboard" />
</EventTrigger>
<EventTrigger RoutedEvent="Button.Click" SourceName="SkipToFillButton">
<SkipStoryboardToFill BeginStoryboardName="MyBeginStoryboard" />
</EventTrigger>
<EventTrigger RoutedEvent="Button.Click" SourceName="StopButton">
<StopStoryboard BeginStoryboardName="MyBeginStoryboard" />
</EventTrigger>
</StackPanel.Triggers>
</StackPanel>
</Page>
使用代码以交互方式控制 Storyboard
上一示例已经演示了如何使用触发器操作进行动画处理。在代码中,还可以使用 Storyboard 类的交互方法来控制 Storyboard。若要使 Storyboard 在代码中可交互,必须使用该 Storyboard 的 Begin 方法的适当重载并指定 true 使之可以控制。有关更多信息,请参见 Begin(FrameworkElement, Boolean) 页。
下面的列表显示了在 Storyboard 启动之后可用来对其进行操作的方法:
使用这些方法的优点是您不需要创建 Trigger 或 TriggerAction 对象,只需要一个对要操作的可控制 Storyboard 的引用。
**注意:**对 Clock 采取的所有交互操作(因而也会对 Storyboard 采取这些操作)将在计时引擎下次滴答时执行,也就是在快要进行下次呈现时执行。例如,如果使用 Seek 方法跳到动画中的另一点,属性值不会立即更改,而是在计时引擎下次滴答时更改。
下面的示例演示如何使用 Storyboard 类的交互方法来应用和控制动画。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SDKSample
{
public class ControllableStoryboardExample : Page
{
private Storyboard myStoryboard;
public ControllableStoryboardExample()
{
// Create a name scope for the page.
NameScope.SetNameScope(this, new NameScope());
this.WindowTitle = "Controllable Storyboard Example";
StackPanel myStackPanel = new StackPanel();
myStackPanel.Margin = new Thickness(10);
// Create a rectangle.
Rectangle myRectangle = new Rectangle();
myRectangle.Name = "myRectangle";
// Assign the rectangle a name by
// registering it with the page, so that
// it can be targeted by storyboard
// animations.
this.RegisterName(myRectangle.Name, myRectangle);
myRectangle.Width = 100;
myRectangle.Height = 100;
myRectangle.Fill = Brushes.Blue;
myStackPanel.Children.Add(myRectangle);
//
// Create an animation and a storyboard to animate the
// rectangle.
//
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = 1.0;
myDoubleAnimation.To = 0.0;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(5000));
myDoubleAnimation.AutoReverse = true;
// Create the storyboard.
myStoryboard = new Storyboard();
myStoryboard.Children.Add(myDoubleAnimation);
Storyboard.SetTargetName(myDoubleAnimation, myRectangle.Name);
Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Rectangle.OpacityProperty));
//
// Create some buttons to control the storyboard
// and a panel to contain them.
//
StackPanel buttonPanel = new StackPanel();
buttonPanel.Orientation = Orientation.Horizontal;
Button beginButton = new Button();
beginButton.Content = "Begin";
beginButton.Click += new RoutedEventHandler(beginButton_Clicked);
buttonPanel.Children.Add(beginButton);
Button pauseButton = new Button();
pauseButton.Content = "Pause";
pauseButton.Click += new RoutedEventHandler(pauseButton_Clicked);
buttonPanel.Children.Add(pauseButton);
Button resumeButton = new Button();
resumeButton.Content = "Resume";
resumeButton.Click += new RoutedEventHandler(resumeButton_Clicked);
buttonPanel.Children.Add(resumeButton);
Button skipToFillButton = new Button();
skipToFillButton.Content = "Skip to Fill";
skipToFillButton.Click += new RoutedEventHandler(skipToFillButton_Clicked);
buttonPanel.Children.Add(skipToFillButton);
Button setSpeedRatioButton = new Button();
setSpeedRatioButton.Content = "Triple Speed";
setSpeedRatioButton.Click += new RoutedEventHandler(setSpeedRatioButton_Clicked);
buttonPanel.Children.Add(setSpeedRatioButton);
Button stopButton = new Button();
stopButton.Content = "Stop";
stopButton.Click += new RoutedEventHandler(stopButton_Clicked);
buttonPanel.Children.Add(stopButton);
myStackPanel.Children.Add(buttonPanel);
this.Content = myStackPanel;
}
// Begins the storyboard.
private void beginButton_Clicked(object sender, RoutedEventArgs args)
{
// Specifying "true" as the second Begin parameter
// makes this storyboard controllable.
myStoryboard.Begin(this, true);
}
// Pauses the storyboard.
private void pauseButton_Clicked(object sender, RoutedEventArgs args)
{
myStoryboard.Pause(this);
}
// Resumes the storyboard.
private void resumeButton_Clicked(object sender, RoutedEventArgs args)
{
myStoryboard.Resume(this);
}
// Advances the storyboard to its fill period.
private void skipToFillButton_Clicked(object sender, RoutedEventArgs args)
{
myStoryboard.SkipToFill(this);
}
// Updates the storyboard's speed.
private void setSpeedRatioButton_Clicked(object sender, RoutedEventArgs args)
{
// Makes the storyboard progress three times as fast as normal.
myStoryboard.SetSpeedRatio(this, 3);
}
// Stops the storyboard.
private void stopButton_Clicked(object sender, RoutedEventArgs args)
{
myStoryboard.Stop(this);
}
}
}
在样式中进行动画处理
可以使用 Storyboard 对象在 Style 中定义动画。在 Style 中使用 Storyboard 进行动画处理与在别处使用 Storyboard 类似,不过,有以下三个例外:
您不必指定 TargetName;Storyboard 始终以 Style 应用到的元素为目标。若要以 Freezable 为目标,必须使用间接目标。有关间接目标的更多信息,请参见间接目标部分。
您不能指定 EventTrigger 或 Trigger 的 SourceName。
您不能使用动态资源引用或数据绑定表达式来设置 Storyboard 或动画属性值。这是因为 Style 中的任何内容都必须是线程安全的,计时系统必须 Freeze Storyboard 对象以使其变为线程安全。如果 Storyboard 或其子时间线包含动态资源引用或数据绑定表达式,则不能将其冻结。有关冻结和其他 Freezable 功能的更多信息,请参见 Freezable 对象概述。
在 XAML 中,不能为 Storyboard 或动画事件声明事件处理程序。
有关演示如何在样式中定义 Storyboard 的示例,请参见如何:在样式中进行动画处理示例。
在 ControlTemplate 中进行动画处理
可以使用 Storyboard 对象在 ControlTemplate 中定义动画。在 ControlTemplate 中使用 Storyboard 进行动画处理与在别处使用 Storyboard 类似,不过,有以下四个例外:
TargetName 只能引用 ControlTemplate 的子对象。如果未指定 TargetName,则动画以 ControlTemplate 所应用到的元素为目标。
EventTrigger 或 Trigger 的 SourceName 只能引用 ControlTemplate 的子对象。
您不能使用动态资源引用或数据绑定表达式来设置 Storyboard 或动画属性值。这是因为 ControlTemplate 中的任何内容都必须是线程安全的,计时系统必须 Freeze Storyboard 对象以使其变为线程安全。如果 Storyboard 或其子时间线包含动态资源引用或数据绑定表达式,则不能将其冻结。有关冻结和其他 Freezable 功能的更多信息,请参见 Freezable 对象概述。
在 XAML 中,不能为 Storyboard 或动画事件声明事件处理程序。
有关演示如何在 ControlTemplate 中定义 Storyboard 的示例,请参见如何:在 ControlTemplate 中进行动画处理示例。
在属性值更改时进行动画处理
在样式和控件模板中,当属性更改时,可以使用 Trigger 对象来启动 Storyboard。有关示例,请参见 如何:在属性值更改时触发动画 和 如何:在 ControlTemplate 中进行动画处理。
属性 Trigger 对象所应用的动画的行为比 EventTrigger 动画或使用 Storyboard 方法启动的动画的行为更复杂一些。 它们在“提交”时使用的是其他 Trigger 对象定义的动画,但是在编写时使用 EventTrigger 和方法触发的动画。