情节提要概述

本主题演示如何使用 Storyboard 对象来组织和应用动画。 该主题还介绍如何以交互方式操作 Storyboard 对象,并介绍间接属性目标语法。

先决条件

若要了解本主题,应熟悉不同的动画类型及其基本功能。 有关动画的简介,请参阅动画概述。 还应了解如何使用附加属性。 有关附加属性的详细信息,请参阅附加属性概述

什么是情节提要

动画并非唯一有用的时间线类型。 还提供了其他时间线类来帮助组织时间线集,并将时间线应用于属性。 容器时间线派生自 TimelineGroup 类,包含 ParallelTimelineStoryboard

Storyboard 是一种容器时间线,它为其包含的时间线提供目标信息。 情节提要可以包含任意类型的 Timeline,包括其他容器时间线和动画。 可以使用 Storyboard 对象将影响各种对象和属性的时间线组合成一个时间线树,以便于组织和控制复杂的计时行为。 例如,假设需要一个执行以下三个操作的按钮。

  • 当用户选择该按钮时,按钮增大并更改颜色。

  • 当用户单击该按钮时,按钮缩小并恢复其原始大小。

  • 当该按钮变为禁用状态时,按钮缩小且不透明度缩减到 50%。

在此情况下,有多组动画适用于同一对象,并且需要根据按钮的状态在不同的时间播放它们。 可以使用 Storyboard 对象组织动画,并将它们成组地应用于一个或多个对象。

何处可以使用情节提要

可以使用 Storyboard 对可动画处理的类的依赖属性进行动画处理(有关如何使类成为可动画处理的类的详细信息,请参阅动画概述)。 不过,由于情节提要是框架级别的功能,该对象必须属于 FrameworkElementFrameworkContentElementNameScope

例如,你可以使用 Storyboard 执行以下操作:

但是,不能使用 Storyboard 对未向 FrameworkElementFrameworkContentElement 注册其名称或未用于设置 FrameworkElementFrameworkContentElement 的属性的 SolidColorBrush 进行动画处理。

如何使用情节提要应用动画

若要使用 Storyboard 组织和应用动画,可以将动画添加为 Storyboard 的子时间线。 Storyboard 类提供 Storyboard.TargetNameStoryboard.TargetProperty 附加属性。 可以在动画上设置这些属性以指定其目标对象和属性。

若要将动画应用于其目标,可以使用触发器操作或方法来开始 Storyboard。 在 XAML 中,将 BeginStoryboard 对象与 EventTriggerTriggerDataTrigger 一起使用。 在代码中,还可以使用 Begin 方法。

下表显示了支持 Storyboard 开始技术的不同位置:基于实例、样式、控件模板和数据模板。 “基于实例”是指直接将动画或情节提要应用于对象实例(而不是在样式、控件模板或数据模板中应用)的技术。

情节提要开始时使用… 基于实例 样式 控件模板 数据模板 示例
BeginStoryboardEventTrigger 使用情节提要对属性进行动画处理
BeginStoryboardTrigger 属性 在属性值更改时触发动画
BeginStoryboardMultiTrigger 属性 MultiTrigger 类示例
BeginStoryboardDataTrigger 如何:在数据更改时触发动画
BeginStoryboardMultiDataTrigger MultiDataTrigger 类示例
Begin 方法 No No 使用情节提要对属性进行动画处理

下面的示例使用 StoryboardRectangle 元素的 Width 以及用于绘制该 RectangleSolidColorBrushColor 进行动画处理。

<!-- This example shows how to animate with a storyboard.-->
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://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;
        }
    }
}

以下部分更加详细地介绍了 TargetNameTargetProperty 附加属性。

以框架元素、框架内容元素和 Freezable 元素为目标

上一节提到过,对于要查找其目标的动画,必须知道目标的名称和要进行动画处理的属性。 指定要进行动画处理的属性所需使用的方法非常简洁明了:只需使用要进行动画处理的属性的名称来设置 TargetProperty 即可。 通过在动画上设置 Storyboard.TargetName 属性来指定具有要进行动画处理的属性的对象名称。

注意

虽然作为 TargetName 的替代方法,可以使用 Target 属性直接绑定到对象,但它不可序列化。 不能保证可以在 XAML 中正确引用 Target 对象。

为了使 TargetName 属性起作用,目标对象必须具有名称。 在 XAML 中为 FrameworkElementFrameworkContentElement 分配名称不同于为 Freezable 对象分配名称。

框架元素是从 FrameworkElement 类继承的类。 框架元素的示例包括 WindowDockPanelButtonRectangle。 实质上,所有窗口、面板和控件都是元素。 框架内容元素是从 FrameworkContentElement 类继承的类。 框架内容元素的示例包括 FlowDocumentParagraph。 如果不确定某个类型是框架元素还是框架内容元素,请查看它是否具有 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 的示例包括 SolidColorBrushRotateTransformGradientStop

若要在 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 XAML 名称范围。 如果忽略 TargetName 属性,则动画以在其上定义该属性的元素为目标(如果存在样式,则以带样式的元素为目标)。

有时,不能为 Freezable 对象分配名称。 例如,如果将 Freezable 声明为资源,或者将其用于设置样式中的属性值,则不能为该对象分配名称。 由于该对象没有名称,因此不能直接以它为目标,但可以间接以它为目标。 下面几节描述如何使用间接目标。

间接目标

有时动画不能直接以 Freezable 为目标,例如当 Freezable 声明为资源或者用于设置样式中的属性值时。 在这些情况下,虽然不能直接以 Freezable 对象为目标,但仍可以对其进行动画处理。 不要使用 Freezable 的名称设置 TargetName 属性,而是向它提供 Freezable“所属”的元素的名称。例如,用于设置矩形元素的 FillSolidColorBrush 属于该矩形。 若要对该画笔进行动画处理,需要使用属性链来设置动画的 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 并尝试对其 Color 进行动画处理,而实际上使用的是 LinearGradientBrush 来设置按钮的 Background。 在这种情况下,不会引发任何异常;由于 LinearGradientBrush 不响应 Color 属性的更改,因此动画不具有可视效果。

下面几节详细介绍间接属性目标语法。

在 XAML 中间接以 Freezable 的属性为目标

若要在 XAML 中以 Freezable 的属性为目标,请使用以下语法。

属性语法
ElementPropertyName.FreezablePropertyName

其中

以下代码演示如何对用于设置矩形元素 FillSolidColorBrushColor 进行动画处理。

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

下面的代码演示如何对上例中所示的 RotateTransformAngle 属性进行动画处理。

<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 时,指定 PathPathParameters

若要创建 PathParameters,请创建 DependencyProperty 类型的数组,其中包含依赖属性标识符字段的列表。 第一个标识符字段用于使用 Freezable 设置的 FrameworkElementFrameworkContentElement 的属性。 下一个标识符字段表示目标 Freezable 的属性。 将其看作一个将 FreezableFrameworkElement 对象连接起来的属性链。

下面是依赖属性链的一个示例,该属性链以用于设置矩形元素的 FillSolidColorBrushColor 为目标。

DependencyProperty[] propertyChain =
    new DependencyProperty[]
        {Rectangle.FillProperty, SolidColorBrush.ColorProperty};

你还需要指定 PathPath 是一个 String,它告诉 Path 如何解释其 PathParameters。 它使用以下语法。

属性路径语法
(OwnerPropertyArrayIndex).(FreezablePropertyArrayIndex)

Where

以下示例演示前面示例中定义的 PathParameters 所附带的 Path

DependencyProperty[] propertyChain =
    new DependencyProperty[]
        {Rectangle.FillProperty, SolidColorBrush.ColorProperty};
string thePath = "(0).(1)";

下面的示例将上例中的代码进行合并,以便对用于设置矩形元素的 FillSolidColorBrushColor 进行动画处理。


// 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 是对象在其数组或集合中的索引。

若要以 RotateTransformTransformGroup 中的第二个转换)的 Angle 属性为目标,请使用以下 PathPathParameters

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 中包含的 RotateTransformAngle 进行动画处理的完整代码。

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 为目标并将其作为起点

上面几节介绍了如何通过从 FrameworkElementFrameworkContentElement 开始,然后创建连接到 Freezable 子属性的属性链,来间接以 Freezable 为目标。 还可以使用 Freezable 作为起点,然后间接以其 Freezable 子属性之一为目标。 如果使用 Freezable 作为间接目标的起点,则存在一个附加限制:起始 Freezable 以及它与间接设定为目标的子属性之间的每个 Freezable 都不能冻结。

在 XAML 中以交互方式控制情节提要

若要在 Extensible Application Markup Language (XAML) 中启动情节提要,请使用 BeginStoryboard 触发器操作。 BeginStoryboard 将动画分发到要进行动画处理的对象和属性,然后启动情节提要。 (有关此过程的详细信息,请参阅动画和计时系统概述。)如果通过指定 Name 属性为 BeginStoryboard 提供名称,它将成为可控制的情节提要。 然后,可以在情节提要启动后以交互方式对它进行控制。 下面列出了可与事件触发器一起使用来控制情节提要的可控制情节提要操作。

在下面的示例中,使用可控制的情节提要操作来以交互方式控制情节提要。

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://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 在代码中可交互,必须使用情节提要的 Begintrue 使其可以控制。 有关详细信息,请参阅 Begin(FrameworkElement, Boolean) 页。

下面列出了在 Storyboard 启动后可用来对其进行操作的方法:

使用这些方法的优点是不需要创建 TriggerTriggerAction 对象;只需引用要操作的可控制 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);
        }
    }
}

Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Shapes
Imports System.Windows.Media
Imports System.Windows.Media.Animation

Namespace SDKSample

    Public Class ControllableStoryboardExample
        Inherits Page
        Private myStoryboard As Storyboard

        Public Sub New()

            ' Create a name scope for the page.

            NameScope.SetNameScope(Me, New NameScope())

            Me.WindowTitle = "Controllable Storyboard Example"
            Dim myStackPanel As New StackPanel()
            myStackPanel.Margin = New Thickness(10)

            ' Create a rectangle.
            Dim myRectangle As 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.
            Me.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.
            '
            Dim myDoubleAnimation As 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.
            '
            Dim buttonPanel As New StackPanel()
            buttonPanel.Orientation = Orientation.Horizontal
            Dim beginButton As New Button()
            beginButton.Content = "Begin"
            AddHandler beginButton.Click, AddressOf beginButton_Clicked
            buttonPanel.Children.Add(beginButton)
            Dim pauseButton As New Button()
            pauseButton.Content = "Pause"
            AddHandler pauseButton.Click, AddressOf pauseButton_Clicked
            buttonPanel.Children.Add(pauseButton)
            Dim resumeButton As New Button()
            resumeButton.Content = "Resume"
            AddHandler resumeButton.Click, AddressOf resumeButton_Clicked
            buttonPanel.Children.Add(resumeButton)
            Dim skipToFillButton As New Button()
            skipToFillButton.Content = "Skip to Fill"
            AddHandler skipToFillButton.Click, AddressOf skipToFillButton_Clicked
            buttonPanel.Children.Add(skipToFillButton)
            Dim setSpeedRatioButton As New Button()
            setSpeedRatioButton.Content = "Triple Speed"
            AddHandler setSpeedRatioButton.Click, AddressOf setSpeedRatioButton_Clicked
            buttonPanel.Children.Add(setSpeedRatioButton)
            Dim stopButton As New Button()
            stopButton.Content = "Stop"
            AddHandler stopButton.Click, AddressOf stopButton_Clicked
            buttonPanel.Children.Add(stopButton)
            myStackPanel.Children.Add(buttonPanel)
            Me.Content = myStackPanel


        End Sub

        ' Begins the storyboard.
        Private Sub beginButton_Clicked(ByVal sender As Object, ByVal args As RoutedEventArgs)
            ' Specifying "true" as the second Begin parameter
            ' makes this storyboard controllable.
            myStoryboard.Begin(Me, True)

        End Sub

        ' Pauses the storyboard.
        Private Sub pauseButton_Clicked(ByVal sender As Object, ByVal args As RoutedEventArgs)
            myStoryboard.Pause(Me)

        End Sub

        ' Resumes the storyboard.
        Private Sub resumeButton_Clicked(ByVal sender As Object, ByVal args As RoutedEventArgs)
            myStoryboard.Resume(Me)

        End Sub

        ' Advances the storyboard to its fill period.
        Private Sub skipToFillButton_Clicked(ByVal sender As Object, ByVal args As RoutedEventArgs)
            myStoryboard.SkipToFill(Me)

        End Sub

        ' Updates the storyboard's speed.
        Private Sub setSpeedRatioButton_Clicked(ByVal sender As Object, ByVal args As RoutedEventArgs)
            ' Makes the storyboard progress three times as fast as normal.
            myStoryboard.SetSpeedRatio(Me, 3)

        End Sub

        ' Stops the storyboard.
        Private Sub stopButton_Clicked(ByVal sender As Object, ByVal args As RoutedEventArgs)
            myStoryboard.Stop(Me)

        End Sub

    End Class

End Namespace

在样式中设置动画效果

可以使用 Storyboard 对象在 Style 中定义动画。 在 Style 中使用 Storyboard 进行动画处理与在其他情况下使用 Storyboard 类似,但有以下三个例外:

  • 不必指定 TargetNameStoryboard 始终以 Style 应用于的元素为目标。 若要以 Freezable 对象为目标,必须使用间接目标。 有关间接目标的详细信息,请参阅间接目标部分。

  • 不能为 EventTriggerTrigger 指定 SourceName

  • 不能使用动态资源引用或数据绑定表达式来设置 Storyboard 或动画属性值。 这是因为 Style 中的任何内容都必须是线程安全的,计时系统必须 FreezeStoryboard 对象以使其变为线程安全。 如果 Storyboard 或其子时间线包含动态资源引用或数据绑定表达式,则不能将其冻结。 有关冻结和其他 Freezable 功能的详细信息,请参阅 Freezable 对象概述

  • 在 XAML 中,不能为 Storyboard 或动画事件声明事件处理程序。

有关演示如何在样式中定义情节提要的示例,请参阅 在样式中进行动画处理示例。

在 ControlTemplate 中设置动画效果

可以使用 Storyboard 对象在 ControlTemplate 中定义动画。 在 ControlTemplate 中使用 Storyboard 进行动画处理与在其他情况下使用 Storyboard 类似,但有以下两个例外:

有关演示如何在 ControlTemplate 中定义情节提要的示例,请参阅在 ControlTemplate 中进行动画处理示例。

在属性值更改时进行动画处理

在样式和控件模板中,当属性更改时,可以使用 Trigger 对象来启动情节提要。 有关示例,请参阅在属性值更改时触发动画在 ControlTemplate 中进行动画处理

属性 Trigger 对象所应用的动画的行为比 EventTrigger 动画或使用 Storyboard 方法启动的动画的行为更复杂。 它们在“切换”时使用的是其他 Trigger 对象定义的动画,但是在编写时使用 EventTrigger 和方法触发的动画。

另请参阅