Graphics and Multimedia - Animation Overview 프로그래밍/WPF2017. 1. 7. 18:24
Animation Overview
https://msdn.microsoft.com/en-us/library/ms752312(v=vs.110).aspx
Windows Presentation Foundation (WPF) provides a powerful set of graphics and layout features that enable you to create attractive user interfaces and appealing documents. Animation can make an attractive user interface even more spectacular and usable. By just animating a background color or applying an animated Transform, you can create dramatic screen transitions or provide helpful visual cues.
This overview provides an introduction to the WPF animation and timing system. It focuses on the animation of WPF objects by using storyboards.
Introducing Animations
Animation is an illusion that is created by quickly cycling through a series of images, each slightly different from the last. The brain perceives the group of images as a single changing scene. In film, this illusion is created by using cameras that record many photographs, or frames, each second. When the frames are played back by a projector, the audience sees a moving picture.
Animation on a computer is similar. For example, a program that makes a drawing of a rectangle fade out of view might work as follows.
The program creates a timer.
The program checks the timer at set intervals to see how much time has elapsed.
Each time the program checks the timer, it computes the current opacity value for the rectangle based on how much time has elapsed.
The program then updates the rectangle with the new value and redraws it.
Prior to WPF, Microsoft Windows developers had to create and manage their own timing systems or use special custom libraries. WPF includes an efficient timing system that is exposed through managed code and Extensible Application Markup Language (XAML) and that is deeply integrated into the WPF framework. WPF animation makes it easy to animate controls and other graphical objects.
WPF handles all the behind-the-scenes work of managing a timing system and redrawing the screen efficiently. It provides timing classes that enable you to focus on the effects you want to create, instead of the mechanics of achieving those effects. WPF also makes it easy to create your own animations by exposing animation base classes from which your classes can inherit, to produce customized animations. These custom animations gain many of the performance benefits of the standard animation classes.
WPF Property Animation System
If you understand a few important concepts about the timing system, WPF animations can be easier to use. Most important is that, in WPF, you animate objects by applying animation to their individual properties. For example, to make a framework element grow, you animate its Width andHeight properties. To make an object fade from view, you animate its Opacity property.
For a property to have animation capabilities, it must meet the following three requirements:
It must be a dependency property.
It must belong to a class that inherits from DependencyObject and implements the IAnimatable interface.
There must be a compatible animation type available. (If WPF does not provide one, you can create your own. See the Custom Animations Overview.)
WPF contains many objects that have IAnimatable properties. Controls such as Button and TabControl, and also Panel and Shape objects inherit fromDependencyObject. Most of their properties are dependency properties.
You can use animations almost anywhere, which includes in styles and control templates. Animations do not have to be visual; you can animate objects that are not part of the user interface if they meet the criteria that are described in this section.
Example: Make an Element Fade In and Out of View
This example shows how to use a WPF animation to animate the value of a dependency property. It uses a DoubleAnimation, which is a type of animation that generates Double values, to animate the Opacity property of a Rectangle. As a result, the Rectangle fades in and out of view.
The first part of the example creates a Rectangle element. The steps that follow show how to create an animation and apply it to the rectangle'sOpacity property .
The following shows how to create a Rectangle element in a StackPanel in XAML.
<StackPanel Margin="10"> <Rectangle Name="MyRectangle" Width="100" Height="100" Fill="Blue"> </Rectangle> </StackPanel>
The following shows how to create a Rectangle element in a StackPanel in code.
StackPanel myPanel = new StackPanel(); myPanel.Margin = new Thickness(10); Rectangle myRectangle = new Rectangle(); myRectangle.Name = "myRectangle"; this.RegisterName(myRectangle.Name, myRectangle); myRectangle.Width = 100; myRectangle.Height = 100; myRectangle.Fill = Brushes.Blue; myPanel.Children.Add(myRectangle); this.Content = myPanel;
Part 1: Create a DoubleAnimation
One way to make an element fade in and out of view is to animate its Opacity property. Because the Opacity property is of type Double, you need an animation that produces double values. A DoubleAnimation is one such animation . A DoubleAnimation creates a transition between two double values. To specify its starting value, you set its From property. To specify its ending value, you set its To property.
An opacity value of 1.0 makes the object completely opaque, and an opacity value of 0.0 makes it completely invisible. To make the animation transition from 1.0 to 0.0 you set its From property to 1.0 and its To property to 0.0. The following shows how to create aDoubleAnimation in XAML.
<DoubleAnimation From="1.0" To="0.0" />
The following shows how to create a DoubleAnimation in code.
Next, you must specify a Duration. The Duration of an animation specifies how long it takes to go from its starting value to its destination value. The following shows how to set the Duration to five seconds in XAML.
<DoubleAnimation From="1.0" To="0.0" Duration="0:0:5" />
The following shows how to set the Duration to five seconds in code.
The previous code showed an animation that transitions from 1.0 to 0.0, which causes the target element to fade from completely opaque to completely invisible. To make the element fade back into view after it vanishes, set the AutoReverse property of the animation to true. To make the animation repeat indefinitely, set its RepeatBehavior property to Forever.The following shows how to set the AutoReverse andRepeatBehavior properties in XAML.
<DoubleAnimation From="1.0" To="0.0" Duration="0:0:5" AutoReverse="True" RepeatBehavior="Forever"/>
The following shows how to set the AutoReverse and RepeatBehavior properties in code.
Part 2: Create a Storyboard
To apply an animation to an object, you create a Storyboard and use the TargetName and TargetPropertyattached properties to specify the object and property to animate.
Create the Storyboard and add the animation as its child. The following shows how to create the Storyboard in XAML.
<Storyboard> <DoubleAnimation From="1.0" To="0.0" Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever" /> </Storyboard>
To create the Storyboard in code, declare a Storyboard variable at the class level.
Then initialize the Storyboard and add the animation as its child.
The Storyboard has to know where to apply the animation. Use the Storyboard.TargetName attached property to specify the object to animate. The following shows how to set the target name of the DoubleAnimation to MyRectangle in XAML.
<Storyboard> <DoubleAnimation Storyboard.TargetName="MyRectangle" From="1.0" To="0.0" Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever" /> </Storyboard>
The following shows how to set the target name of the DoubleAnimation to MyRectangle in code.
Use the TargetProperty attached property to specify the property to animate. The following shows how the animation is configured to target the Opacity property of the Rectangle in XAML .
<Storyboard> <DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:5" AutoReverse="True" RepeatBehavior="Forever" /> </Storyboard>
The following shows how the animation is configured to target the Opacity property of the Rectangle in code.
For more information about TargetProperty syntax and for additional examples, see the Storyboards Overview.
Part 3 (XAML): Associate the Storyboard with a Trigger
The easiest way to apply and start a Storyboard in XAML is to use an event trigger . This section shows how to associate the Storyboard with a trigger in XAML.
Create a BeginStoryboard object and associate your storyboard with it. A BeginStoryboard is a type of TriggerAction that applies and starts a Storyboard.
<BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:5" AutoReverse="True" RepeatBehavior="Forever" /> </Storyboard> </BeginStoryboard>
Create an EventTrigger and add the BeginStoryboard to its Actions collection. Set the RoutedEvent property of the EventTrigger to therouted event that you want to start the Storyboard. (For more information about routed events, see the Routed Events Overview.)
<!-- Animates the rectangle's opacity. --> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:5" AutoReverse="True" RepeatBehavior="Forever" /> </Storyboard> </BeginStoryboard> </EventTrigger>
Add the EventTrigger to the Triggers collection of the Rectangle.
<Rectangle Name="MyRectangle" Width="100" Height="100" Fill="Blue"> <Rectangle.Triggers> <!-- Animates the rectangle's opacity. --> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:5" AutoReverse="True" RepeatBehavior="Forever" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle>
Part 3 (Code): Associate the Storyboard with an Event Handler
The easiest way to apply and start a Storyboard in code is to use an event handler. This section shows how to associate the Storyboard with an event handler in code.
Complete Example
The following shows how to create a rectangle that fades in and out of view in XAML .
<Window x:Class="WpfApplication1.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"> <Grid> <StackPanel Margin="10"> <Rectangle Name="MyRectangle" Width="100" Height="100" Fill="Blue"> <Rectangle.Triggers> <!-- Animates the rectangle's opacity. --> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:5" AutoReverse="True" RepeatBehavior="Forever" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle> </StackPanel> </Grid> </Window>
The following shows how to create a rectangle that fades in and out of view in code.
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; using System.Windows.Media.Animation; namespace WpfApplication1 { public partial class MainWindow : Window { private Storyboard myStoryboard; public MainWindow() { InitializeComponent(); StackPanel myPanel = new StackPanel(); myPanel.Margin = new Thickness(10); Rectangle myRectangle = new Rectangle(); myRectangle.Name = "myRectangle"; this.RegisterName(myRectangle.Name, myRectangle); myRectangle.Width = 100; myRectangle.Height = 100; myRectangle.Fill = Brushes.Blue; DoubleAnimation myDoubleAnimation = new DoubleAnimation(); myDoubleAnimation.From = 1.0; myDoubleAnimation.To = 0.0; myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(5)); myDoubleAnimation.AutoReverse = true; myDoubleAnimation.RepeatBehavior = RepeatBehavior.Forever; myStoryboard = new Storyboard(); myStoryboard.Children.Add(myDoubleAnimation); Storyboard.SetTargetName(myDoubleAnimation, myRectangle.Name); Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Rectangle.OpacityProperty)); // Use the Loaded event to start the Storyboard. myRectangle.Loaded += new RoutedEventHandler(myRectangleLoaded); myPanel.Children.Add(myRectangle); this.Content = myPanel; } private void myRectangleLoaded(object sender, RoutedEventArgs e) { myStoryboard.Begin(this); } } }
Animation Types
Because animations generate property values, different animation types exist for different property types. To animate a property that takes a Double, such as the Width property of an element, use an animation that produces Double values. To animate a property that takes a Point, use an animation that produces Point values, and so on. Because of the number of different property types, there are several animation classes in theSystem.Windows.Media.Animation namespace. Fortunately, they follow a strict naming convention that makes it easy to differentiate between them:
< Type>Animation
Known as a "From/To/By" or "basic" animation, these animate between a starting and destination value, or by adding an offset value to its starting value.
To specify a starting value, set the From property of the animation.
To specify an ending value, set the To property of the animation.
To specify an offset value, set the By property of the animation.
The examples in this overview use these animations, because they are the simplest to use. From/To/By animations are described in detail in the From/To/By Animations Overview.
< Type>AnimationUsingKeyFrames
Key frame animations are more powerful than From/To/By animations because you can specify any number of target values and even control their interpolation method. Some types can only be animated with key frame animations. Key frame animations are described in detail in theKey-Frame Animations Overview.
< Type>AnimationUsingPath
Path animations enable you to use a geometric path in order to produce animated values.
< Type>AnimationBase
Abstract class that, when you implement it, animates a < Type> value. This class serves as the base class for < Type>Animation and <Type>AnimationUsingKeyFrames classes. You have to deal directly with these classes only if you want to create your own custom animations. Otherwise, use a < Type>Animation or KeyFrame< Type>Animation.
In most cases, you will want to use the < Type>Animation classes, such as DoubleAnimation and ColorAnimation.
The following table shows several common animation types and some properties with which they are used.
Property type | Corresponding basic (From/To/By) animation | Corresponding key frame animation | Corresponding Path Animation | Usage example |
---|---|---|---|---|
None | Animate the Color of a SolidColorBrushor a GradientStop. | |||
Animate the Center position of anEllipseGeometry. | ||||
None | None |
Animations Are Timelines
All the animation types inherit from the Timeline class; therefore, all animations are specialized types of timelines. A Timeline defines a segment of time. You can specify the timing behaviors of a timeline: its Duration, how many times it is repeated, and even how fast time progresses for it.
Because an animation is a Timeline, it also represents a segment of time. An animation also calculates output values as it progresses though its specified segment of time (or Duration). As the animation progresses, or "plays," it updates the property that it is associated with.
Three frequently used timing properties are Duration, AutoReverse, and RepeatBehavior.
The Duration Property
As previously mentioned, a timeline represents a segment of time. The length of that segment is determined by the Duration of the timeline, which is usually specified by using a TimeSpan value. When a timeline reaches the end of its duration, it has completed an iteration.
An animation uses its Duration property to determine its current value. If you do not specify a Duration value for an animation, it uses 1 second, which is the default.
The following syntax shows a simplified version of the Extensible Application Markup Language (XAML) attribute syntax for the Durationproperty.
hours:minutes:seconds |
The following table shows several Duration settings and their resulting values.
Setting | Resulting value |
---|---|
0:0:5.5 | 5.5 seconds. |
0:30:5.5 | 30 minutes and 5.5 seconds. |
1:30:5.5 | 1 hour, 30 minutes, and 5.5 seconds. |
One way to specify a Duration in code is to use the FromSeconds method to create a TimeSpan, then declare a new Duration structure using that TimeSpan.
For more information about Duration values and the complete Extensible Application Markup Language (XAML) syntax, see the Durationstructure.
AutoReverse
The AutoReverse property specifies whether a timeline plays backward after it reaches the end of its Duration. If you set this animation property to true, an animation reverses after it reaches the end of its Duration, playing from its ending value back to its starting value. By default, this property is false.
RepeatBehavior
The RepeatBehavior property specifies how many times a timeline plays. By default, timelines have an iteration count of 1.0, which means they play one time and do not repeat at all.
For more information about these properties and others, see the Timing Behaviors Overview.
Applying an Animation to a Property
The previous sections describe the different types of animations and their timing properties. This section shows how to apply the animation to the property that you want to animate. Storyboard objects provide one way to apply animations to properties. A Storyboard is a container timeline that provides targeting information for the animations it contains.
Targeting Objects and Properties
The Storyboard class provides the TargetName and TargetPropertyattached properties. By setting these properties on an animation, you tell the animation what to animate. However, before an animation can target an object, the object must usually be given a name.
Assigning a name to a FrameworkElement differs from assigning a name to a Freezable object. Most controls and panels are framework elements; however, most purely graphical objects, such as brushes, transforms, and geometries, are freezable objects. If you are not sure whether a type is aFrameworkElement or a Freezable, refer to the Inheritance Hierarchy section of its reference documentation.
To make a FrameworkElement an animation target, you give it a name by setting its Name property. In code, you must also use theRegisterName method to register the element name with the page to which it belongs.
To make a Freezable object an animation target in XAML, you use the x:Name Directive to assign it a name. In code, you just use theRegisterName method to register the object with the page to which it belongs.
The sections that follow provide an example of naming an element in XAML and code. For more detailed information about naming and targeting, see the Storyboards Overview.
Applying and Starting Storyboards
To start a storyboard in XAML, you associate it with an EventTrigger. An EventTrigger is an object that describes what actions to take when a specified event occurs. One of those actions can be a BeginStoryboard action, which you use to start your storyboard. Event triggers are similar in concept to event handlers because they enable you to specify how your application responds to a particular event. Unlike event handlers, event triggers can be fully described in XAML; no other code is required.
To start a Storyboard in code, you can use an EventTrigger or use the Begin method of the Storyboard class.
Interactively Control a Storyboard
The previous example showed how to start a Storyboard when an event occurs. You can also interactively control a Storyboard after it starts: you can pause, resume, stop, advance it to its fill period, seek, and remove the Storyboard. For more information and an example that shows how to interactively control a Storyboard, see the Storyboards Overview.
What Happens After an Animation Ends?
The FillBehavior property specifies how a timeline behaves when it ends. By default, a timeline starts Filling when it ends. An animation that is Fillingholds its final output value.
The DoubleAnimation in the previous example does not end because its RepeatBehavior property is set to Forever. The following example animates a rectangle by using a similar animation. Unlike the previous example, the RepeatBehavior and AutoReverse properties of this animation are left at their default values. Therefore, the animation progresses from 1 to 0 over five seconds and then stops.
<Rectangle Name="MyRectangle" Width="100" Height="100" Fill="Blue"> <Rectangle.Triggers> <!-- Animates the rectangle's opacity. --> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Opacity" From="1.0" To="0" Duration="0:0:5" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle>
DoubleAnimation myDoubleAnimation = new DoubleAnimation(); myDoubleAnimation.From = 1.0; myDoubleAnimation.To = 0.0; myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(5));
Because its FillBehavior was not changed from its default value, which is HoldEnd, the animation holds it final value, 0, when it ends. Therefore, theOpacity of the rectangle remains at 0 after the animation ends. If you set the Opacity of the rectangle to another value, your code appears to have no effect, because the animation is still affecting the Opacity property.
One way to regain control of an animated property in code is to use the BeginAnimation method and specify null for the AnimationTimelineparameter. For more information and an example, see How to: Set a Property After Animating It with a Storyboard.
Note that, although setting a property value that has an Active or Filling animation appears to have no effect, the property value does change. For more information, see the Animation and Timing System Overview.
Data Binding and Animating Animations
Most animation properties can be data bound or animated; for example, you can animate the Duration property of a DoubleAnimation. However, because of the way the timing system works, data bound or animated animations do not behave like other data bound or animated objects. To understand their behavior, it helps to understand what it means to apply an animation to a property.
Refer to the example in the previous section that showed how to animate the Opacity of a rectangle. When the rectangle in the previous example is loaded, its event trigger applies the Storyboard. The timing system creates a copy of the Storyboard and its animation. These copies are frozen (made read-only) and Clock objects are created from them. These clocks do the actual work of animating the targeted properties.
The timing system creates a clock for the DoubleAnimation and applies it to the object and property that is specified by the TargetName andTargetProperty of the DoubleAnimation. In this case, the timing system applies the clock to the Opacity property of the object that is named "MyRectangle."
Although a clock is also created for the Storyboard, the clock is not applied to any properties. Its purpose is to control its child clock, the clock that is created for the DoubleAnimation.
For an animation to reflect data binding or animation changes, its clock must be regenerated. Clocks are not regenerated for you automatically. To make an animation reflect changes, reapply its storyboard by using a BeginStoryboard or the Begin method. When you use either of these methods, the animation restarts. In code, you can use the Seek method to shift the storyboard back to its previous position.
For an example of a data bound animation, see Key Spline Animation Sample. For more information about how the animation and timing system works, see Animation and Timing System Overview.
Other Ways to Animate
The examples in this overview show how to animate by using storyboards. When you use code, you can animate in several other ways. For more information, see the Property Animation Techniques Overview.
Animation Samples
The following samples can help you start adding animation to your applications.
From, To, and By Animation Target Values Sample
Demonstrates different From/To/By settings.
Animation Timing Behavior Sample
Demonstrates the different ways you can control the timing behavior of an animation. This sample also shows how to data bind the destination value of an animation.
Related Topics
Title | Description |
---|---|
Describes how the timing system uses the Timeline and Clock classes, which allow you to create animations. | |
Lists helpful tips for solving issues with animations, such as performance. | |
Describes how to extend the animation system with key frames, animation classes, or per-frame callbacks. | |
Describes how to create an animation that transitions between two values. | |
Describes how to create an animation with multiple target values, including the ability to control the interpolation method. | |
Explains how to apply mathematical formulas to your animations to get realistic behavior, such as bouncing. | |
Describes how to move or rotate an object along a complex path. | |
Describes property animations using storyboards, local animations, clocks, and per-frame animations. | |
Describes how to use storyboards with multiple timelines to create complex animations. | |
Describes the Timeline types and properties used in animations. | |
Describes the events available on the Timeline and Clock objects for executing code at points in the timeline, such as begin, pause, resume, skip, or stop. | |
Contains code examples for using animations and timelines in your application. | |
Contains code examples for using the Clock object in your application. | |
Contains code examples for using key-frame animations in your application. | |
Contains code examples for using path animations in your application. |
Animation and Timing System Overview
This topic describes how the timing system uses the animation, Timeline, and Clock classes to animate properties.
Prerequisites
To understand this topic, you should be able to use WPF animations to animate properties, as described in the Animation Overview. It also helps to be familiar with dependency properties; for more information, see the Dependency Properties Overview.
Timelines and Clocks
The Animation Overview described how a Timeline represents a segment of time, and an animation is a type of Timeline that produces output values. By itself, a Timeline, doesn't do anything other than just describe a segment of time. It's the timeline's Clock object that does the real work. Likewise, animation doesn't actually animate properties: an animation class describes how output values should be calculated, but it’s the Clock that was created for the animation that drives the animation output and applies it to properties.
A Clock is a special type of object that maintains timing-related run-time state for the Timeline. It provides three bits of information that are essential to the animation and timing system: CurrentTime, CurrentProgress, and CurrentState. A Clock determines its current time, progress, and state by using the timing behaviors described by its Timeline: Duration, RepeatBehavior, AutoReverse, and so on.
In most cases, a Clock is created automatically for your timeline. When you animate by using a Storyboard or the BeginAnimation method, clocks are automatically created for your timelines and animations and applied to their targeted properties. You can also create a Clock explicitly by using the CreateClock method of your Timeline. The MediaTimeline.CreateClock method creates a clock of the appropriate type for the Timeline on which it is called. If the Timeline contains child timelines, it creates Clock objects for them as well. The resulting Clock objects are arranged in trees that match the structure of the Timeline objects tree from which they are created.
There are different types of clocks for different types of timelines. The following table shows the Clock types that correspond to some of the different Timeline types.
Timeline type | Clock type | Clock purpose |
---|---|---|
Animation (inherits from AnimationTimeline) | Generates output values for a dependency property. | |
Processes a media file. | ||
Groups and controls its child Clock objects | ||
Groups and controls its child Clock objects |
You can apply any AnimationClock objects you create to compatible dependency properties by using the ApplyAnimationClock method.
In performance-intensive scenarios, such as animating large numbers of similar objects, managing your own Clock use can provide performance benefits.
Clocks and the Time Manager
When you animate objects in WPF, it’s the time manager that manages the Clock objects created for your timelines. The time manager is the root of a tree of Clock objects and controls the flow of time in that tree. A time manager is automatically created for each WPF application and is invisible to the application developer. The time manager "ticks" many times per second; the actual number of ticks that occur each second varies depending on available system resources. During each one of these ticks, the time manager computes the state of all Active Clock objects in the timing tree.
The following illustration shows the relationship between the time manager, and AnimationClock, and an animated dependency property.
When the time manager ticks, it updates the time of every Active Clock in the application. If the Clock is an AnimationClock, it uses the GetCurrentValue method of the AnimationTimeline from which it was created to calculate its current output value. The AnimationClock supplies the AnimationTimeline with the current local time, an input value, which is typically the base value of the property, and a default destination value. When you retrieve the value of an animated by property using the GetValue method or its CLR accessor, you get the output of its AnimationClock.
Clock Groups
The preceding section described how there are different types of Clock objects for different types of timelines. The following illustration shows the relationship between the time manager, a ClockGroup, an AnimationClock, and an animated dependency property. A ClockGroup is created for timelines that group other timelines, such as the Storyboard class, which groups animations and other timelines.
Composition
It's possible to associate multiple clocks with a single property, in which case each clock uses the output value of the preceding clock as its base value. The following illustration shows three AnimationClock objects applied to the same property. Clock1 uses the base value of the animated property as its input and uses it to generate output. Clock2 takes the output from Clock1 as its input and uses it to generate output. Clock3 takes the output from Clock2 as its input and uses it to generate output. When multiple clocks affect the same property simultaneously, they are said to be in a composition chain.
Note that although a relationship is created among the input and output of the AnimationClock objects in the composition chain, their timing behaviors are not affected; Clock objects (including AnimationClock objects) have a hierarchical dependency on their parent Clock objects.
To apply multiple clocks to the same property, use the Compose HandoffBehavior when applying a Storyboard, animation, or AnimationClock.
Ticks and Event Consolidation
In addition to calculating output values, the time manager does other work every time it ticks: it determines the state of each clock and raises events as appropriate.
While ticks occur frequently, it's possible for a lot of things to happen between ticks. For example, a Clock might be stopped, started, and stopped again, in which case its CurrentState value will have changed three times. In theory, the CurrentStateInvalidated event could be raised multiple times in a single tick; however, the timing engine consolidates events, so that the CurrentStateInvalidated event can be raised at most once per tick. This is true for all timing events: at most one event of each type is raised for a given Clock object.
When a Clock switches states and returns back to its original state between ticks (such as changing from Active to Stopped and back to Active), the associated event still occurs.
For more information about timing events, see the Timing Events Overview.
Current Values and Base Values of Properties
An animatable property can have two values: a base value and a current value. When you set property using its CLR accessor or the SetValue method, you set its base value. When a property is not animated, its base and current values are the same.
When you animate a property, the AnimationClock sets the property's current value. Retrieving the property's value through its CLR accessor or the GetValue method returns the output of the AnimationClock when the AnimationClock is Active or Filling. You can retrieve the property's base value by using the GetAnimationBaseValue method.
Animation Tips and Tricks
When working with animations in WPF, there are a number of tips and tricks that can make your animations perform better and save you frustration.
General Issues
Animating the Position of a Scroll Bar or Slider Freezes It
If you animate the position of a scroll bar or slider using an animation that has a FillBehavior of HoldEnd (the default value), the user will no longer be able to move the scroll bar or slider. That's because, even though the animation ended, it's still overriding the target property's base value. To stop the animation from overriding the property's current value, remove it, or give it a FillBehavior of Stop. For more information and an example, see How to: Set a Property After Animating It with a Storyboard.
Animating the Output of an Animation Has No Effect
You can't animate an object that is the output of another animation. For example, if you use an ObjectAnimationUsingKeyFrames to animate theFill of a Rectangle from a RadialGradientBrush to a SolidColorBrush, you can't animate any properties of the RadialGradientBrush orSolidColorBrush.
Can't Change the Value of a Property after Animating it
In some cases, it might appear that you can't change the value of a property after it's been animated, even after the animation has ended. That's because, even though the animation ended, it's still overriding the property's base value. To stop the animation from overriding the property's current value, remove it, or give it a FillBehavior of Stop. For more information and an example, see How to: Set a Property After Animating It with a Storyboard.
Changing a Timeline Has No Effect
Although most Timeline properties are animatable and can be data bound, changing the property values of an active Timeline seems to have no effect. That's because, when a Timeline is begun, the timing system makes a copy of the Timeline and uses it to create a Clock object. Modifying the original has no effect on the system's copy.
For a Timeline to reflect changes, its clock must be regenerated and used to replace the previously created clock. Clocks are not regenerated for you automatically. The following are several ways to apply timeline changes:
If the timeline is or belongs to a Storyboard, you can make it reflect changes by reapplying its storyboard using a BeginStoryboard or theBegin method. This has the side effect of also restarting the animation. In code, you can use the Seek method to advance the storyboard back to its previous position.
If you applied an animation directly to a property using the BeginAnimation method, call the BeginAnimation method again and pass it the animation that has been modified.
If you are working directly at the clock level, create and apply a new set of clocks and use them to replace the previous set of generated clocks.
For more information about timelines and clocks, see Animation and Timing System Overview.
FillBehavior.Stop Doesn't Work as Expected
There are times when setting the FillBehavior property to Stop seems to have no effect, such as when one animation "hands off" to another because it has a HandoffBehavior setting of SnapshotAndReplace.
The following example creates a Canvas, a Rectangle and a TranslateTransform. The TranslateTransform will be animated to move the Rectanglearound the Canvas.
<Canvas Width="600" Height="200"> <Rectangle Canvas.Top="50" Canvas.Left="0" Width="50" Height="50" Fill="Red"> <Rectangle.RenderTransform> <TranslateTransform x:Name="MyTranslateTransform" X="0" Y="0" /> </Rectangle.RenderTransform> </Rectangle> </Canvas>
The examples in this section use the preceding objects to demonstrate several cases where the FillBehavior property doesn't behave as you might expect it to.
FillBehavior="Stop" and HandoffBehavior with Multiple Animations
Sometimes it seems as though an animation ignores its FillBehavior property when it is replaced by a second animation. Take the following example, which creates two Storyboard objects and uses them to animate the same TranslateTransform shown in the preceding example.
The first Storyboard, B1, animates the X property of the TranslateTransform from 0 to 350, which moves the rectangle 350 pixels to the right. When the animation reaches the end of its duration and stops playing, the X property reverts to its original value, 0. As a result, the rectangle moves to the right 350 pixels and then jumps back to its original position.
<Button Content="Start Storyboard B1"> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard x:Name="B1"> <DoubleAnimation Storyboard.TargetName="MyTranslateTransform" Storyboard.TargetProperty="X" From="0" To="350" Duration="0:0:5" FillBehavior="Stop" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button>
The second Storyboard, B2, also animates the X property of the same TranslateTransform. Because only the To property of the animation in thisStoryboard is set, the animation uses the current value of the property it animates as its starting value.
<!-- Animates the same object and property as the preceding Storyboard. --> <Button Content="Start Storyboard B2"> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard x:Name="B2"> <DoubleAnimation Storyboard.TargetName="MyTranslateTransform" Storyboard.TargetProperty="X" To="500" Duration="0:0:5" FillBehavior="Stop" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button>
If you click the second button while the first Storyboard is playing, you might expect the following behavior:
The first storyboard ends and sends the rectangle back to its original position, because the animation has a FillBehavior of Stop.
The second storyboard takes effect and animates from the current position, which is now 0, to 500.
But that's not what happens. Instead, the rectangle does not jump back; it continues moving to the right. That's because the second animation uses the current value of the first animation as its starting value and animates from that value to 500. When the second animation replaces the first because the SnapshotAndReplaceHandoffBehavior is used, the FillBehavior of the first animation does not matter.
FillBehavior and the Completed Event
The next examples demonstrate another scenario in which the StopFillBehavior seems to have no effect. Again, the example uses a Storyboard to animate the X property of the TranslateTransform from 0 to 350. However, this time the example registers for the Completed event.
<Button Content="Start Storyboard C"> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard Completed="StoryboardC_Completed"> <DoubleAnimation Storyboard.TargetName="MyTranslateTransform" Storyboard.TargetProperty="X" From="0" To="350" Duration="0:0:5" FillBehavior="Stop" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button>
The Completed event handler starts another Storyboard that animates the same property from its current value to 500.
private void StoryboardC_Completed(object sender, EventArgs e) { Storyboard translationAnimationStoryboard = (Storyboard)this.Resources["TranslationAnimationStoryboardResource"]; translationAnimationStoryboard.Begin(this); }
The following is the markup that defines the second Storyboard as a resource.
<Page.Resources> <Storyboard x:Key="TranslationAnimationStoryboardResource"> <DoubleAnimation Storyboard.TargetName="MyTranslateTransform" Storyboard.TargetProperty="X" To="500" Duration="0:0:5" /> </Storyboard> </Page.Resources>
When you run the Storyboard, you might expect the X property of the TranslateTransform to animate from 0 to 350, then revert to 0 after it completes (because it has a FillBehavior setting of Stop), and then animate from 0 to 500. Instead, the TranslateTransform animates from 0 to 350 and then to 500.
That's because of the order in which WPF raises events and because property values are cached and are not recalculated unless the property is invalidated. The Completed event is processed first because it was triggered by the root timeline (the first Storyboard). At this time, the Xproperty still returns its animated value because it hasn't been invalidated yet. The second Storyboard uses the cached value as its starting value and begins animating.
Performance
Animations Continue to Run After Navigating Away from a Page
When you navigate away from a Page that contains running animations, those animations will continue to play until the Page is garbage collected. Depending on the navigation system you're using, a page that you navigate away from might stay in memory for an indefinite amount of time, all the while consuming resources with its animations. This is most noticeable when a page contains constantly running ("ambient") animations.
For this reason, it's a good idea to use the Unloaded event to remove animations when you navigate away from a page.
There are different ways to remove an animation. The following techniques can be used to remove animations that belong to a Storyboard.
To remove a Storyboard you started with an event trigger, see How to: Remove a Storyboard.
To use code to remove a Storyboard, see the Remove method.
The next technique may be used regardless of how the animation was started.
To remove animations from a specific property, use the BeginAnimation(DependencyProperty, AnimationTimeline) method. Specify the property being animated as the first parameter, and null as the second. This will remove all animation clocks from the property.
For more information about the different ways to animate properties, see Property Animation Techniques Overview.
Using the Compose HandoffBehavior Consumes System Resources
When you apply a Storyboard, AnimationTimeline, or AnimationClock to a property using the ComposeHandoffBehavior, any Clock objects previously associated with that property continue to consume system resources; the timing system will not remove these clocks automatically.
To avoid performance issues when you apply a large number of clocks using Compose, you should remove composing clocks from the animated property after they complete. There are several ways to remove a clock.
To remove all clocks from a property, use the ApplyAnimationClock(DependencyProperty, AnimationClock) orBeginAnimation(DependencyProperty, AnimationTimeline) method of the animated object. Specify the property being animated as the first parameter, and null as the second. This will remove all animation clocks from the property.
To remove a specific AnimationClock from a list of clocks, use the Controller property of the AnimationClock to retrieve a ClockController, then call the Remove method of the ClockController. This is typically done in the Completed event handler for a clock. Note that only root clocks can be controlled by a ClockController; the Controller property of a child clock will return null. Note also that the Completed event will not be called if the effective duration of the clock is forever. In that case, the user will need to determine when to call Remove.
This is primarily an issue for animations on objects that have a long lifetime. When an object is garbage collected, its clocks will also be disconnected and garbage collected.
For more information about clock objects, see Animation and Timing System Overview.
Custom Animations Overview
This topic describes how and when to extend the WPF animation system by creating custom key frames, animation classes, or by using per-frame callback to bypass it.
Prerequisites
To understand this topic, you should be familiar with the different types of animations provided by the WPF. For more information, see the From/To/By Animations Overview, the Key-Frame Animations Overview, and the Path Animations Overview.
Because the animation classes inherit from the Freezable class, you should be familiar with Freezable objects and how to inherit from Freezable. For more information, see the Freezable Objects Overview.
Extending the Animation System
There are a number of ways to extend the WPF animation system, depending on the level of built-in functionality you want to use. There are three primary extensibility points in the WPF animation engine:
Create a custom key frame object by inheriting from one of the <Type>KeyFrame classes, such as DoubleKeyFrame. This approach uses most of the built-in functionality of the WPF animation engine.
Create your own animation class by inheriting from AnimationTimeline or one of the <Type>AnimationBase classes.
Use per-frame callback to generate animations on a per-frame basis. This approach completely bypasses the animation and timing system.
The following table describes some the scenarios for extending the animation system.
When you want to... | Use this approach |
---|---|
Customize the interpolation between values of a type that has a corresponding <Type>AnimationUsingKeyFrames | Create a custom key frame. For more information, see the Create a Custom Key Frame section. |
Customize more than just the interpolation between values of a type that has a corresponding <Type>Animation. | Create a custom animation class that inherits from the <Type>AnimationBase class that corresponds to the type you want to animate. For more information, see the Create a Custom Animation Class section. |
Animate a type that has no corresponding WPF animation | Use an ObjectAnimationUsingKeyFrames or create a class that inherits from AnimationTimeline. For more information, see the Create a Custom Animation Class section. |
Animate multiple objects with values that are computed each frame and are based on the last set of object interactions | Use per-frame callback. For more information, see the Create a Use Per-Frame Callback section. |
Create a Custom Key Frame
Creating a custom key frame class is the simplest way to extend the animation system. Use this approach when you want to a different interpolation method for a key-frame animation. As described in the Key-Frame Animations Overview, a key-frame animation uses key frame objects to generate its output values. Each key frame object performs three functions:
Specifies a target value using its Value property.
Specifies the time at which that value should be reached using its KeyTime property.
Interpolates between the value of the previous key frame and its own value by implementing the InterpolateValueCore method.
Implementation Instructions
Derive from the <Type>KeyFrame abstract class and implement the InterpolateValueCore method. The InterpolateValueCore method returns the current value of the key frame. It takes two parameters: the value of the previous key frame and a progress value that ranges from 0 to 1. A progress of 0 indicates the key frame has just started, and a value of 1 indicates that the key frame has just completed and should return the value specified by its Value property.
Because the <Type>KeyFrame classes inherit from the Freezable class, you must also override CreateInstanceCore core to return a new instance of your class. If the class does not use dependency properties to store its data or it requires extra initialization after creation, you might need to override additional methods; see the Freezable Objects Overview for more information.
After you've created your custom <Type>KeyFrame animation, you can use it with the <Type>AnimationUsingKeyFrames for that type.
Create a Custom Animation Class
Creating your own animation type gives you more control over how an object in animated. There are two recommended ways to create your own animation type: you can derive from the AnimationTimeline class or the <Type>AnimationBase class. Deriving from the <Type>Animation or <Type>AnimationUsingKeyFrames classes is not recommended.
Derive from <Type>AnimationBase
Deriving from a <Type>AnimationBase class is the simplest way to create a new animation type. Use this approach when you want to create a new animation for type that already has a corresponding <Type>AnimationBase class.
Implementation Instructions
Derive from a <Type>Animation class and implement the GetCurrentValueCore method. The GetCurrentValueCore method returns the current value of the animation. It takes three parameters: a suggested starting value, a suggested ending value, and an AnimationClock, which you use to determine the progress of the animation.
Because the <Type>AnimationBase classes inherit from the Freezable class, you must also override CreateInstanceCore core to return a new instance of your class. If the class does not use dependency properties to store its data or it requires extra initialization after creation, you might need to override additional methods; see the Freezable Objects Overview for more information.
For more information, see the GetCurrentValueCore method documentation for the <Type>AnimationBase class for the type that you want to animate. For an example, see the Custom Animation Sample
Alternative Approaches
If you simply want to change how animation values are interpolated, considering deriving from one of the <Type>KeyFrame classes. The key frame you create can be used with the corresponding <Type>AnimationUsingKeyFrames provided by WPF.
Derive from AnimationTimeline
Derive from the AnimationTimeline class when you want to create an animation for a type that doesn't already have a matching WPF animation, or you want to create an animation that is not strongly typed.
Implementation Instructions
Derive from the AnimationTimeline class and override the following members:
CreateInstanceCore – If your new class is concrete, you must override CreateInstanceCore to return a new instance of your class.
GetCurrentValue – Override this method to return the current value of your animation. It takes three parameters: a default origin value, a default destination value, and an AnimationClock. Use the AnimationClock to obtain the current time or progress for the animation. You can choose whether to use the default origin and destination values.
IsDestinationDefault – Override this property to indicate whether your animation uses the default destination value specified by the GetCurrentValue method.
TargetPropertyType – Override this property to indicate the Type of output your animation produces.
If the class does not use dependency properties to store its data or it requires extra initialization after creation, you might need to override additional methods; see the Freezable Objects Overview for more information.
The recommended paradigm (used by WPF animations) is to use two inheritance levels:
Create an abstract <Type>AnimationBase class that derives from AnimationTimeline. This class should override the TargetPropertyType method. It should also introduce a new abstract method, GetCurrentValueCore, and override GetCurrentValue so that it validates the types of the default origin value and default destination value parameters, then calls GetCurrentValueCore.
Create another class that inherits from your new <Type>AnimationBase class and overrides the CreateInstanceCore method, the GetCurrentValueCore method that you introduced, and the IsDestinationDefault property.
Alternative Approaches
If you want to animate a type that has no corresponding From/To/By animation or key-frame animation, consider using an ObjectAnimationUsingKeyFrames. Because it is weakly typed, an ObjectAnimationUsingKeyFrames can animate any type of value. The drawback to this approach is that ObjectAnimationUsingKeyFrames only supports discrete interpolation.
Use Per-Frame Callback
Use this approach when you need to completely bypass the WPF animation system. One scenario for this approach is physics animations, where at each animation step a new direction or position of animated objects needs to be recomputed based on the last set of object interactions.
Implementation Instructions
Unlike the other approaches described in this overview, to use per-frame callback you don't need to create a custom animation or key frame class.
Instead, you register for the Rendering event of the object that contains the objects you want to animate. This event handler method gets called once per frame. Each time that WPF marshals the persisted rendering data in the visual tree across to the composition tree, your event handler method is called.
In your event handler, perform your whatever calculations necessary for your animation effect and set the properties of the objects you want to animate with these values.
To obtain the presentation time of the current frame, the EventArgs associated with this event can be cast as RenderingEventArgs, which provide a RenderingTime property that you can use to obtain the current frame's rendering time.
For more information, see the Rendering page.
From/To/By Animations Overview
This topic describes how to use From/To/By animations to animate dependency properties. A From/To/By animation creates a transition between two values.
This topic contains the following sections.
Prerequisites
To understand this topic, you should be familiar with WPF animations features. For an introduction to animation features, see the Animation Overview.
What Is a From/To/By Animation?
A From/To/By animation is a type of AnimationTimeline that creates a transition between a starting value and an ending value. The amount of time that the transition takes to complete is determined by the Duration of that animation.
You can apply a From/To/By animation to a property by using a Storyboard in markup and code, or by using the BeginAnimation method in code. You may also use a From/To/By Animation to create an AnimationClock and apply it to one or more properties. For more information about the different methods for applying animations, see the Property Animation Techniques Overview.
From/To/By animations can have no more than two target values. If you require an animation that has more than two target values, use a key-frame animation. Key-frame animations are described in the Key-Frame Animations Overview.
From/To/By Animation Types
Because animations generate property values, there are different animation types for different property types. To animate a property that takes aDouble, such as the Width property of an element, use an animation that produces Double values. To animate a property that takes a Point, use an animation that produces Point values, and so on.
From/To/By animation classes belong to the System.Windows.Media.Animation namespace and use the following naming convention:
<Type>Animation
Where <Type> is the type of value that the class animates.
WPF provides the following From/To/By animation classes.
Property type | Corresponding From/To/By animation class |
---|---|
Target Values
A From/To/By animation creates a transition between two target values. It is common to specify a starting value (set it by using the From property) and an ending value (set it by using the To property). However, you can also specify only a starting value, a destination value, or an offset value. In these cases, the animation obtains the missing target value from the property that is being animated. The following list describes the different ways to specify the target values of an animation.
Starting Value
Use the From property when you want to explicitly specify the starting value of an animation. You can use the From property by itself, or with the To or By property. If you specify only the From property, the animation transitions from that value to the base value of the animated property.
Ending Value
To specify an ending value of an animation, use its To property. If you use the To property by itself, the animation obtains its starting value from the property that is being animated or from the output of another animation that is applied to the same property. You can use the Toproperty together with the From property to explicitly specify starting and ending values for the animation.
Offset Value
The By property enables you to specify an offset instead of an explicit starting or ending value for the animation. The By property of an animation specifies by how much the animation changes a value over its duration. You can use the By property by itself or with the Fromproperty. If you specify only the By property, the animation adds the offset value to the base value of the property or to the output of another animation.
Using From/To/By Values
The following sections describe how to use the From, To, and By properties together or separately.
The examples in this section each use a DoubleAnimation, which is a type of From/To/By animation, to animate the Width property of a Rectangle that is 10 device independent pixels high and 100 device independent pixels wide.
Although each example uses a DoubleAnimation, the From, To, and By properties of all From/To/By animations behave identically. Although each of these examples uses a Storyboard, you can use From/To/By animations in other ways. For more information, see Property Animation Techniques Overview.
From/To
When you set the From and To values together, the animation progresses from the value that is specified by the From property, to the value that is specified by the To property.
The following example sets the From property of the DoubleAnimation to 50 and its To property to 300. As a result, the Width of the Rectangle is animated from 50 to 300.
// Demonstrates the From and To properties used together. // Create a NameScope for this page so that // Storyboards can be used. NameScope.SetNameScope(this, new NameScope()); Rectangle myRectangle = new Rectangle(); // Assign the Rectangle a name so that // it can be targeted by a Storyboard. this.RegisterName( "fromToAnimatedRectangle", myRectangle); myRectangle.Height = 10; myRectangle.Width = 100; myRectangle.HorizontalAlignment = HorizontalAlignment.Left; myRectangle.Fill = Brushes.Black; // Demonstrates the From and To properties used together. // Animates the rectangle's Width property from 50 to 300 over 10 seconds. DoubleAnimation myDoubleAnimation = new DoubleAnimation(); myDoubleAnimation.From = 50; myDoubleAnimation.To = 300; myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(10)); Storyboard.SetTargetName(myDoubleAnimation, "fromToAnimatedRectangle"); Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Rectangle.WidthProperty)); Storyboard myStoryboard = new Storyboard(); myStoryboard.Children.Add(myDoubleAnimation); // Use an anonymous event handler to begin the animation // when the rectangle is clicked. myRectangle.MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs args) { myStoryboard.Begin(myRectangle); };
To
When you set just the To property, the animation progresses from the base value of the animated property, or from the output of a composing animation that was previously applied to the same property, to the value that is specified by the To property.
("Composing animation" refers to an Active or Filling animation that previously applied to the same property that is still in effect when the current animation was applied by using the Compose handoff behavior.)
The following example sets just the To property of the DoubleAnimation to 300. Because no starting value was specified, the DoubleAnimationuses the base value (100) of the Width property as its starting value. The Width of the Rectangle is animated from 100 to the animation's target value of 300.
// Demonstrates the use of the To property. // Create a NameScope for this page so that // Storyboards can be used. NameScope.SetNameScope(this, new NameScope()); Rectangle myRectangle = new Rectangle(); // Assign the Rectangle a name so that // it can be targeted by a Storyboard. this.RegisterName( "toAnimatedRectangle", myRectangle); myRectangle.Height = 10; myRectangle.Width = 100; myRectangle.HorizontalAlignment = HorizontalAlignment.Left; myRectangle.Fill = Brushes.Gray; // Demonstrates the To property used by itself. Animates // the Rectangle's Width property from its base value // (100) to 300 over 10 seconds. DoubleAnimation myDoubleAnimation = new DoubleAnimation(); myDoubleAnimation.To = 300; myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(10)); Storyboard.SetTargetName(myDoubleAnimation, "toAnimatedRectangle"); Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Rectangle.WidthProperty)); Storyboard myStoryboard = new Storyboard(); myStoryboard.Children.Add(myDoubleAnimation); // Use an anonymous event handler to begin the animation // when the rectangle is clicked. myRectangle.MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs args) { myStoryboard.Begin(myRectangle); };
By
When you set just the By property of an animation, the animation progresses from the base value of the property that is being animated, or from the output of a composing animation to the sum of that value and the value that is specified by the By property.
The following example sets just the By property of the DoubleAnimation to 300. Because the example does not specify a starting value, theDoubleAnimation uses the base value of the Width property, 100, as its starting value. The ending value is determined by adding the By value of the animation, 300, to its starting value, 100: 400. As a result, the Width of the Rectangle is animated from 100 to 400.
// Demonstrates the use of the By property. // Create a NameScope for this page so that // Storyboards can be used. NameScope.SetNameScope(this, new NameScope()); Rectangle myRectangle = new Rectangle(); // Assign the Rectangle a name so that // it can be targeted by a Storyboard. this.RegisterName( "byAnimatedRectangle", myRectangle); myRectangle.Height = 10; myRectangle.Width = 100; myRectangle.HorizontalAlignment = HorizontalAlignment.Left; myRectangle.Fill = Brushes.RoyalBlue; // Demonstrates the By property used by itself. // Increments the Rectangle's Width property by 300 over 10 seconds. // As a result, the Width property is animated from its base value // (100) to 400 (100 + 300) over 10 seconds. DoubleAnimation myDoubleAnimation = new DoubleAnimation(); myDoubleAnimation.By = 300; myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(10)); Storyboard.SetTargetName(myDoubleAnimation, "byAnimatedRectangle"); Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Rectangle.WidthProperty)); Storyboard myStoryboard = new Storyboard(); myStoryboard.Children.Add(myDoubleAnimation); // Use an anonymous event handler to begin the animation // when the rectangle is clicked. myRectangle.MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs args) { myStoryboard.Begin(myRectangle); };
From/By
When you set the From and By properties of an animation, the animation progresses from the value that is specified by the From property, to the value that is specified by the sum of the From and By properties.
The following example sets the From property of the DoubleAnimation to 50 and its By property to 300. The ending value is determined by adding the By value of the animation, 300, to its starting value, 50: 350. As a result, the Width of the Rectangle is animated from 50 to 350.
// Demonstrates the use of the From and By properties. // Create a NameScope for this page so that // Storyboards can be used. NameScope.SetNameScope(this, new NameScope()); Rectangle myRectangle = new Rectangle(); // Assign the Rectangle a name so that // it can be targeted by a Storyboard. this.RegisterName( "byAnimatedRectangle", myRectangle); myRectangle.Height = 10; myRectangle.Width = 100; myRectangle.HorizontalAlignment = HorizontalAlignment.Left; myRectangle.Fill = Brushes.BlueViolet; // Demonstrates the From and By properties used together. // Increments the Rectangle's Width property by 300 over 10 seconds. // As a result, the Width property is animated from 50 // to 350 (50 + 300) over 10 seconds. DoubleAnimation myDoubleAnimation = new DoubleAnimation(); myDoubleAnimation.From = 50; myDoubleAnimation.By = 300; myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(10)); Storyboard.SetTargetName(myDoubleAnimation, "byAnimatedRectangle"); Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Rectangle.WidthProperty)); Storyboard myStoryboard = new Storyboard(); myStoryboard.Children.Add(myDoubleAnimation); // Use an anonymous event handler to begin the animation // when the rectangle is clicked. myRectangle.MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs args) { myStoryboard.Begin(myRectangle); };
From
When you specify just the From value of an animation, the animation progresses from the value that is specified by the From property, to the base value of the property that is being animated or to the output of a composing animation.
The following example sets just the From property of the DoubleAnimation to 50. Because no ending value was specified, the DoubleAnimationuses the base value of the Width property, 100, as its ending value. The Width of the Rectangle is animated from 50 to the base value of the Widthproperty, 100.
// Demonstrates the use of the From property. // Create a NameScope for this page so that // Storyboards can be used. NameScope.SetNameScope(this, new NameScope()); Rectangle myRectangle = new Rectangle(); // Assign the Rectangle a name so that // it can be targeted by a Storyboard. this.RegisterName( "fromAnimatedRectangle", myRectangle); myRectangle.Height = 10; myRectangle.Width = 100; myRectangle.HorizontalAlignment = HorizontalAlignment.Left; myRectangle.Fill = Brushes.Purple; // Demonstrates the From property used by itself. Animates the // rectangle's Width property from 50 to its base value (100) // over 10 seconds. DoubleAnimation myDoubleAnimation = new DoubleAnimation(); myDoubleAnimation.From = 50; myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(10)); Storyboard.SetTargetName(myDoubleAnimation, "fromAnimatedRectangle"); Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Rectangle.WidthProperty)); Storyboard myStoryboard = new Storyboard(); myStoryboard.Children.Add(myDoubleAnimation); // Use an anonymous event handler to begin the animation // when the rectangle is clicked. myRectangle.MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs args) { myStoryboard.Begin(myRectangle); };
Other Animation Types
From/To/By animations are not the only type of animations that WPF provides: it also provides key-frame animations and path animations.
A key-frame animation animates along any number of destination values, described using key frames. For more information, see the Key-Frame Animations Overview.
A path animation generates output values from a PathGeometry. For more information, see the Path Animations Overview.
WPF also enables you to create your own custom animation types. For more information, see the Custom Animations Overview.
Key-Frame Animations Overview
This topic introduces you to key-frame animations. Key-frame animations enable you to animate using more than two target values, and control an animation's interpolation method.
Prerequisites
To understand this overview, you should be familiar with Windows Presentation Foundation (WPF) animations and timelines. For an introduction to animations, see the Animation Overview. It also helps to be familiar with From/To/By animations. For more information, see the From/To/By Animations Overview.
What is a Key-Frame Animation?
Like a From/To/By animation, a key-frame animation animates the value of a target property. It creates a transition among its target values over its Duration. However, while a From/To/By animation creates a transition between two values, a single key-frame animation can create transitions among any number of target values. Unlike a From/To/By animation, a key frame animation has no From, To, or By properties with which to set its target values. A key-frame animation's target values are described using key frames objects (hence the term, "key-frame animation"). To specify the animation's target values, you create key frame objects and add them to the animation's KeyFrames collection. When the animation runs, it transitions between the frames you specified.
In addition to supporting multiple target values, some key-frame methods even support multiple interpolation methods. An animation's interpolation method defines how it transitions from one value to the next. There are three types of interpolations: discrete, linear, and splined.
To animate with a key-frame animation, you complete the following steps.
Declare the animation and specify its Duration, just like you would for a from/to/by animation.
For each target value, create a key frame of the appropriate type, set its value and KeyTime, and add it to the animation's KeyFrames collection.
Associate the animation with a property, just like you would with a From/To/By animation. For more information about applying an animation to a property using a storyboard, see Storyboards Overview.
The following example uses a DoubleAnimationUsingKeyFrames to animate a Rectangle element to four different locations.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Microsoft.Samples.KeyFrameAnimations.KeyFramesIntroduction" WindowTitle="KeyFrame Animations"> <Border Width="400" BorderBrush="Black"> <Rectangle Fill="Blue" Width="50" Height="50" HorizontalAlignment="Left"> <Rectangle.RenderTransform> <TranslateTransform x:Name="MyAnimatedTranslateTransform" X="0" Y="0" /> </Rectangle.RenderTransform> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.MouseLeftButtonDown"> <BeginStoryboard> <Storyboard> <!-- Animate the TranslateTransform's X property from 0 to 350, then 50, then 200 over 10 seconds. --> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="MyAnimatedTranslateTransform" Storyboard.TargetProperty="X" Duration="0:0:10"> <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0" /> <LinearDoubleKeyFrame Value="350" KeyTime="0:0:2" /> <LinearDoubleKeyFrame Value="50" KeyTime="0:0:7" /> <LinearDoubleKeyFrame Value="200" KeyTime="0:0:8" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle> </Border> </Page>
Like a From/To/By animation, a key-frame animation can be applied to a property by using a Storyboard in markup and code or by using the BeginAnimation method in code. You may also use a key-frame animation to create an AnimationClock and apply it to one or more properties. For more information about the different methods for applying animations, see the Property Animation Techniques Overview.
Key-Frame Animation Types
Because animations generate property values, there are different animation types for different property types. To animate a property that takes a Double (such as an element's Width property), you use an animation that produces Double values. To animate a property that takes a Point, you use an animation that produces Point values, and so on.
The key-frame animation classes belong to the System.Windows.Media.Animation namespace and adhere to the following naming convention:
<Type>AnimationUsingKeyFrames
Where <Type> is the type of value that the class animates.
WPF provides the following key-frame animation classes.
Property type | Corresponding from/to/by animation class | Interpolation methods supported |
---|---|---|
Discrete | ||
Discrete, Linear, Splined | ||
Discrete, Linear, Splined | ||
Discrete, Linear, Splined | ||
Discrete, Linear, Splined | ||
Discrete, Linear, Splined | ||
Discrete, Linear, Splined | ||
Discrete, Linear, Splined | ||
Discrete | ||
Discrete | ||
Discrete, Linear, Splined | ||
Discrete, Linear, Splined | ||
Discrete, Linear, Splined | ||
Discrete, Linear, Splined | ||
Discrete, Linear, Splined | ||
Discrete | ||
Discrete, Linear, Splined | ||
Discrete, Linear, Splined | ||
Discrete, Linear, Splined | ||
Discrete, Linear, Splined |
Target Values (key frames) and Key Times
Just as there are different types of key-frame animations for animating different property types, there are also different types of key frame objects: one for each type of value animated and interpolation method supported. Key frame types adhere to the following naming convention:
<InterpolationMethod><Type>KeyFrame
Where <InterpolationMethod> is the interpolation method the key frame uses and <Type> is the type of value that the class animates. A key-frame animation that supports all three interpolation methods will have three key frame types that you can use. For example, you can use three key frame types with a DoubleAnimationUsingKeyFrames: DiscreteDoubleKeyFrame, LinearDoubleKeyFrame, and SplineDoubleKeyFrame. (Interpolation methods are described in detail in a later section.)
The primary purpose of a key frame is to specify a KeyTime and a Value. Every key frame type provides these two properties.
The Value property specifies the target value for that key-frame.
The KeyTime property specifies when (within the animation's Duration) a key frame's Value is reached.
When a key frame animation begins, iterates through its key frames in the order defined by their KeyTime properties.
If there is no key frame at time 0, the animation creates a transition between the target property's current value and the Value of the first key frame; otherwise, the animation's output value becomes the value of the first key frame.
The animation creates a transition between the Value of the first and second key frames using the interpolation method specified by the second key frame. The transition starts at the first key frame's KeyTime and ends when the second key frame's KeyTime is reached.
The animation continues, creating transitions between each subsequent key frame and its preceding key frame.
Finally, the animation transitions to the value of the key frame with the greatest key time that is equal to or smaller than the animation's Duration.
If the animation's Duration is Automatic or its Duration is equal to the time of the last key frame, the animation ends. Otherwise, if the animation's Duration is greater than the key time of the last key frame, the animation holds the key frame value until it reaches the end of its Duration. Like all animations, a key-frame animation uses its FillBehavior property to determine whether it holds it final value when it reaches the end of its active period. For more information, see the Timing Behaviors Overview.
The following example uses the DoubleAnimationUsingKeyFrames object defined in the preceding example to demonstrate how the Value and KeyTime properties work.
The first key frame immediately sets the animation's output value to 0.
The second key frame animates from 0 to 350. It starts after the first key frame ends (at time = 0 seconds) and plays for 2 seconds, ending at time = 0:0:2.
The third key frame animates from 350 to 50. It starts when the second key frame ends (at time = 2 seconds) and plays for 5 seconds, ending at time = 0:0:7.
The fourth key frame animates from 50 to 200. It starts when the third key frame ends (at time = 7 seconds) and plays for 1 second, ending at time = 0:0:8.
Because the Duration property of the animation was set to 10 seconds, the animation holds its final value for two seconds before ending at time = 0:0:10.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Microsoft.Samples.KeyFrameAnimations.KeyFramesIntroduction" WindowTitle="KeyFrame Animations"> <Border Width="400" BorderBrush="Black"> <Rectangle Fill="Blue" Width="50" Height="50" HorizontalAlignment="Left"> <Rectangle.RenderTransform> <TranslateTransform x:Name="MyAnimatedTranslateTransform" X="0" Y="0" /> </Rectangle.RenderTransform> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.MouseLeftButtonDown"> <BeginStoryboard> <Storyboard> <!-- Animate the TranslateTransform's X property from 0 to 350, then 50, then 200 over 10 seconds. --> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="MyAnimatedTranslateTransform" Storyboard.TargetProperty="X" Duration="0:0:10"> <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0" /> <LinearDoubleKeyFrame Value="350" KeyTime="0:0:2" /> <LinearDoubleKeyFrame Value="50" KeyTime="0:0:7" /> <LinearDoubleKeyFrame Value="200" KeyTime="0:0:8" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle> </Border> </Page>
Interpolation Methods
The preceding sections mentioned that some key-frame animations support multiple interpolation methods. An animation's interpolation describes how an animation transitions between values over its duration. By selecting which key frame type you use with your animation, you can define the interpolation method for that key frame segment. There are three different types of interpolation methods: linear, discrete, and splined.
Linear Interpolation
With linear interpolation, the animation progresses at a constant rate of the segment duration. For example, if a key frame segment transitions from 0 to 10 over a duration of 5 seconds, the animation will output the following values at the specified times:
Time | Output value |
---|---|
0 | 0 |
1 | 2 |
2 | 4 |
3 | 6 |
4 | 8 |
4.25 | 8.5 |
4.5 | 9 |
5 | 10 |
Discrete Interpolation
With discrete interpolation, the animation function jumps from one value to the next without interpolation. If a key frame segment transitions from 0 to 10 over a duration of 5 seconds, the animation will output the following values at the specified times:
Time | Output value |
---|---|
0 | 0 |
1 | 0 |
2 | 0 |
3 | 0 |
4 | 0 |
4.25 | 0 |
4.5 | 0 |
5 | 10 |
Notice how the animation does not change its output value until the very end of the segment duration.
Splined interpolation is more complex. It is described in the next section.
Splined Interpolation
Splined interpolation can be used to achieve more realistic timing effects. Because animations are so often used to imitate effects that occur in the real world, developers might need fine control of the acceleration and deceleration of objects, and close manipulation of timing segments. Spline key frames enable you to animate with splined interpolation. With other key frames, you specify a Value and KeyTime. With a spline key frame, you also specify a KeySpline. The following example shows a single spline key frame for a DoubleAnimationUsingKeyFrames. Notice the KeySpline property; that's what makes a spline key frame different from the other types of key frames.
<SplineDoubleKeyFrame Value="500" KeyTime="0:0:7" KeySpline="0.0,1.0 1.0,0.0" />
A cubic Bezier curve is defined by a start point, an end point, and two control points. The KeySpline property of a spline key frame defines the two control point of a Bezier curve that extends from (0,0) to (1,1). The first control point controls the curve factor of the first half of the Bezier curve, and the second control point controls the curve factor of the second half of the Bezier segment. The resulting curve describes the rate of change for that spline key frame. The steeper the curve, the faster the key frame changes its values. As the curve gets flatter, the key frame changes its values more slowly.
You might use KeySpline to simulate physical trajectories like falling water or bouncing balls, or apply other "ease in" and "ease out" effects to motion animations. For user interaction effects like background fades or control button rebound, you might apply splined interpolation to speed up or slow down the rate of change for an animation in a specific way.
The following example specifies a KeySpline of 0,1 1,0, which creates the following Bezier curve.
<SplineDoubleKeyFrame Value="500" KeyTime="0:0:7" KeySpline="0.0,1.0 1.0,0.0" />
This key frame animates rapidly when it begins, slows down, and then speeds up again before it ends.
The following example specifies a KeySpline of 0.5,0.25 0.75,1.0, which creates the following Bezier curve.
<SplineDoubleKeyFrame Value="350" KeyTime="0:0:15" KeySpline="0.25,0.5 0.75,1" />
Because the curvature of the Bezier curve changes very little, this key frame animates at an almost constant rate; it slows down somewhat toward its very end.
The following example uses a DoubleAnimationUsingKeyFrames to animate the position of rectangle. Because the DoubleAnimationUsingKeyFrames uses SplineDoubleKeyFrame objects, the transition between each key frame value uses splined interpolation.
<!-- This rectangle is animated using a key frame animation with splined interpolation. --> <Rectangle Width="50" Height="50" Fill="Purple"> <Rectangle.RenderTransform> <TranslateTransform x:Name="SplineAnimatedTranslateTransform" X="0" Y="0" /> </Rectangle.RenderTransform> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <!-- Animate the TranslateTransform's X property from its base value (0) to 500, then 200, then 350 over 15 seconds. --> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="SplineAnimatedTranslateTransform" Storyboard.TargetProperty="X" Duration="0:0:15" RepeatBehavior="Forever"> <SplineDoubleKeyFrame Value="500" KeyTime="0:0:7" KeySpline="0.0,1.0 1.0,0.0" /> <SplineDoubleKeyFrame Value="200" KeyTime="0:0:10" KeySpline="0.0,0.0 1.0,0.0" /> <SplineDoubleKeyFrame Value="350" KeyTime="0:0:15" KeySpline="0.25,0.5 0.75,1" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle>
Splined interpolation can be difficult to understand; experimenting with different settings can help. The Key Spline Animation Sample enables you to change key spline values and see the result it has on an animation.
Combining Interpolation Methods
You can use key frames with different interpolation types in a single key frame animation. When two key frame animations with different interpolations follow each other, the interpolation method of the second key frame is used to create the transition from the first value to the second.
In the following example, a DoubleAnimationUsingKeyFrames is created that uses linear, splined, and discrete interpolation.
<!-- This rectangle is animated using a key frame animation with a combination of interpolation methods. --> <Rectangle Width="50" Height="50" Fill="Orange"> <Rectangle.RenderTransform> <TranslateTransform x:Name="ComboAnimatedTranslateTransform" X="0" Y="0" /> </Rectangle.RenderTransform> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <!-- Animate the TranslateTransform's X property from its base value (0) to 500, then 200, then 350 over 15 seconds. --> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ComboAnimatedTranslateTransform" Storyboard.TargetProperty="X" Duration="0:0:15" RepeatBehavior="Forever"> <DiscreteDoubleKeyFrame Value="500" KeyTime="0:0:7" /> <LinearDoubleKeyFrame Value="200" KeyTime="0:0:10" /> <SplineDoubleKeyFrame Value="350" KeyTime="0:0:15" KeySpline="0.25,0.5 0.75,1" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle>
More about Duration and Key Times
Like other animations, key-frame animations have a Duration property. In addition to specifying the animation's Duration, you need to specify what portion of that duration is given to each key frame. You do so by describing a KeyTime for each of the animation's key frames. Each key frame's KeyTime specifies when that key frame ends.
The KeyTime property does not specify how long the key time plays. The amount of time a key frame plays is determined by when the key frame ends, when the previous key frame ended, and the animation's duration. Key times may be specified as a time value, a percentage, or as the special values Uniform or Paced.
The following list describes the different ways of specifying key times.
TimeSpan Values
You may use TimeSpan values to specify a KeyTime. The value should be greater than or equal to 0 and less than or equal to the animation's duration. The following example shows an animation with a duration of 10 seconds and four key frames whose key times are specified as time values.
The first key frame animates from the base value to 100 over the first 3 seconds, ending at time = 0:0:03.
The second key frame animates from 100 to 200. It starts after the first key frame ends (at time = 3 seconds) and plays for 5 seconds, ending at time = 0:0:8.
The third key frame animates from 200 to 500. It starts when the second key frame ends (at time = 8 seconds) and plays for 1 second, ending at time = 0:0:9.
The fourth key frame animates from 500 to 600. It starts when the third key frame ends (at time = 9 seconds) and plays for 1 second, ending at time = 0:0:10.
<!-- This rectangle is animated with KeyTimes using TimeSpan values. Goes to 100 in the first 3 seconds, 100 to 200 in the next 5 seconds, 300 to 500 in the next second, and 500 to 600 in the final second. --> <Rectangle Width="50" Height="50" Fill="Blue"> <Rectangle.RenderTransform> <TranslateTransform x:Name="TranslateTransform01" X="10" Y="30" /> </Rectangle.RenderTransform> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="TranslateTransform01" Storyboard.TargetProperty="X" Duration="0:0:10" RepeatBehavior="Forever"> <!-- KeyTime properties are expressed as TimeSpan values which are in the form of "hours:minutes:seconds". --> <LinearDoubleKeyFrame Value="100" KeyTime="0:0:3" /> <LinearDoubleKeyFrame Value="200" KeyTime="0:0:8" /> <LinearDoubleKeyFrame Value="500" KeyTime="0:0:9" /> <LinearDoubleKeyFrame Value="600" KeyTime="0:0:10" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle>
Percentage Values
A percentage value specifies that the key frame ends at some percentage of the animation's Duration. In XAML, you specify the percentage as a number followed by the % symbol. In code, you use the FromPercent method and pass it a Double indicating the percentage. The value must be greater than or equal to 0 and less than or equal to 100 percent. The following example shows an animation with a duration of 10 seconds and four key frames whose key times are specified as percentages.
The first key frame animates from the base value to 100 over the first 3 seconds, ending at time = 0:0:3.
The second key frame animates from 100 to 200. It starts after the first key frame ends (at time = 3 seconds) and plays for 5 seconds, ending at time = 0:0:8 (0.8 * 10 = 8).
The third key frame animates from 200 to 500. It starts when the second key frame ends (at time = 8 seconds) and plays for 1 second, ending at time = 0:0:9 (0.9 * 10 = 9).
The fourth key frame animates from 500 to 600. It starts when the third key frame ends (at time = 9 seconds) and plays for 1 second, ending at time = 0:0:10 (1 * 10 = 10).
<!-- Identical animation behavior to the previous rectangle but using percentage values for KeyTimes rather then TimeSpan. --> <Rectangle Height="50" Width="50" Fill="Purple"> <Rectangle.RenderTransform> <TranslateTransform x:Name="TranslateTransform02" X="10" Y="110" /> </Rectangle.RenderTransform> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="TranslateTransform02" Storyboard.TargetProperty="X" Duration="0:0:10" RepeatBehavior="Forever"> <!-- KeyTime properties are expressed as Percentages. --> <LinearDoubleKeyFrame Value="100" KeyTime="30%" /> <LinearDoubleKeyFrame Value="200" KeyTime="80%" /> <LinearDoubleKeyFrame Value="500" KeyTime="90%" /> <LinearDoubleKeyFrame Value="600" KeyTime="100%" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle>
Special Value, Uniform
Use Uniform timing when you want each key frame to take the same amount of time.
A Uniform key time divides the available time equally by the number of key frames to determine the end time of each key frame. The following example shows an animation with a duration of 10 seconds and four key frames whose key times are specified as Uniform.
The first key frame animates from the base value to 100 over the first 2.5 seconds, ending at time = 0:0:2.5.
The second key frame animates from 100 to 200. It starts after the first key frame ends (at time = 2.5 seconds) and plays for approximately 2.5 seconds, ending at time = 0:0:5.
The third key frame animates from 200 to 500. It starts when the second key frame ends (at time = 5 seconds) and plays for 2.5 seconds, ending at time = 0:0:7.5.
The fourth key frame animates from 500 to 600. It starts when the second key frame ends (at time = 7.5 seconds) and plays for 2.5 seconds, ending at time = 0:0:1.
<!-- This rectangle is animated with KeyTimes using Uniform values. --> <Rectangle Height="50" Width="50" Fill="Red"> <Rectangle.RenderTransform> <TranslateTransform x:Name="TranslateTransform03" X="10" Y="190" /> </Rectangle.RenderTransform> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="TranslateTransform03" Storyboard.TargetProperty="X" Duration="0:0:10" RepeatBehavior="Forever"> <!-- KeyTime properties are expressed with values of Uniform. When a key time is set to "Uniform" the total allotted time of the animation is divided evenly between key frames. In this example, the total duration of the animation is ten seconds and there are four key frames each of which are set to "Uniform", therefore, the duration of each key frame is 3.3 seconds (10/3). --> <LinearDoubleKeyFrame Value="100" KeyTime="Uniform" /> <LinearDoubleKeyFrame Value="200" KeyTime="Uniform" /> <LinearDoubleKeyFrame Value="500" KeyTime="Uniform" /> <LinearDoubleKeyFrame Value="600" KeyTime="Uniform" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle>
Special Value, Paced
Use Paced timing when you want to animate at a constant rate.
A Paced key time allocates the available time according to the length of each of the key frames to determine the duration of each frame. This will provide the behavior that the velocity or pace of the animation remains constant. The following example shows an animation with a duration of 10 seconds and three key frames whose key times are specified as Paced.
<!-- Using Paced Values. Rectangle moves between key frames at uniform rate except for first key frame because using a Paced value on the first KeyFrame in a collection of frames gives a time of zero. --> <Rectangle Height="50" Width="50" Fill="Orange"> <Rectangle.RenderTransform> <TranslateTransform x:Name="TranslateTransform04" X="10" Y="270" /> </Rectangle.RenderTransform> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="TranslateTransform04" Storyboard.TargetProperty="X" Duration="0:0:10" RepeatBehavior="Forever"> <!-- KeyTime properties are expressed with values of Paced. Paced values are used when a constant rate is desired. The time allocated to a key frame with a KeyTime of "Paced" is determined by the time allocated to the other key frames of the animation. This time is calculated to attempt to give a "paced" or "constant velocity" for the animation. --> <LinearDoubleKeyFrame Value="100" KeyTime="Paced" /> <LinearDoubleKeyFrame Value="200" KeyTime="Paced" /> <LinearDoubleKeyFrame Value="500" KeyTime="Paced" /> <LinearDoubleKeyFrame Value="600" KeyTime="Paced" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle>
Note that, if the last key frame's key time is Paced or Uniform, its resolved key time will be set to 100 percent. If the first key frame in a multiframe animation is paced, its resolved key time will be set to 0. (If the key frame collection contains only a single key frame and it is a paced key frame, its resolved key time will be set to 100 percent.)
Different key frames within a single key frame animation may use different key time types.
Combining Key Times, Out-Of-Order Key Frames
You can use key frames with different KeyTime value types in the same animation. And, although it's recommended that you add key frames in the order in which they should play, it's not necessary. The animation and timing system is capable of resolving out of order key frames. Key frames with invalid key times are ignored.
The following list describes the procedure by which key times are resolved for a key-frame animation's key frames.
Determine the animation’s total interpolation time, the total time it takes the key-frame animation to complete a forward iteration.
If the animation's Duration is not Automatic or Forever, the total interpolation time is the value of the animation's Duration property.
Otherwise, the total interpolation time is the largest TimeSpan KeyTime value specified among its key frames, if any exist.
Otherwise, the total interpolation time is 1 second.
Use the total interpolation time value to resolve Percent KeyTime values.
Resolve last key frame, if it wasn't already resolved in the previous steps. If the KeyTime of the last key frame is Uniform or Paced, its resolved time will be equal to the total interpolation time.
If the KeyTime of the first key frame is Paced and this animation has more than on key frames, resolve its KeyTime value to zero; if there is only one key frame and its KeyTime value is Paced, it is resolved to the total interpolation time, as described in the preceding step.
Resolve remaining Uniform KeyTime values: they are each given an equal share of the available time. During this process, unresolved Paced KeyTime values are temporarily treated as Uniform KeyTime values, and get a temporary resolved time.
Resolve the KeyTime values of key frames with unspecified key times by using the key frames declared nearest them that have resolved KeyTime values.
Resolve remaining Paced KeyTime values. Paced KeyTime use the KeyTime values of the neighboring key frames to determine their resolved time. The goal is to ensure that the velocity of the animation is constant around this key frame's resolved time.
Sort key frames in order of resolved time (primary key), and order of declaration (secondary key), i.e., use a stable sort based on the resolved key frame KeyTime values.
Easing Functions
Easing functions allow you to apply custom mathematical formulas to your animations. For example, you may want an object to realistically bounce or behave as though it were on a spring. You could use Key-Frame or even From/To/By animations to approximate these effects but it would take a significant amount of work and the animation would be less accurate than using a mathematical formula.
Besides creating your own custom easing function by inheriting from EasingFunctionBase, you can use one of several easing functions provided by the runtime to create common effects.
BackEase: Retracts the motion of an animation slightly before it begins to animate in the path indicated.
BounceEase: Creates a bouncing effect.
CircleEase: Creates an animation that accelerates and/or decelerates using a circular function.
CubicEase: Creates an animation that accelerates and/or decelerates using the formula f( t) = t3.
ElasticEase: Creates an animation that resembles a spring oscillating back and forth until it comes to rest.
ExponentialEase: Creates an animation that accelerates and/or decelerates using an exponential formula.
PowerEase: Creates an animation that accelerates and/or decelerates using the formula f( t) = tp where p is equal to the Power property.
QuadraticEase: Creates an animation that accelerates and/or decelerates using the formula f( t) = t2.
QuarticEase: Creates an animation that accelerates and/or decelerates using the formula f( t) = t4.
QuinticEase: Create an animation that accelerates and/or decelerates using the formula f( t) = t5.
SineEase: Creates an animation that accelerates and/or decelerates using a sine formula.
You can explore the behavior of these easing functions with the following sample.
To apply an easing function to an animation, use the EasingFunction property of the animation specify the easing function to apply to the animation. The following example applies a BounceEase easing function to a DoubleAnimation to create a bouncing effect.
<Rectangle Name="myRectangle" Width="200" Height="30" Fill="Blue"> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.MouseDown"> <BeginStoryboard> <Storyboard> <Storyboard x:Name="myStoryboard"> <DoubleAnimation From="30" To="200" Duration="00:00:3" Storyboard.TargetName="myRectangle" Storyboard.TargetProperty="Height"> <DoubleAnimation.EasingFunction> <BounceEase Bounces="2" EasingMode="EaseOut" Bounciness="2" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle>
In the previous example, the easing function was applied to a From/To/By animation. You can also apply these easing functions to Key-Frame animations. The following example shows how to use key frames with easing functions associated with them to create an animation of a rectangle that contracts upward, slows down, then expands downward (as though falling) and then bounces to a stop.
<Rectangle Name="myRectangle" Width="200" Height="200" Fill="Blue"> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.MouseDown"> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Height" Storyboard.TargetName="myRectangle"> <!-- This keyframe animates the ellipse up to the crest where it slows down and stops. --> <EasingDoubleKeyFrame Value="30" KeyTime="00:00:02"> <EasingDoubleKeyFrame.EasingFunction> <CubicEase EasingMode="EaseOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> <!-- This keyframe animates the ellipse back down and makes it bounce. --> <EasingDoubleKeyFrame Value="200" KeyTime="00:00:06"> <EasingDoubleKeyFrame.EasingFunction> <BounceEase Bounces="5" EasingMode="EaseOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle>
You can use the EasingMode property to alter how the easing function behaves, that is, change how the animation interpolates. There are three possible values you can give for EasingMode:
EaseIn: Interpolation follows the mathematical formula associated with the easing function.
EaseOut: Interpolation follows 100% interpolation minus the output of the formula associated with the easing function.
EaseInOut: Interpolation uses EaseIn for the first half of the animation and EaseOut for the second half.
The graphs below demonstrate the different values of EasingMode where f( x) represents the animation progress and t represents time.
Note |
---|
You can use PowerEase to create the same behavior as CubicEase, QuadraticEase, QuarticEase, and QuinticEase by using the Power property. For example, if you want to use PowerEase to substitute for CubicEase, specify a Power value of 3. |
In addition to using the easing functions included in the run-time, you can create your own custom easing functions by inheriting fromEasingFunctionBase. The following example demonstrates how to create a simple custom easing function. You can add your own mathematical logic for how the easing function behaves by overriding the EaseInCore method.
namespace CustomEasingFunction { public class CustomSeventhPowerEasingFunction : EasingFunctionBase { public CustomSeventhPowerEasingFunction() : base() { } // Specify your own logic for the easing function by overriding // the EaseInCore method. Note that this logic applies to the "EaseIn" // mode of interpolation. protected override double EaseInCore(double normalizedTime) { // applies the formula of time to the seventh power. return Math.Pow(normalizedTime, 7); } // Typical implementation of CreateInstanceCore protected override Freezable CreateInstanceCore() { return new CustomSeventhPowerEasingFunction(); } } }
<Window x:Class="CustomEasingFunction.Window1" xmlns:CustomEase="clr-namespace:CustomEasingFunction" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="500" Width="300"> <StackPanel> <TextBlock Margin="10" TextWrapping="Wrap">Click on the rectangle to start the animation</TextBlock> <StackPanel x:Name="LayoutRoot" Background="White"> <Rectangle Name="myRectangle" Width="200" Height="30" Fill="Blue"> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.MouseDown"> <BeginStoryboard> <Storyboard> <DoubleAnimation From="30" To="300" Duration="00:00:3" Storyboard.TargetName="myRectangle" Storyboard.TargetProperty="Height"> <DoubleAnimation.EasingFunction> <!-- You get the EasingMode property for free on your custom easing function.--> <CustomEase:CustomSeventhPowerEasingFunction EasingMode="EaseIn"/> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle> </StackPanel> </StackPanel> </Window>
Path Animations Overview
This topic introduces path animations, which enable you to use a geometric path to generate output values. Path animations are useful for moving and rotating objects along complex paths.
Prerequisites
To understand this topic, you should be familiar with WPF animations features. For an introduction to animation features, see the Animation Overview.
Because you use a PathGeometry object to define a path animation, you should also be familiar with PathGeometry and the different types ofPathSegment objects. For more information, see the Geometry Overview.
What Is a Path Animation?
A path animation is a type of AnimationTimeline that uses a PathGeometry as its input. Instead of setting a From, To, or By property (as you do for a From/To/By animation) or using key frames (as you use for a key-frame animation), you define a geometric path and use it to set the PathGeometryproperty of the path animation. As the path animation progresses, it reads the x, y, and angle information from the path and uses that information to generate its output.
Path animations are very useful for animating an object along a complex path. One way to move an object along a path is to use a MatrixTransformand a MatrixAnimationUsingPath to transform an object along a complex path. The following example demonstrates this technique by using theMatrixAnimationUsingPath object to animate the Matrix property of a MatrixTransform. The MatrixTransform is applied to a button and causes it to move along a curved path. Because the DoesRotateWithTangent property is set to true, the rectangle rotates along the tangent of the path.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="PresentationOptions" Margin="20"> <Canvas Width="400" Height="400"> <!-- The Button that is animated across the screen by animating the MatrixTransform applied to the button. --> <Button MinWidth="100" Content="A Button"> <Button.RenderTransform> <MatrixTransform x:Name="ButtonMatrixTransform"> <MatrixTransform.Matrix > <Matrix /> </MatrixTransform.Matrix> </MatrixTransform> </Button.RenderTransform> <Button.Triggers> <EventTrigger RoutedEvent="Button.Loaded"> <BeginStoryboard> <Storyboard> <MatrixAnimationUsingPath Storyboard.TargetName="ButtonMatrixTransform" Storyboard.TargetProperty="Matrix" DoesRotateWithTangent="True" Duration="0:0:5" RepeatBehavior="Forever" > <MatrixAnimationUsingPath.PathGeometry> <PathGeometry Figures="M 10,100 C 35,0 135,0 160,100 180,190 285,200 310,100" PresentationOptions:Freeze="True" /> </MatrixAnimationUsingPath.PathGeometry> </MatrixAnimationUsingPath> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> </Canvas> </Page>
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Navigation; using System.Windows.Shapes; namespace SDKSample { /// <summary> /// Shows how to animate an object along /// a geometric path. /// </summary> public class MatrixAnimationUsingPathDoesRotateWithTangentExample : Page { public MatrixAnimationUsingPathDoesRotateWithTangentExample() { this.Margin = new Thickness(20); // Create a NameScope for the page so that // we can use Storyboards. NameScope.SetNameScope(this, new NameScope()); // Create a button. Button aButton = new Button(); aButton.MinWidth = 100; aButton.Content = "A Button"; // Create a MatrixTransform. This transform // will be used to move the button. MatrixTransform buttonMatrixTransform = new MatrixTransform(); aButton.RenderTransform = buttonMatrixTransform; // Register the transform's name with the page // so that it can be targeted by a Storyboard. this.RegisterName("ButtonMatrixTransform", buttonMatrixTransform); // Create a Canvas to contain the button // and add it to the page. // Although this example uses a Canvas, // any type of panel will work. Canvas mainPanel = new Canvas(); mainPanel.Width = 400; mainPanel.Height = 400; mainPanel.Children.Add(aButton); this.Content = mainPanel; // Create the animation path. PathGeometry animationPath = new PathGeometry(); PathFigure pFigure = new PathFigure(); pFigure.StartPoint = new Point(10, 100); PolyBezierSegment pBezierSegment = new PolyBezierSegment(); pBezierSegment.Points.Add(new Point(35, 0)); pBezierSegment.Points.Add(new Point(135, 0)); pBezierSegment.Points.Add(new Point(160, 100)); pBezierSegment.Points.Add(new Point(180, 190)); pBezierSegment.Points.Add(new Point(285, 200)); pBezierSegment.Points.Add(new Point(310, 100)); pFigure.Segments.Add(pBezierSegment); animationPath.Figures.Add(pFigure); // Freeze the PathGeometry for performance benefits. animationPath.Freeze(); // Create a MatrixAnimationUsingPath to move the // button along the path by animating // its MatrixTransform. MatrixAnimationUsingPath matrixAnimation = new MatrixAnimationUsingPath(); matrixAnimation.PathGeometry = animationPath; matrixAnimation.Duration = TimeSpan.FromSeconds(5); matrixAnimation.RepeatBehavior = RepeatBehavior.Forever; // Set the animation's DoesRotateWithTangent property // to true so that rotates the rectangle in addition // to moving it. matrixAnimation.DoesRotateWithTangent = true; // Set the animation to target the Matrix property // of the MatrixTransform named "ButtonMatrixTransform". Storyboard.SetTargetName(matrixAnimation, "ButtonMatrixTransform"); Storyboard.SetTargetProperty(matrixAnimation, new PropertyPath(MatrixTransform.MatrixProperty)); // Create a Storyboard to contain and apply the animation. Storyboard pathAnimationStoryboard = new Storyboard(); pathAnimationStoryboard.Children.Add(matrixAnimation); // Start the storyboard when the button is loaded. aButton.Loaded += delegate(object sender, RoutedEventArgs e) { // Start the storyboard. pathAnimationStoryboard.Begin(this); }; } } }
For more information about the path syntax that is used in the XAML example, see the Path Markup Syntax overview. For the complete sample, seePath Animation Sample.
You can apply a path animation to a property by using a Storyboard in XAML and code, or by using the BeginAnimation method in code. You can also use a path animation to create an AnimationClock and apply it to one or more properties. For more information about the different methods for applying animations, see Property Animation Techniques Overview.
Path Animation Types
Because animations generate property values, there are different animation types for different property types. To animate a property that takes aDouble (such as the X property of a TranslateTransform), you use an animation that produces Double values. To animate a property that takes a Point, you use an animation that produces Point values, and so on.
Path animation classes belong to the System.Windows.Media.Animation namespace and use the following naming convention:
<Type>AnimationUsingPath
Where <Type> is the type of value that the class animates.
WPF provides the following path animation classes.
Property type | Corresponding path animation class | Example |
---|---|---|
A MatrixAnimationUsingPath generates Matrix values from its PathGeometry. When used with a MatrixTransform, a MatrixAnimationUsingPath can move an object along a path. If you set the DoesRotateWithTangent property of the MatrixAnimationUsingPath to true, it also rotates the object along the curves of the path.
A PointAnimationUsingPath generates Point values from the x- and y-coordinates of its PathGeometry. By using a PointAnimationUsingPath to animate a property that takes Point values, you can move an object along a path. A PointAnimationUsingPath cannot rotate objects.
A DoubleAnimationUsingPath generates Double values from its PathGeometry. By setting the Source property, you can specify whether theDoubleAnimationUsingPath uses the x-coordinate, y-coordinate, or angle of the path as its output. You can use a DoubleAnimationUsingPath to rotate an object or move it along the x-axis or y-axis.
Path Animation Input
Each path animation class provides a PathGeometry property for specifying its input. The path animation uses the PathGeometry to generate its output values. The PathGeometry class lets you describe multiple complex figures that are composed of arcs, curves, and lines.
At the heart of a PathGeometry is a collection of PathFigure objects; these objects are so named because each figure describes a discrete shape in the PathGeometry. Each PathFigure consists of one or more PathSegment objects, each of which describes a segment of the figure.
There are many types of segments.
Segment Type | Description |
---|---|
Creates an elliptical arc between two points. | |
Creates a cubic Bezier curve between two points. | |
Creates a line between two points. | |
Creates a series of cubic Bezier curves. | |
Creates a series of lines. | |
Creates a series of quadratic Bezier curves. | |
Creates a quadratic Bezier curve. |
The segments in a PathFigure are combined into a single geometric shape, which uses the end point of a segment as the start point of the next segment. The StartPoint property of a PathFigure specifies the point from which the first segment is drawn. Each subsequent segment starts at the end point of the previous segment. For example, a vertical line from 10,50 to 10,150 can be defined by setting the StartPoint property to 10,50 and creating a LineSegment with a Point property setting of 10,150.
For more information about PathGeometry objects, see the Geometry Overview.
In XAML, you can also use a special abbreviated syntax to set the Figures property of a PathGeometry. For more information, see Path Markup Syntax overview.
For more information about the path syntax that is used in the XAML example, see the Path Markup Syntax overview.
Property Animation Techniques Overview
This topic describes the different approaches for animating properties: storyboards, local animations, clocks, and per-frame animations.
Prerequisites
To understand this topic, you should be familiar with the basic animation features described in the Animation Overview.
Different Ways to Animate
Because there are many different scenarios for animating properties, WPF provides several approaches for animating properties.
For each approach, the following table indicates whether it can be used per-instance, in styles, in control templates, or in data templates; whether it can be used in XAML; and whether the approach enables you to interactively control the animation. "Per-Instance" refers to the technique of applying an animation or storyboard directly to instances of an object, rather than in a style, control template, or data template.
Animation technique | Scenarios | Supports XAML | Interactively controllable |
---|---|---|---|
Storyboard animation | Per-instance, Style, ControlTemplate, DataTemplate | Yes | Yes |
Local animation | Per-instance | No | No |
Clock animation | Per-instance | No | Yes |
Per-frame animation | Per-instance | No | N/A |
Storyboard Animations
Use a Storyboard when you want to define and apply your animations in XAML, interactively control your animations after they start, create a complex tree of animations, or animate in a Style, ControlTemplate or DataTemplate. For an object to be animated by a Storyboard, it must be a FrameworkElement or FrameworkContentElement, or it must be used to set a FrameworkElement or FrameworkContentElement. For more details, see the Storyboards Overview.
A Storyboard is a special type of container Timeline that provides targeting information for the animations it contains. To animate with a Storyboard, you complete the following three steps.
Declare a Storyboard and one or more animations.
Use the TargetName and TargetProperty attached properties to specify the target object and property of each animation.
(Code only) Define a NameScope for a FrameworkElement or FrameworkContentElement. Register the names of the objects to animate with that FrameworkElement or FrameworkContentElement.
Begin the Storyboard.
Beginning a Storyboard applies animations to the properties they animate and starts them. There are two ways to begin a Storyboard: you can use the Begin method provided by the Storyboard class, or you can use a BeginStoryboard action. The only way to animate in XAML is to use a BeginStoryboard action. A BeginStoryboard action can be used in an EventTrigger, property Trigger, or a DataTrigger.
The following table shows the different places where each Storyboard begin technique is supported: per-instance, style, control template, and data template.
Storyboard is begun using… | Per-instance | Style | Control template | Data template | Example |
---|---|---|---|---|---|
BeginStoryboard and an EventTrigger | Yes | Yes | Yes | Yes | |
BeginStoryboard and a property Trigger | No | Yes | Yes | Yes | |
BeginStoryboard and a DataTrigger | No | Yes | Yes | Yes | |
Begin method | Yes | No | No | No |
For more information about Storyboard objects, see the Storyboards Overview.
Local Animations
Local animations provide a convenient way to animate a dependency property of any Animatable object. Use local animations when you want to apply a single animation to a property and you don't need to interactively control the animation after it starts. Unlike a Storyboard animation, a local animation can animate an object that isn't associated with a FrameworkElement or a FrameworkContentElement. You also don't have to define a NameScope for this type of animation.
Local animations may only be used in code, and cannot be defined in styles, control templates, or data templates. A local animation cannot be interactively controlled after it is started.
To animate using a local animation, complete the following steps.
Create an AnimationTimeline object.
Use the BeginAnimation method of the object that you want to animate to apply the AnimationTimeline to the property that you specify.
The following example shows how to animate the width and background color of a Button.
/* This sample demonstrates how to apply non-storyboard animations to a property. To animate in markup, you must use storyboards. */ using System; using System.Windows; using System.Windows.Navigation; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Windows.Controls; namespace Microsoft.Samples.Animation.LocalAnimations { // Create the demonstration. public class LocalAnimationExample : Page { public LocalAnimationExample() { WindowTitle = "Local Animation Example"; StackPanel myStackPanel = new StackPanel(); myStackPanel.Margin = new Thickness(20); // Create and set the Button. Button aButton = new Button(); aButton.Content = "A Button"; // Animate the Button's Width. DoubleAnimation myDoubleAnimation = new DoubleAnimation(); myDoubleAnimation.From = 75; myDoubleAnimation.To = 300; myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(5)); myDoubleAnimation.AutoReverse = true; myDoubleAnimation.RepeatBehavior = RepeatBehavior.Forever; // Apply the animation to the button's Width property. aButton.BeginAnimation(Button.WidthProperty, myDoubleAnimation); // Create and animate a Brush to set the button's Background. SolidColorBrush myBrush = new SolidColorBrush(); myBrush.Color = Colors.Blue; ColorAnimation myColorAnimation = new ColorAnimation(); myColorAnimation.From = Colors.Blue; myColorAnimation.To = Colors.Red; myColorAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(7000)); myColorAnimation.AutoReverse = true; myColorAnimation.RepeatBehavior = RepeatBehavior.Forever; // Apply the animation to the brush's Color property. myBrush.BeginAnimation(SolidColorBrush.ColorProperty, myColorAnimation); aButton.Background = myBrush; // Add the Button to the panel. myStackPanel.Children.Add(aButton); this.Content = myStackPanel; } } }
Clock Animations
Use Clock objects when you want to animate without using a Storyboard and you want to create complex timing trees or interactively control animations after they start. You can use Clock objects to animate a dependency property of any Animatable object.
You cannot use Clock objects directly to animate in styles, control templates, or data templates. (The animation and timing system actually does use Clock objects to animate in styles, control templates, and data templates, but it must create those Clock objects for you from a Storyboard. For more information about the relationship between Storyboard objects and Clock objects, see the Animation and Timing System Overview.)
To apply a single Clock to a property, you complete the following steps.
Create an AnimationTimeline object.
Use the CreateClock method of the AnimationTimeline to create an AnimationClock.
Use the ApplyAnimationClock method of the object that you want to animate to apply the AnimationClock to the property you specify.
The following example shows how to create an AnimationClock and apply it to two similar properties.
/* This example shows how to create and apply an AnimationClock. */ using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; using System.Windows.Media.Animation; namespace Microsoft.Samples.Animation.TimingBehaviors { public class AnimationClockExample : Page { ScaleTransform myScaleTransform; public AnimationClockExample() { this.WindowTitle = "Opacity Animation Example"; this.Background = Brushes.White; StackPanel myStackPanel = new StackPanel(); myStackPanel.Margin = new Thickness(20); // Create a button that with a ScaleTransform. // The ScaleTransform will animate when the // button is clicked. Button myButton = new Button(); myButton.Margin = new Thickness(50); myButton.HorizontalAlignment = HorizontalAlignment.Left; myButton.Content = "Click Me"; myScaleTransform = new ScaleTransform(1,1); myButton.RenderTransform = myScaleTransform; // Associate an event handler with the // button's Click event. myButton.Click += new RoutedEventHandler(myButton_Clicked); myStackPanel.Children.Add(myButton); this.Content = myStackPanel; } // Create and apply and animation when the button is clicked. private void myButton_Clicked(object sender, RoutedEventArgs e) { // Create a DoubleAnimation to animate the // ScaleTransform. DoubleAnimation myAnimation = new DoubleAnimation( 1, // "From" value 5, // "To" value new Duration(TimeSpan.FromSeconds(5)) ); myAnimation.AutoReverse = true; // Create a clock the for the animation. AnimationClock myClock = myAnimation.CreateClock(); // Associate the clock the ScaleX and // ScaleY properties of the button's // ScaleTransform. myScaleTransform.ApplyAnimationClock( ScaleTransform.ScaleXProperty, myClock); myScaleTransform.ApplyAnimationClock( ScaleTransform.ScaleYProperty, myClock); } } }
To create a timing tree and use it animate properties, you complete the following steps.
Use ParallelTimeline and AnimationTimeline objects to create the timing tree.
Use the CreateClock of the root ParallelTimeline to create a ClockGroup.
Iterate through the Children of the ClockGroup and apply its child Clock objects. For each AnimationClock child, use the ApplyAnimationClock method of the object that you want to animate to apply the AnimationClock to the property you specify
For more information about Clock objects, see the Animation and Timing System Overview.
Per-Frame Animation: Bypass the Animation and Timing System
Use this approach when you need to completely bypass the WPF animation system. One scenario for this approach is physics animations, where each step in the animation requires objects to be recomputed based on the last set of object interactions.
Per-frame animations cannot be defined inside styles, control templates, or data templates.
To animate frame-by-frame, you register for the Rendering event of the object that contains the objects you want to animate. This event handler method gets called once per frame. Each time that WPF marshals the persisted rendering data in the visual tree across to the composition tree, your event handler method is called.
In your event handler, perform whatever calculations are necessary for your animation effect and set the properties of the objects you want to animate with these values.
To obtain the presentation time for the current frame, the EventArgs associated with this event can be cast as RenderingEventArgs, which provide a RenderingTime property that you can use to obtain the current frame's rendering time.
For more information, see the Rendering page.
Storyboards Overview
This topic shows how to use Storyboard objects to organize and apply animations. It describes how to interactively manipulate Storyboard objects and describes indirect property targeting syntax.
Prerequisites
To understand this topic, you should be familiar with the different animation types and their basic features. For an introduction to animation, see the Animation Overview. You should also know how to use attached properties. For more information about attached properties, see the Attached Properties Overview.
What Is a Storyboard?
Animations are not the only useful type of timeline. Other timeline classes are provided to help you organize sets of timelines, and to apply timelines to properties. Container timelines derive from the TimelineGroup class, and include ParallelTimeline and Storyboard.
A Storyboard is a type of container timeline that provides targeting information for the timelines it contains. A Storyboard can contain any type of Timeline, including other container timelines and animations. Storyboard objects enable you to combine timelines that affect a variety of objects and properties into a single timeline tree, making it easy to organize and control complex timing behaviors. For example, suppose you want a button that does these three things.
Grow and change color when the user selects the button.
Shrink away and then grow back to its original size when clicked.
Shrink and fade to 50 percent opacity when it becomes disabled.
In this case, you have multiple sets of animations that apply to the same object, and you want to play at different times, dependent on the state of the button. Storyboard objects enable you to organize animations and apply them in groups to one or more objects.
Where Can You Use a Storyboard?
A Storyboard can be used to animate dependency properties of animatable classes (for more information about what makes a class animatable, see the Animation Overview). However, because storyboarding is a framework-level feature, the object must belong to the NameScope of a FrameworkElement or a FrameworkContentElement.
For example, you could use a Storyboard to do the following:
Animate a SolidColorBrush (Non-framework element) that paints the Background of a Button (a type of FrameworkElement),
Animate a SolidColorBrush (Non-framework element) that paints the fill of a GeometryDrawing (Non-framework element) displayed using an Image (FrameworkElement).
In code, animate a SolidColorBrush declared by a class that also contains a FrameworkElement, if the SolidColorBrush registered its name with that FrameworkElement.
However, you could not use a Storyboard to animate a SolidColorBrush that did not register its name with a FrameworkElement or FrameworkContentElement, or was not used to set a property of a FrameworkElement or FrameworkContentElement.
How to Apply Animations with a Storyboard
To use a Storyboard to organize and apply animations, you add the animations as child timelines of the Storyboard. The Storyboard class provides the Storyboard.TargetName and Storyboard.TargetProperty attached properties. You set these properties on an animation to specify its target object and property.
To apply animations to their targets, you begin the Storyboard using a trigger action or a method. In XAML, you use a BeginStoryboard object with an EventTrigger, Trigger, or DataTrigger. In code, you can also use the Begin method.
The following table shows the different places where each Storyboard begin technique is supported: per-instance, style, control template, and data template. "Per-Instance" refers to the technique of applying an animation or storyboard directly to instances of an object, rather than in a style, control template, or data template.
Storyboard is begun using… | Per-instance | Style | Control template | Data template | Example |
---|---|---|---|---|---|
BeginStoryboard and an EventTrigger | Yes | Yes | Yes | Yes | |
BeginStoryboard and a property Trigger | No | Yes | Yes | Yes | |
No | Yes | Yes | Yes | ||
Begin method | Yes | No | No | No |
The following example uses a Storyboard to animate the Width of a Rectangle element and the Color of a SolidColorBrush used to paint that Rectangle.
<!-- 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; } } }
The following sections describe the TargetName and TargetProperty attached properties in more detail.
Targeting Framework Elements, Framework Content Elements, and Freezables
The previous section mentioned that, for an animation to find its target, it must know the target's name and the property to animate. Specifying the property to animate is straight forward: simply set Storyboard.TargetProperty with the name of the property to animate. You specify the name of the object whose property you want to animate by setting the Storyboard.TargetName property on the animation.
For the TargetName property to work, the targeted object must have a name. Assigning a name to a FrameworkElement or a FrameworkContentElement in XAML is different than assigning a name to a Freezable object.
Framework elements are those classes that inherit from the FrameworkElement class. Examples of framework elements include Window, DockPanel, Button, and Rectangle. Essentially all windows, panels, and controls are elements. Framework content elements are those classes that inherit from the FrameworkContentElement class. Examples of framework content elements include FlowDocument and Paragraph. If you're not sure whether a type is a framework element or a framework content element, check to see whether it has a Name property. If it does, it's probably a framework element or a framework content element. To be sure, check the Inheritance Hierarchy section of its type page.
To enable the targeting of a framework element or a framework content element in XAML, you set its Name property. In code, you also need to use the RegisterName method to register the element's name with the element for which you've created a NameScope.
The following example, taken from the preceding example, assigns the name MyRectangle a Rectangle, a type of FrameworkElement.
<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);
After it has a name, you can animate a property of that element.
<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 types are those classes that inherit from the Freezable class. Examples of Freezable include SolidColorBrush, RotateTransform, and GradientStop.
To enable the targeting of a Freezable by an animation in XAML, you use the x:Name Directive to assign it a name. In code, you use theRegisterName method to register its name with the element for which you've created a NameScope.
The following example assigns a name to a Freezable object.
<SolidColorBrush x:Name="MySolidColorBrush" Color="Blue" />
SolidColorBrush mySolidColorBrush = new SolidColorBrush(Colors.Blue); this.RegisterName("MySolidColorBrush", mySolidColorBrush);
The object can then be targeted by an animation.
<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 objects use name scopes to resolve the TargetName property. For more information about WPF name scopes, see WPF XAML Namescopes. If the TargetName property is omitted, the animation targets the element on which it is defined, or, in the case of styles, the styled element.
Sometimes a name can't be assigned to a Freezable object. For example, if a Freezable is declared as a resource or used to set a property value in a style, it can't be given a name. Because it doesn't have a name, it can't be targeted directly—but it can be targeted indirectly. The following sections describe how to use indirect targeting.
Indirect Targeting
There are times a Freezable can't be targeted directly by an animation, such as when the Freezable is declared as a resource or used to set a property value in a style. In these cases, even though you can't target it directly, you can still animate the Freezable object. Instead of setting the TargetNameproperty with the name of the Freezable, you give it the name of the element to which the Freezable "belongs." For example, a SolidColorBrush used to set the Fill of a rectangle element belongs to that rectangle. To animate the brush, you would set the animation's TargetProperty with a chain of properties that starts at the property of the framework element or framework content element the Freezable was used to set and ends with theFreezable property to animate.
<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);
Note that, if the Freezable is frozen, a clone will be made, and that clone will be animated. When this happens, the original object'sHasAnimatedProperties property continues to return false, because the original object is not actually animated. For more information about cloning, see the Freezable Objects Overview.
Also note that, when using indirect property targeting, it's possible to target objects that don't exist. For example, you might assume that theBackground of a particular button was set with a SolidColorBrush and try to animate its Color, when in fact a LinearGradientBrush was used to set the button's Background. In these cases, no exception is thrown; the animation fails to have a visible effect because LinearGradientBrush does not react to changes to the Color property.
The following sections describe indirect property targeting syntax in more detail.
Indirectly Targeting a Property of a Freezable in XAML
To target a property of a freezable in XAML, use the following syntax.
ElementPropertyName.FreezablePropertyName |
Where
ElementPropertyName is the property of the FrameworkElement which the Freezable is used to set, and
FreezablePropertyName is the property of the Freezable to animate.
The following code shows how to animate the Color of a SolidColorBrush used to set the
Fill of a rectangle element.
<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>
Sometimes you need to target a freezable contained in a collection or array.
To target a freezable contained in a collection, you use the following path syntax.
ElementPropertyName.Children[CollectionIndex].FreezablePropertyName |
Where CollectionIndex is the index of the object in its array or collection.
For example, suppose that a rectangle has a TransformGroup resource applied to its RenderTransform property, and you want to animate one of the transforms it contains.
<TransformGroup x:Key="MyTransformGroupResource" x:Shared="False"> <ScaleTransform /> <RotateTransform /> </TransformGroup>
The following code shows how to animate the Angle property of the RotateTransform shown in the previous example.
<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>
Indirectly Targeting a Property of a Freezable in Code
In code, you create a PropertyPath object. When you create the PropertyPath, you specify a Path and PathParameters.
To create PathParameters, you create an array of type DependencyProperty that contains a list of dependency property identifier fields. The first identifier field is for the property of the FrameworkElement or FrameworkContentElement that the Freezable is used to set. The next identifier field represents the property of the Freezable to target. Think of it as a chain of properties that connects the Freezable to the FrameworkElement object.
The following is an example of a dependency property chain that targets the Color of a SolidColorBrush used to set the Fill of a rectangle element.
DependencyProperty[] propertyChain =
new DependencyProperty[]
{Rectangle.FillProperty, SolidColorBrush.ColorProperty};
You also need to specify a Path. A Path is a String that tells the Path how to interpret its PathParameters. It uses the following syntax.
(OwnerPropertyArrayIndex).(FreezablePropertyArrayIndex) |
Where
OwnerPropertyArrayIndex is the index of the DependencyProperty array that contains the identifier of the FrameworkElement object's property that the Freezable is used to set, and
FreezablePropertyArrayIndex is the index of the DependencyProperty array that contains the identifier of property to target.
The following example shows the Path that would accompany the PathParameters defined in the preceding example .
DependencyProperty[] propertyChain = new DependencyProperty[] {Rectangle.FillProperty, SolidColorBrush.ColorProperty}; string thePath = "(0).(1)";
The following example combines the code in the previous examples to animate the Color of a SolidColorBrush used to set the Fill of a rectangle element.
// 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);
Sometimes you need to target a freezable contained in a collection or array. For example, suppose that a rectangle has a TransformGroup resource applied to its RenderTransform property, and you want to animate one of the transforms it contains.
<TransformGroup x:Key="MyTransformGroupResource" x:Shared="False"> <ScaleTransform /> <RotateTransform /> </TransformGroup>
To target a Freezable contained in a collection, you use the following path syntax.
(OwnerPropertyArrayIndex).(CollectionChildrenPropertyArrayIndex)[CollectionIndex].(FreezablePropertyArrayIndex) |
Where CollectionIndex is the index of the object in its array or collection.
To target the Angle property of the RotateTransform, the second transform in the TransformGroup, you would use the following Path andPathParameters.
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);
The following example shows the complete code for animating the Angle of a RotateTransform contained within a TransformGroup.
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);
Indirectly Targeting with a Freezable as the Starting Point
The previous sections described how to indirectly target a Freezable by starting with a FrameworkElement or FrameworkContentElement and creating a property chain to a Freezable sub-property. You can also use a Freezable as a starting point and indirectly target one of its Freezablesub-properties. One additional restriction applies when using a Freezable as a starting point for indirect targeting: the starting Freezable and everyFreezable between it and the indirectly targeted sub-property must not be frozen.
Interactively Controlling a Storyboard in XAML
To start a storyboard in Extensible Application Markup Language (XAML), you use a BeginStoryboard trigger action. BeginStoryboard distributes the animations to the objects and properties they animate, and starts the storyboard. (For details about this process, see the Animation and Timing System Overview.) If you give the BeginStoryboard a name by specifying its Name property, you make it a controllable storyboard. You can then interactively control the storyboard after it's started. The following is a list of controllable storyboard actions that you use with event triggers to control a storyboard.
PauseStoryboard: Pauses the storyboard.
ResumeStoryboard: Resumes a paused storyboard.
SetStoryboardSpeedRatio: Changes the storyboard's speed.
SkipStoryboardToFill: Advances a storyboard to the end of its fill period, if it has one.
StopStoryboard: Stops the storyboard.
RemoveStoryboard: Removes the storyboard.
In the following example, controllable storyboard actions are used to interactively control a storyboard.
<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>
Interactively Controlling a Storyboard by Using Code
The previous examples have shown how to animate using trigger actions. In code, you may also control a storyboard using interactive methods of the Storyboard class. For a Storyboard to be made interactive in code, you must use the appropriate overload of the storyboard's Begin method and specify true to make it controllable. See the Begin(FrameworkElement, Boolean) page for more information.
The following list shows the methods that can be used to manipulate a Storyboard after it has started:
The advantage to using these methods is that you don't need to create Trigger or TriggerAction objects; you just need a reference to the controllableStoryboard you want to manipulate.
Note: All interactive actions taken on a Clock, and therefore also on a Storyboard will occur on the next tick of the timing engine which will happen shortly before the next render. For example, if you use the Seek method to jump to another point in an animation, the property value does not change instantly, rather, the value changes on the next tick of the timing engine.
The following example shows how to apply and control animations using the interactive methods of the Storyboard class.
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); } } }
Animate in a Style
You can use Storyboard objects to define animations in a Style. Animating with a Storyboard in a Style is similar to using a Storyboard elsewhere, with the following three exceptions:
You don't specify a TargetName; the Storyboard always targets the element to which the Style is applied. To target Freezable objects, you must use indirect targeting. For more information about indirect targeting, see the Indirect Targeting section.
You can't specify a SourceName for an EventTrigger or a Trigger.
You can't use dynamic resource references or data binding expressions to set Storyboard or animation property values. That's because everything inside a Style must be thread-safe, and the timing system must FreezeStoryboard objects to make them thread-safe. A Storyboard cannot be frozen if it or its child timelines contain dynamic resource references or data binding expressions. For more information about freezing and other Freezable features, see the Freezable Objects Overview.
In XAML, you can't declare event handlers for Storyboard or animation events.
For an example showing how to define a storyboard in a style, see the How to: Animate in a Style example.
Animate in a ControlTemplate
You can use Storyboard objects to define animations in a ControlTemplate. Animating with a Storyboard in a ControlTemplate is similar to using aStoryboard elsewhere, with the following two exceptions:
The TargetName may only refer to child objects of the ControlTemplate. If TargetName is not specified, the animation targets the element to which the ControlTemplate is applied.
The SourceName for an EventTrigger or a Trigger may only refer to child objects of the ControlTemplate.
You can't use dynamic resource references or data binding expressions to set Storyboard or animation property values. That's because everything inside a ControlTemplate must be thread-safe, and the timing system must FreezeStoryboard objects to make them thread-safe. AStoryboard cannot be frozen if it or its child timelines contain dynamic resource references or data binding expressions. For more information about freezing and other Freezable features, see the Freezable Objects Overview.
In XAML, you can't declare event handlers for Storyboard or animation events.
For an example showing how to define a storyboard in a ControlTemplate, see the How to: Animate in a ControlTemplate example.
Animate When a Property Value Changes
In styles and control templates, you can use Trigger objects to start a storyboard when a property changes. For examples, see How to: Trigger an Animation When a Property Value Changes and How to: Animate in a ControlTemplate.
Animations applied by property Trigger objects behave in a more complex fashion than EventTrigger animations or animations started usingStoryboard methods. They "handoff" with animations defined by other Trigger objects, but compose with EventTrigger and method-triggered animations.
Timing Behaviors Overview
This topic describes the timing behaviors of animations and other Timeline objects.
Prerequisites
To understand this topic, you should be familiar with basic animation features. For more information, see the Animation Overview.
Timeline Types
A Timeline represents a segment of time. It provides properties that enable you to specify the length of that segment, when it should start, how many times it will repeat, how fast time progresses in that segment, and more.
Classes that inherit from the timeline class provide additional functionality, such as animation and media playback. WPF provides the followingTimeline types.
Timeline type | Description |
---|---|
Abstract base class for Timeline objects that generate output values for animating properties. | |
Generates output from a media file. | |
A type of TimelineGroup that groups and controls child Timeline objects. | |
A type of ParallelTimeline that provides targeting information for the Timeline objects it contains. | |
Abstract base class that defines timing behaviors. | |
Abstract class for Timeline objects that can contain other Timeline objects. |
Properties that Control the Length of a Timeline
A Timeline represents a segment of time, and the length of a timeline can be described in different ways. The following table defines several terms for describing the length of a timeline.
Term | Description | Properties | |||
---|---|---|---|---|---|
Simple duration | The length of time a timeline takes to make a single forward iteration. | ||||
One repetition | The length of time it takes for a timeline to play forward once and, if the AutoReverse property is true, play backwards once. | ||||
Active period | The length of time it takes for a timeline to complete all the repetitions specified by its RepeatBehaviorproperty. |
The Duration Property
As previously mentioned, a timeline represents a segment of time. The length of that segment is determined by the timeline's Duration. When a timeline reaches the end of its duration, it stops playing. If the timeline has child timelines, they stop playing as well. In the case of an animation, the Duration specifies how long the animation takes to transition from its starting value to its ending value. A timeline's duration is sometimes called its simple duration, to distinguish between the duration of a single iteration and the total length of time the animation plays including repetitions. You can specify a duration using a finite time value or the special values Automatic or Forever. An animation's duration should resolve to a TimeSpan value, so it can transition between values.
The following example shows a DoubleAnimation with a Duration of five seconds.
<DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:5" />
Container timelines, such as Storyboard and ParallelTimeline, have a default duration of Automatic, which means they automatically end when their last child stops playing. The following example shows a Storyboard whose Duration resolves to five seconds, the length of time it takes all its child DoubleAnimation objects to complete.
<Storyboard > <DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:5" /> <DoubleAnimation Storyboard.TargetName="MyOtherRectangle" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:3" /> </Storyboard>
By setting the Duration of a container timeline to a TimeSpan value, you can force to play longer or shorter than its child Timeline objects would play. If you set the Duration to a value that's smaller than the length of the container timeline's child Timeline objects, the child Timeline objects stop playing when the container timeline does. The following example sets the Duration of the Storyboard from the preceding examples to three seconds. As a result, the first DoubleAnimation stops progressing after three seconds, when it has animated the target rectangle's width to 60.
<Storyboard Duration="0:0:3"> <DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:5" /> <DoubleAnimation Storyboard.TargetName="MyOtherRectangle" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:3" /> </Storyboard>
The RepeatBehavior Property
The RepeatBehavior property of a Timeline controls how many times it repeats its simple duration. Using the RepeatBehavior property, you can specify how many times the timeline plays (an iteration Count) or the total length of time it should play (a repeat Duration). In either case, the animation goes through as many beginning-to-end runs as necessary to fill the requested count or duration. By default, timelines have an iteration count of 1.0, which means they play once and do not repeat at all.
The following example uses the RepeatBehavior property to make a DoubleAnimation play for twice its simple duration by specifying an iteration count.
<DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:5" RepeatBehavior="2x" />
The next example uses the RepeatBehavior property to make the DoubleAnimation play for half its simple duration.
<DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:5" RepeatBehavior="0.5x" />
If you set the RepeatBehavior property of a Timeline to Forever, the Timeline repeats until stopped interactively or by the timing system. The following example uses the RepeatBehavior property to make the DoubleAnimation play indefinitely.
<DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:5" RepeatBehavior="Forever" />
For an additional example, see How to: Repeat an Animation.
The AutoReverse Property
The AutoReverse property specifies whether a Timeline will play backwards at the end of each forward iteration. The following example sets to theAutoReverse property of a DoubleAnimation to true; as a result, it animates from zero to 100, and then from 100 to zero. It plays for a total of 10 seconds.
<DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:5" AutoReverse="True" />
When you use a Count value to specify the RepeatBehavior of a Timeline and the AutoReverse property of that Timeline is true, a single repetition consists of one forward iteration followed by one backwards iteration. The following example sets the RepeatBehavior of the DoubleAnimationfrom the preceding example to a Count of two. As a result, the DoubleAnimation plays for 20 seconds: forward for five seconds, backwards for five seconds, forward for 5 seconds again, and then backwards for five seconds.
<DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:5" RepeatBehavior="2" AutoReverse="True" />
If a container timeline has child Timeline objects, they reverse when the container timeline does. For additional examples, see How to: Specify Whether a Timeline Automatically Reverses.
The BeginTime Property
The BeginTime property enables you to specify when a timeline starts. A timeline's begin time is relative to its parent timeline. A begin time of zero seconds means the timeline starts as soon as it parent starts; any other value creates an offset between when the parent timeline starts playing and when the child timeline plays. For example, a begin time of two seconds means the timeline starts playing when its parent has reached a time of two seconds. By default, all timelines have a begin time of zero seconds. You may also set a timeline's begin time to null, which prevents the timeline from starting. In WPF, you specify null using the x:Null Markup Extension.
Note that the begin time is not applied each time a timeline repeats because of its RepeatBehavior setting. If you were to create an animation with aBeginTime of 10 seconds and a RepeatBehavior of Forever, there would be a 10-second delay before the animation played for the first time, but not for each successive repetition. However, if the animation's parent timeline were to restart or repeat, the 10-second delay would occur.
The BeginTime property is useful for staggering timelines. The following example creates a Storyboard that has two child DoubleAnimation objects. The first animation has a Duration of five seconds, and the second has a Duration of 3 seconds. The example sets the BeginTime of the secondDoubleAnimation to 5 seconds, so that it begins playing after the first DoubleAnimation ends.
<Storyboard> <DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:5" BeginTime="0:0:0" /> <DoubleAnimation Storyboard.TargetName="MyOtherRectangle" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:3" BeginTime="0:0:5" /> </Storyboard>
The FillBehavior Property
When a Timeline reaches the end of its total active duration, the FillBehavior property specifies whether it stops or holds its last value. An animation with a FillBehavior of HoldEnd "holds" its output value: the property being animated retains the last value of the animation. A value of Stop causes that the animation stop affecting its target property after it ends.
The following example creates a Storyboard that has two child DoubleAnimation objects. Both DoubleAnimation objects animate the Width of aRectangle from 0 to 100. The Rectangle elements have non-animated Width values of 500 device independent pixels.
The FillBehavior property of the first DoubleAnimation is set to HoldEnd, the default value. As a result, the Width of the Rectangle stays at 100 after the DoubleAnimation ends.
The FillBehavior property of the second DoubleAnimation is set to Stop. As a result, the Width of the second Rectangle reverts to 500 after theDoubleAnimation ends.
<Rectangle Name="MyRectangle" Width="500" Height="100" Opacity="1" Fill="Red"> </Rectangle> <Rectangle Name="MyOtherRectangle" Width="500" Height="100" Opacity="1" Fill="Orange"> </Rectangle> <Button Content="Start FillBehavior Example"> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:5" FillBehavior="HoldEnd" /> <DoubleAnimation Storyboard.TargetName="MyOtherRectangle" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:5" FillBehavior="Stop" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button>
Properties that Control the Speed of a Timeline
The Timeline class provides three properties for specifying its speed:
SpeedRatio – Specifies that rate, relative to its parent, at which time progresses for a Timeline. Values greater than one increase the speed of the Timeline and its child Timeline objects; values between zero and one slow it down. A value of one indicates that Timeline progresses at the same rate as its parent. The SpeedRatio setting of a container timeline affects all of its child Timeline objects as well.
AccelerationRatio – Specifies the percentage of the Duration of a Timeline spent accelerating. For an example, see How to: Accelerate or Decelerate an Animation.
DecelerationRatio - Specifies the percentage of the Duration of a Timeline spent decelerating. For an example, see How to: Accelerate or Decelerate an Animation.
Timing Events Overview
This topic describes how to use the five timing events available on Timeline and Clock objects.
Prerequisites
To understand this topic, you should understand how to create and use animations. To get started with animation, see the Animation Overview.
There are multiple ways to animate properties in WPF:
Using storyboard objects (markup and code): You can use Storyboard objects to arrange and distribute animations to one or more objects. For an example, see How to: Animate a Property by Using a Storyboard.
Using local animations (code only): You can apply AnimationTimeline objects directly to the properties they animate. For an example, seeHow to: Animate a Property Without Using a Storyboard.
Using clocks (code only): You can explicitly manage clock creation and distribute the animation clocks yourself. For an example, see How to: Animate a Property by Using an AnimationClock.
Because you can use them in markup and code, the examples in this overview use Storyboard objects. However, the concepts described can be applied to the other methods of animating properties.
What is a clock?
A timeline, by itself, doesn't actually do anything other than describe a segment of time. It's the timeline's Clock object that does the real work: it maintains timing-related run-time state for the timeline. In most cases, such as when using storyboards, a clock is created automatically for your timeline. You can also create a Clock explicitly by using the CreateClock method. For more information about Clock objects, see the Animation and Timing System Overview.
Why Use Events?
With the exception of one (seek aligned to last tick), all interactive timing operations are asynchronous. There is no way for you to know exactly when they will execute. That can be a problem when you have other code that's dependent upon your timing operation. Suppose that you wanted to stop a timeline that animated a rectangle. After the timeline stops, you change the color of the rectangle.
myStoryboard.Stop(myRectangle); // This statement might execute // before the storyboard has stopped. myRectangle.Fill = Brushes.Blue;
In the previous example, the second line of code might execute before the storyboard stops. That's because stopping is an asynchronous operation. Telling a timeline or clock to stop creates a "stop request" of sorts that isn't processed until the timing engine's next tick.
To execute commands after a timeline completes, use timing events. In the following example, an event handler is used to change the color of a rectangle after the storyboard stops playing.
// Register for the CurrentStateInvalidated timing event. myStoryboard.CurrentStateInvalidated += new EventHandler(myStoryboard_CurrentStateInvalidated);
// Change the rectangle's color after the storyboard stops. void myStoryboard_CurrentStateInvalidated(object sender, EventArgs e) { Clock myStoryboardClock = (Clock)sender; if (myStoryboardClock.CurrentState == ClockState.Stopped) { myRectangle.Fill = Brushes.Blue; } }
For a more complete example, see How to: Receive Notification When a Clock's State Changes.
Public Events
The Timeline and Clock classes both provide five timing events. The following table lists these events and the conditions that trigger them.
Event | Triggering interactive operation | Other triggers |
---|---|---|
Completed | Skip to fill | The clock completes. |
CurrentGlobalSpeedInvalidated | Pause, resume, seek, set speed ratio, skip to fill, stop | The clock reverses, accelerates, starts, or stops. |
CurrentStateInvalidated | Begin, skip to fill, stop | The clock starts, stops, or fills. |
CurrentTimeInvalidated | Begin, seek, skip to fill, stop | The clock progresses. |
RemoveRequested | Remove |
Ticking and Event Consolidation
When you animate objects in WPF, it’s the timing engine that manages your animations. The timing engine tracks the progression of time and computes the state of each animation. It makes many such evaluation passes in a second. These evaluation passes are known as "ticks."
While ticks occur frequently, it's possible for a lot of things to happen between ticks. For example, a timeline might be stopped, started, and stopped again, in which case its current state will have changed three times. In theory, the event could be raised multiple times in a single tick; however, the timing engine consolidates events, so that each event can be raised at most once per tick.
Registering for Events
There are two ways to register for timing events: you can register with the timeline or with the clock created from the timeline. Registering for an event directly with a clock is fairly straightforward, although it can only be done from code. You can register for events with a timeline from markup or code. The next section describes how to register for clock events with a timeline.
Registering for Clock Events with a Timeline
Although a timeline's Completed, CurrentGlobalSpeedInvalidated, CurrentStateInvalidated, CurrentTimeInvalidated, and RemoveRequested events appear to be associated with the timeline, registering for these events actually associates an event handler with the Clock created for the timeline.
When you register for the Completed event on a timeline, for example, you're actually telling the system to register for the Completed event of each clock that is created for the timeline. In code, you must register for this event before the Clock is created for this timeline; otherwise, you won't receive notification. This happens automatically in XAML; the parser automatically registers for the event before the Clock is created.
How-to Topics
How to: Accelerate or Decelerate an Animation
This example demonstrates how to make an animation accelerate and decelerate over time. In the following example, several rectangles are animated by animations with different AccelerationRatio and DecelerationRatio settings.
Example
<!-- This example shows how to use the AccelerationRatio and DecelerationRatio properties of timelines to make animations speed up or slow down as they progress. --> <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:SampleControls="SampleControls" WindowTitle="Acceleration and Deceleration Example"> <StackPanel Margin="20"> <Rectangle Name="nonAcceleratedOrDeceleratedRectangle" Fill="#9933FF" Width="10" Height="20" HorizontalAlignment="Left" /> <Rectangle Name="acceleratedRectangle" Fill="#3333FF" Width="10" Height="20" HorizontalAlignment="Left" /> <Rectangle Name="deceleratedRectangle" Fill="#33FF66" Width="10" Height="20" HorizontalAlignment="Left" /> <Rectangle Name="acceleratedAndDeceleratedRectangle" Fill="#CCFF33" Width="10" Height="20" HorizontalAlignment="Left" /> <!-- Create a button to start the animations. --> <Button Margin="0,30,0,0" HorizontalAlignment="Left" Content="Start Animations"> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <!-- Creates an animation without acceleration or deceleration for comparison. --> <DoubleAnimation Storyboard.TargetName="nonAcceleratedOrDeceleratedRectangle" Storyboard.TargetProperty="(Rectangle.Width)" Duration="0:0:10" From="20" To="400" /> <!-- Creates an animation that accelerates through 40% of its duration. --> <DoubleAnimation Storyboard.TargetName="acceleratedRectangle" Storyboard.TargetProperty="(Rectangle.Width)" AccelerationRatio="0.4" Duration="0:0:10" From="20" To="400" /> <!-- Creates an animation that decelerates through 60% of its duration. --> <DoubleAnimation Storyboard.TargetName="deceleratedRectangle" Storyboard.TargetProperty="(Rectangle.Width)" DecelerationRatio="0.6" Duration="0:0:10" From="20" To="400" /> <!-- Creates an animation that accelerates through 40% of its duration and decelerates through the 60% of its duration. --> <DoubleAnimation Storyboard.TargetName="acceleratedAndDeceleratedRectangle" Storyboard.TargetProperty="(Rectangle.Width)" AccelerationRatio="0.4" DecelerationRatio="0.6" Duration="0:0:10" From="20" To="400" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> </StackPanel> </Page>
Code has been omitted from this example. For the complete code, see the Animation Timing Behavior Sample.
How to: Accumulate Animation Values During Repeat Cycles
This example shows how to use the IsCumulative property to accumulate animation values across repeating cycles.
Example
Use the IsCumulative property to accumulate base values of an animation across repeating cycles. For example, if you set an animation to repeat 9 times (RepeatBehavior = "9x") and you set the property to animate between 10 and 15 (From = 10 To = 15), the property animates from 10 to 15 during the first cycle, from 15 to 20 during the second cycle, from 20 to 25 during the third cycle, and so on. Hence, each animation cycle uses the ending animation value from the previous animation cycle as its base value.
You can use the IsCumulative property with most basic animations and most key frame animations. For more information, see Animation Overview and Key-Frame Animations Overview.
The following example shows this behavior by animating the width of four rectangles. The example:
Animates the first rectangle with DoubleAnimation and sets the IsCumulative property to true.
Animates the second rectangle with DoubleAnimation and sets the IsCumulative property to the default value of false.
Animates the third rectangle with DoubleAnimationUsingKeyFrames and sets the IsCumulative property to true.
Animates the last rectangle with DoubleAnimationUsingKeyFrames and sets the IsCumulative property to false.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel Margin="20" > <!-- This rectangle is animated with DoubleAnimation and IsCumulative set to "True". --> <Rectangle Name="withIsCumulative" Width="100" Height="20" Margin="12,0,0,5" Fill="#AA3333FF" HorizontalAlignment="Left" /> <!-- This rectangle is animated with DoubleAnimation and IsCumulative set to "False". --> <Rectangle Name="withoutIsCumulative" Width="100" Height="20" Margin="12,0,0,5" Fill="#AA3333FF" HorizontalAlignment="Left" /> <!-- This rectangle is animated with DoubleAnimationUsingKeyFrames and IsCumulative set to "True". --> <Rectangle Name="withIsCumulativeUsingKeyFrames" Width="100" Height="20" Margin="12,0,0,5" Fill="#AA3333FF" HorizontalAlignment="Left" /> <!-- This rectangle is animated with DoubleAnimationUsingKeyFrames and IsCumulative set to "False". --> <Rectangle Name="withoutIsCumulativeUsingKeyFrames" Width="100" Height="20" Margin="12,0,0,5" Fill="#AA3333FF" HorizontalAlignment="Left" /> <!-- Create a button to restart the animations. --> <Button Margin="0,30,0,0" HorizontalAlignment="Left"> Restart Animations <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <!-- DoubleAnimation with IsCumulative set to "True". Because IsCumulative is set to "True", the base values of the animation will accumulate over repeat cycles. In this example, the first iteration will be from 100 to 200, the second will be from 200 to 300, the third from 300 to 400, etc. --> <DoubleAnimation Storyboard.TargetName="withIsCumulative" Storyboard.TargetProperty="Width" RepeatBehavior="4x" AutoReverse="True" IsCumulative="True" Duration="0:0:3" From="100" To="200" /> <!-- DoubleAnimation with IsCumulative set to "False". The starting value 100 pixels and repeat cycles do not build on earlier ones. --> <DoubleAnimation Storyboard.TargetName="withoutIsCumulative" Storyboard.TargetProperty="Width" RepeatBehavior="4x" AutoReverse="True" IsCumulative="False" Duration="0:0:3" From="100" To="200" /> <!-- DoubleAnimationUsingKeyFrames with IsCumulative set to "True". Similar to the DoubleAnimation above, the base value of each cycle builds on the last one. Note that the output value is the total output value from all the key frames for a total output of 100 pixels. --> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="withIsCumulativeUsingKeyFrames" Storyboard.TargetProperty="Width" RepeatBehavior="4x" AutoReverse="True" IsCumulative="True" > <DoubleAnimationUsingKeyFrames.KeyFrames> <LinearDoubleKeyFrame Value="100" KeyTime="0:0:0" /> <LinearDoubleKeyFrame Value="130" KeyTime="0:0:1" /> <SplineDoubleKeyFrame KeySpline="0.6,0.0 0.9,0.00" Value="200" KeyTime="0:0:3" /> </DoubleAnimationUsingKeyFrames.KeyFrames> </DoubleAnimationUsingKeyFrames> <!-- DoubleAnimationUsingKeyFrames with IsCumulative set to "False". The base value of each cycle does not build on the last one. --> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="withoutIsCumulativeUsingKeyFrames" Storyboard.TargetProperty="Width" RepeatBehavior="4x" AutoReverse="True" IsCumulative="False" > <DoubleAnimationUsingKeyFrames.KeyFrames> <LinearDoubleKeyFrame Value="100" KeyTime="0:0:0" /> <LinearDoubleKeyFrame Value="130" KeyTime="0:0:1" /> <SplineDoubleKeyFrame KeySpline="0.6,0.0 0.9,0.00" Value="200" KeyTime="0:0:3" /> </DoubleAnimationUsingKeyFrames.KeyFrames> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> </StackPanel> </Page>
How to: Add an Animation Output Value to an Animation Starting Value
This example shows how to add an animation output value to an animation starting value.
Example
The IsAdditive property specifies whether you want the output value of an animation added to the starting value (base value) of an animated property. You can use the IsAdditive property with most basic animations and most key frame animations. For more information, see Animation Overview and Key-Frame Animations Overview.
The following example shows the effect of using the DoubleAnimation.IsAdditive property with DoubleAnimation and using the DoubleAnimationUsingKeyFrames.IsAdditive property with DoubleAnimationUsingKeyFrames.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel Margin="20" > <!-- This rectangle is animated with DoubleAnimation and IsAdditive set to "True". --> <Rectangle Name="withIsAdditive" Width="100" Height="20" Margin="12,0,0,5" Fill="#AA3333FF" HorizontalAlignment="Left" /> <!-- This rectangle is animated with DoubleAnimation and IsAdditive set to "False". --> <Rectangle Name="withoutIsAdditive" Width="100" Height="20" Margin="12,0,0,5" Fill="#AA3333FF" HorizontalAlignment="Left" /> <!-- This rectangle is animated with DoubleAnimationUsingKeyFrames and IsAdditive set to "True". --> <Rectangle Name="withIsAdditiveUsingKeyFrames" Width="100" Height="20" Margin="12,0,0,5" Fill="#AA3333FF" HorizontalAlignment="Left" /> <!-- This rectangle is animated with DoubleAnimationUsingKeyFrames and IsAdditive set to "False". --> <Rectangle Name="withoutIsAdditiveUsingKeyFrames" Width="100" Height="20" Margin="12,0,0,5" Fill="#AA3333FF" HorizontalAlignment="Left" /> <!-- Create a button to restart the animations. --> <Button Margin="0,30,0,0" HorizontalAlignment="Left"> Restart Animations <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <!-- DoubleAnimation with IsAdditive set to "True". Because IsAdditive is set to "True" the actual starting value of the animation is equal to the sum of the default starting value of 100 (From="100)and the animation output value of 100 (From="100" To="200") Therefore the animation begins at 200 pixels. Notice that each time the button is clicked and the animation is initiated, the animation starting value builds upon the preceeding ending value. --> <DoubleAnimation Storyboard.TargetName="withIsAdditive" Storyboard.TargetProperty="Width" Duration="0:0:3" From="100" To="200" IsAdditive="True" /> <!-- DoubleAnimation with IsAdditive set to "False". The starting value is the default starting value of 100 pixels and subsequent animations do not build on earlier ones. --> <DoubleAnimation Storyboard.TargetName="withoutIsAdditive" Storyboard.TargetProperty="Width" Duration="0:0:3" From="100" To="200" IsAdditive="False" /> <!-- DoubleAnimationUsingKeyFrames with IsAdditive set to "True". Similar to the DoubleAnimation above, this animation adds its output value to its starting value. Note that the output value is the total output value from all the key frames for a total output of 100 pixels. --> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="withIsAdditiveUsingKeyFrames" Storyboard.TargetProperty="Width" IsAdditive="True" > <DoubleAnimationUsingKeyFrames.KeyFrames> <LinearDoubleKeyFrame Value="100" KeyTime="0:0:0" /> <LinearDoubleKeyFrame Value="130" KeyTime="0:0:1" /> <SplineDoubleKeyFrame KeySpline="0.6,0.0 0.9,0.00" Value="200" KeyTime="0:0:3" /> </DoubleAnimationUsingKeyFrames.KeyFrames> </DoubleAnimationUsingKeyFrames> <!-- DoubleAnimationUsingKeyFrames with IsAdditive set to "False". The starting value is the default starting value of 100 pixels and subsequent animations do not build on earlier ones. --> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="withoutIsAdditiveUsingKeyFrames" Storyboard.TargetProperty="Width" IsAdditive="False" > <DoubleAnimationUsingKeyFrames.KeyFrames> <LinearDoubleKeyFrame Value="100" KeyTime="0:0:0" /> <LinearDoubleKeyFrame Value="130" KeyTime="0:0:1" /> <SplineDoubleKeyFrame KeySpline="0.6,0.0 0.9,0.00" Value="200" KeyTime="0:0:3" /> </DoubleAnimationUsingKeyFrames.KeyFrames> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> </StackPanel> </Page>
How to: Animate a Property by Using a Storyboard
This example shows how to use a Storyboard to animate properties. To animate a property by using a Storyboard, create an animation for each property that you want to animate and also create a Storyboard to contain the animations.
The type of property determines the type of animation to use. For example, to animate a property that takes Double values, use a DoubleAnimation. The TargetName and TargetProperty attached properties specify the object and property to which the animation is applied.
To start a storyboard in Extensible Application Markup Language (XAML), use a BeginStoryboard action and an EventTrigger. The EventTrigger begins the BeginStoryboard action when the event that is specified by its RoutedEvent property occurs. The BeginStoryboard action starts the Storyboard.
The following example uses Storyboard objects to animate two Button controls. To make the first button change in size, its Width is animated. To make the second button change color, the Color property of the SolidColorBrush is used to set the Background of the button that is animated.
Example
<!-- StoryboardExample.xaml Uses storyboards to animate properties. --> <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowTitle="Animate Properties with Storyboards"> <Border Background="White"> <StackPanel Margin="30" HorizontalAlignment="Left" MinWidth="500"> <TextBlock>Storyboard Animation Example</TextBlock> <!-- The width of this button is animated. --> <Button Name="myWidthAnimatedButton" Height="30" Width="200" HorizontalAlignment="Left"> A Button <Button.Triggers> <!-- Animates the width of the first button from 200 to 300. --> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="myWidthAnimatedButton" Storyboard.TargetProperty="Width" From="200" To="300" Duration="0:0:3" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> <!-- The color of the brush used to paint this button is animated. --> <Button Height="30" Width="200" HorizontalAlignment="Left">Another Button <Button.Background> <SolidColorBrush x:Name="myAnimatedBrush" Color="Blue" /> </Button.Background> <Button.Triggers> <!-- Animates the color of the brush used to paint the second button from red to blue . --> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetName="myAnimatedBrush" Storyboard.TargetProperty="Color" From="Red" To="Blue" Duration="0:0:7" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> </StackPanel> </Border> </Page>
Note |
---|
Although animations can target both a FrameworkElement object, such as a Control or Panel, and a Freezable object, such as a Brush or Transform, only framework elements have a Name property. To assign a name to a freezable so that it can be targeted by an animation, use the x:Name Directive, as the previous example shows. |
If you use code, you must create a NameScope for a FrameworkElement and register the names of the objects to animate with that FrameworkElement. To start the animations in code, use a BeginStoryboard action with an EventTrigger. Optionally, you can use an event handler and the Begin method of Storyboard. The following example shows how to use the Begin method.
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; namespace Microsoft.Samples.Animation.AnimatingWithStoryboards { // Uses a storyboard to animate the properties // of two buttons. public class StoryboardExample : Page { public StoryboardExample() { // Create a name scope for the page. NameScope.SetNameScope(this, new NameScope()); this.WindowTitle = "Animate Properties using Storyboards"; StackPanel myStackPanel = new StackPanel(); myStackPanel.MinWidth = 500; myStackPanel.Margin = new Thickness(30); myStackPanel.HorizontalAlignment = HorizontalAlignment.Left; TextBlock myTextBlock = new TextBlock(); myTextBlock.Text = "Storyboard Animation Example"; myStackPanel.Children.Add(myTextBlock); // // Create and animate the first button. // // Create a button. Button myWidthAnimatedButton = new Button(); myWidthAnimatedButton.Height = 30; myWidthAnimatedButton.Width = 200; myWidthAnimatedButton.HorizontalAlignment = HorizontalAlignment.Left; myWidthAnimatedButton.Content = "A Button"; // Set the Name of the button so that it can be referred // to in the storyboard that's created later. // The ID doesn't have to match the variable name; // it can be any unique identifier. myWidthAnimatedButton.Name = "myWidthAnimatedButton"; // Register the name with the page to which the button belongs. this.RegisterName(myWidthAnimatedButton.Name, myWidthAnimatedButton); // Create a DoubleAnimation to animate the width of the button. DoubleAnimation myDoubleAnimation = new DoubleAnimation(); myDoubleAnimation.From = 200; myDoubleAnimation.To = 300; myDoubleAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(3000)); // Configure the animation to target the button's Width property. Storyboard.SetTargetName(myDoubleAnimation, myWidthAnimatedButton.Name); Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Button.WidthProperty)); // Create a storyboard to contain the animation. Storyboard myWidthAnimatedButtonStoryboard = new Storyboard(); myWidthAnimatedButtonStoryboard.Children.Add(myDoubleAnimation); // Animate the button width when it's clicked. myWidthAnimatedButton.Click += delegate(object sender, RoutedEventArgs args) { myWidthAnimatedButtonStoryboard.Begin(myWidthAnimatedButton); }; myStackPanel.Children.Add(myWidthAnimatedButton); // // Create and animate the second button. // // Create a second button. Button myColorAnimatedButton = new Button(); myColorAnimatedButton.Height = 30; myColorAnimatedButton.Width = 200; myColorAnimatedButton.HorizontalAlignment = HorizontalAlignment.Left; myColorAnimatedButton.Content = "Another Button"; // Create a SolidColorBrush to paint the button's background. SolidColorBrush myBackgroundBrush = new SolidColorBrush(); myBackgroundBrush.Color = Colors.Blue; // Because a Brush isn't a FrameworkElement, it doesn't // have a Name property to set. Instead, you just // register a name for the SolidColorBrush with // the page where it's used. this.RegisterName("myAnimatedBrush", myBackgroundBrush); // Use the brush to paint the background of the button. myColorAnimatedButton.Background = myBackgroundBrush; // Create a ColorAnimation to animate the button's background. ColorAnimation myColorAnimation = new ColorAnimation(); myColorAnimation.From = Colors.Red; myColorAnimation.To = Colors.Blue; myColorAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(7000)); // Configure the animation to target the brush's Color property. Storyboard.SetTargetName(myColorAnimation, "myAnimatedBrush"); Storyboard.SetTargetProperty(myColorAnimation, new PropertyPath(SolidColorBrush.ColorProperty)); // Create a storyboard to contain the animation. Storyboard myColorAnimatedButtonStoryboard = new Storyboard(); myColorAnimatedButtonStoryboard.Children.Add(myColorAnimation); // Animate the button background color when it's clicked. myColorAnimatedButton.Click += delegate(object sender, RoutedEventArgs args) { myColorAnimatedButtonStoryboard.Begin(myColorAnimatedButton); }; myStackPanel.Children.Add(myColorAnimatedButton); this.Content = myStackPanel; } } }
For more information about animation and storyboards, see Animation Overview.
If you use code, you are not limited to using Storyboard objects in order to animate properties. For more information and examples, see How to: Animate a Property Without Using a Storyboard and How to: Animate a Property by Using an AnimationClock.
How to: Animate in a ControlTemplate
This example shows how to use Storyboard, EventTrigger, and Trigger objects to animate within a ControlTemplate.
Example
<!-- ControlStoryboardExample.xaml Uses storyboards to animate properties with a ControlTemplate. --> <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowTitle="Animate Properties with Storyboards"> <Page.Resources> <ControlTemplate x:Key="MyControlTemplate" TargetType="{x:Type ContentControl}"> <Border Margin="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"> <Border Name="innerBorder" Padding="10"> <Border.Background> <SolidColorBrush x:Name="innerBorderBackgroundBrush" Color="White" /> </Border.Background> <Grid Name="contentPanel"> <Grid.Background> <SolidColorBrush x:Name="contentPanelBrush" Color="White" /> </Grid.Background> <ContentPresenter Margin="10" Content="{TemplateBinding Content}" TextBlock.Foreground="{TemplateBinding Foreground}" /> </Grid> </Border> </Border> <ControlTemplate.Triggers> <!-- Event Trigger Example --> <EventTrigger RoutedEvent="Border.MouseEnter" SourceName="innerBorder"> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetName="innerBorderBackgroundBrush" Storyboard.TargetProperty="Color" From="White" To="#CCCCFF" Duration="0:0:3" AutoReverse="True" /> </Storyboard> </BeginStoryboard> </EventTrigger> <!-- Property Trigger Example --> <Trigger Property="IsMouseOver" Value="True" SourceName="contentPanel"> <Trigger.EnterActions> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetName="contentPanelBrush" Storyboard.TargetProperty="Color" To="Purple" Duration="0:0:1" /> </Storyboard> </BeginStoryboard> </Trigger.EnterActions> <Trigger.ExitActions> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetName="contentPanelBrush" Storyboard.TargetProperty="Color" To="White" Duration="0:0:1" /> </Storyboard> </BeginStoryboard> </Trigger.ExitActions> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Page.Resources> <Border Background="White"> <StackPanel Margin="30" HorizontalAlignment="Left" MinWidth="500"> <ContentControl Template="{StaticResource MyControlTemplate}" Content="Hello, World" /> </StackPanel> </Border> </Page>
How to: Animate in a Style
This example shows how to animate properties within a style. When animating within a style, only the framework element for which the style is defined can be targeted directly. To target a freezable object, you must "dot down" from a property of the styled element.
In the following example, several animations are defined within a style and applied to a Button. When the user moves the mouse over the button, it fades from opaque to partially translucent and back again, repeatedly. When the user moves the mouse off the button, it becomes completely opaque. When the button is clicked, its background color changes from orange to white and back again. Because the SolidColorBrush used to paint the button can't be targeted directly, it is accessed by dotting down from the button's Background property.
Example
<!-- StyleStoryboardsExample.xaml This example shows how to create storyboards in a style. --> <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowTitle="Storyboards in Styles Example" Background="White"> <Page.Resources> <!-- Defines a Button style. --> <Style TargetType="{x:Type Button}" x:Key="MyButtonStyle"> <Setter Property="Button.Background"> <Setter.Value> <SolidColorBrush Color="Orange" /> </Setter.Value> </Setter> <Style.Triggers> <!-- Animates the button's opacity on mouse over. --> <EventTrigger RoutedEvent="Button.MouseEnter"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="(Button.Opacity)" From="1.0" To="0.5" Duration="0:0:0.5" AutoReverse="True" RepeatBehavior="Forever" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <!-- Returns the button's opacity to 1 when the mouse leaves. --> <EventTrigger RoutedEvent="Button.MouseLeave"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="(Button.Opacity)" To="1" Duration="0:0:0.1" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <!-- Changes the button's color when clicked. Notice that the animation can't target the SolidColorBrush used to paint the button's background directly. The brush must be accessed through the button's Background property. --> <EventTrigger RoutedEvent="Button.Click"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)" From="Orange" To="White" Duration="0:0:0.1" AutoReverse="True" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style> </Page.Resources> <StackPanel Margin="20"> <Button Style="{StaticResource MyButtonStyle}">Click Me</Button> </StackPanel> </Page>
Note that when animating within a style, it's possible to target objects that don't exist. For example, suppose your style uses a SolidColorBrush to set a Button's background property, but at some point the style is overridden and the button's background is set with a LinearGradientBrush. Trying to animate the SolidColorBrush won't throw an exception; the animation will simply fail silently.
Fore more information about storyboard targeting syntax, see the Storyboards Overview. For more information about animation, see the Animation Overview. For more information about styles, see the Styling and Templating.
How to: Animate the Opacity of an Element or Brush
To make a framework element fade in and out of view, you can animate its Opacity property or you can animate the Opacity property of the Brush (or brushes) used to paint it. Animating the element's opacity makes it and its children fade in and out of view, but animating the brush used to paint the element enables you to be more selective about which portion of the element fades. For example, you could animate the opacity of a brush used to paint a button's background. This would cause the button's background to fade in and out of view, while leaving its text fully opaque.
In the following example, two buttons are animated so that they fade in and out of view. The Opacity of the first Button is animated from 1.0 to 0.0over a Duration of five seconds. The second button is also animated, but the Opacity of the SolidColorBrush used to paint its Background is animated rather than the opacity of the entire button. When the example is run, the first button completely fades in and out of view, while only the background of the second button fades in and out of view. Its text and border remain fully opaque.
Example
<!-- OpacityAnimationExample.xaml This example shows how to animate the opacity of objects, making them fade in and out of view. --> <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowTitle="Opacity Animation Example" Background="White"> <StackPanel Margin="20"> <!-- Clicking this button animates its opacity. --> <Button Name="opacityAnimatedButton"> A Button <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="opacityAnimatedButton" Storyboard.TargetProperty="(Button.Opacity)" From="1" To="0" Duration="0:0:5" AutoReverse="True" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> <!-- Clicking this button animates the opacity of the brush used to paint its background. --> <Button> A Button <Button.Background> <SolidColorBrush x:Name="MyAnimatedBrush" Color="Orange" /> </Button.Background> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="MyAnimatedBrush" Storyboard.TargetProperty="(Brush.Opacity)" From="1" To="0" Duration="0:0:5" AutoReverse="True" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> </StackPanel> </Page>
Code has been omitted from this example. The full sample also shows how to animate the opacity of a Color within a LinearGradientBrush. For the full sample, see the Animating the Opacity of an Element Sample.
How to: Change the Speed of a Clock Without Changing the Speed of Its Timeline
A ClockController object's SpeedRatio property enables you to change the speed of a Clock without altering the SpeedRatio of the clock's Timeline. In the following example, a ClockController is used to interactively modify the SpeedRatio of a clock. The CurrentGlobalSpeedInvalidated event and the clock's CurrentGlobalSpeed property are used to display the clock's current global speed each time its interactive SpeedRatio is changed.
Example
/* This example shows how to interactively control the speed of a clock */ using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; using System.Windows.Media.Animation; namespace Microsoft.Samples.Animation.TimingBehaviors { public class ClockControllerSpeedRatioExample : Page { private AnimationClock myControllableClock; private Button speedRatioButton; private TextBox speedRatioSettingTextBox; private double doubleParseResult = 1; private TextBlock currentGlobalSpeedTextBlock; public ClockControllerSpeedRatioExample() { StackPanel mainPanel = new StackPanel(); // Create a rectangle to animate. Rectangle animatedRectangle = new Rectangle(); animatedRectangle.Width = 100; animatedRectangle.Height = 100; animatedRectangle.Fill = Brushes.Orange; mainPanel.Children.Add(animatedRectangle); // Create a DoubleAnimation to // animate its width. DoubleAnimation widthAnimation = new DoubleAnimation( 100, 500, new Duration(TimeSpan.FromSeconds(5))); //widthAnimation.RepeatBehavior = RepeatBehavior.Forever; widthAnimation.AutoReverse = true; widthAnimation.SpeedRatio = 0.5; // Create a clock from the animation. myControllableClock = widthAnimation.CreateClock(); // Apply the clock to the rectangle's Width property. animatedRectangle.ApplyAnimationClock( Rectangle.WidthProperty, myControllableClock); // // Create some controls the enable the user to // interactively control the SpeedRatio of the clock. // StackPanel speedRatioDetailsPanel = new StackPanel(); speedRatioDetailsPanel.Margin = new Thickness(0,20,0,20); speedRatioDetailsPanel.Orientation = Orientation.Horizontal; Label speedRatioLabel = new Label(); speedRatioLabel.Content = "Speed Ratio:"; speedRatioDetailsPanel.Children.Add(speedRatioLabel); // Create a text box so that the user can // specify the amount by which to seek. speedRatioSettingTextBox = new TextBox(); speedRatioSettingTextBox.Text = myControllableClock.Controller.SpeedRatio.ToString(); speedRatioSettingTextBox.VerticalAlignment = VerticalAlignment.Top; speedRatioSettingTextBox.TextChanged += new TextChangedEventHandler(seekAmountTextBox_TextChanged); speedRatioDetailsPanel.Children.Add(speedRatioSettingTextBox); // Create a button to apply SpeedRatio changes. speedRatioButton = new Button(); speedRatioButton.Content = "Apply Speed Ratio"; speedRatioButton.Click += new RoutedEventHandler(speedRatioButton_Clicked); speedRatioDetailsPanel.Children.Add(speedRatioButton); mainPanel.Children.Add(speedRatioDetailsPanel); // Display the clock's global speed information. Label myLabel = new Label(); myLabel.Content = "CurrentGlobalSpeed "; mainPanel.Children.Add(myLabel); currentGlobalSpeedTextBlock = new TextBlock(); currentGlobalSpeedTextBlock.Text = myControllableClock.CurrentGlobalSpeed.ToString(); mainPanel.Children.Add(currentGlobalSpeedTextBlock); // List for speed changes. myControllableClock.CurrentGlobalSpeedInvalidated += new EventHandler(myControllableClock_currentGlobalSpeedInvalidated); this.Content = mainPanel; } // Updates the clock's SpeedRatio. private void speedRatioButton_Clicked(object sender, RoutedEventArgs e) { // This statement triggers a CurrentGlobalSpeedInvalidated // event. myControllableClock.Controller.SpeedRatio = doubleParseResult; } // Displays the current global speed. private void myControllableClock_currentGlobalSpeedInvalidated(object sender, EventArgs e) { currentGlobalSpeedTextBlock.Text = myControllableClock.CurrentGlobalSpeed.ToString() + " Event triggered at: " + DateTime.Now.ToString(); } // Verifies that speedRatioSettingTextBox has valid text content. // If it doesn't, the speedRatioButton is disabled. private void seekAmountTextBox_TextChanged(object sender, TextChangedEventArgs e) { TextBox theTextBox = (TextBox)e.Source; if (theTextBox.Text == null || theTextBox.Text.Length < 1 || Double.TryParse(theTextBox.Text, System.Globalization.NumberStyles.Any, null, out doubleParseResult) == false) speedRatioButton.IsEnabled = false; else speedRatioButton.IsEnabled = true; } } }
How to: Control a Storyboard After It Starts Using Its Interactive Methods
This example shows how to use code to control a Storyboard after it has started. To control a storyboard in XAML, use Trigger and TriggerAction objects; for an example, see How to: Use Event Triggers to Control a Storyboard After It Starts.
To start a storyboard, you use its Begin method, which distributes the storyboard's animations to the properties they animate and starts the storyboard.
To make a storyboard controllable, you use the Begin method and specify true as the second parameter. You can then use the storyboard's interactive methods to pause, resume, seek, stop, speed up, or slow down the storyboard, or advance it to its fill period. The following is a list of the storyboard's interactive methods:
Pause: Pauses the storyboard.
Resume: Resumes a paused storyboard.
SetSpeedRatio: Sets the storyboard's interactive speed.
Seek: Seeks the storyboard the specified location.
SeekAlignedToLastTick: Seeks the storyboard to the specified location. Unlike the Seek method, this operation is processed before the next tick.
SkipToFill: Advances the storyboard to its fill period, if it has one.
Stop: Stops the storyboard.
In the following example, several storyboard methods are used to interactively control a storyboard.
Note: To see an example of controlling a storyboard using triggers with XAML, see How to: Use Event Triggers to Control a Storyboard After It Starts.
Example
/* This example shows how to control a storyboard after it has started. */ using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; using System.Windows.Media.Animation; namespace Microsoft.Samples.Animation.TimingBehaviors { public partial class ControlStoryboardExample : Page { private Storyboard myStoryboard; public ControlStoryboardExample() { // Create a name scope for the page. NameScope.SetNameScope(this, new NameScope()); this.WindowTitle = "Controlling a Storyboard"; this.Background = Brushes.White; StackPanel myStackPanel = new StackPanel(); myStackPanel.Margin = new Thickness(20); // Create a rectangle. Rectangle myRectangle = new Rectangle(); myRectangle.Width = 100; myRectangle.Height = 20; myRectangle.Margin = new Thickness(12,0,0,5); myRectangle.Fill = new SolidColorBrush(Color.FromArgb(170, 51, 51, 255)); myRectangle.HorizontalAlignment = HorizontalAlignment.Left; myStackPanel.Children.Add(myRectangle); // Assign the rectangle a name by // registering it with the page, so that // it can be targeted by storyboard // animations. this.RegisterName("myRectangle", myRectangle); // // Create an animation and a storyboard to animate the // rectangle. // DoubleAnimation myDoubleAnimation = new DoubleAnimation(100, 500, new Duration(TimeSpan.FromSeconds(5))); Storyboard.SetTargetName(myDoubleAnimation, "myRectangle"); Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Rectangle.WidthProperty)); myStoryboard = new Storyboard(); myStoryboard.Children.Add(myDoubleAnimation); // // 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); } } }
How to: Control an Animation using From, To, and By
A "From/To/By" or "basic animation" creates a transition between two target values (see Animation Overview for an introduction to different types of animations). To set the target values of a basic animation, use its From, To, and By properties. The following table summarizes how the From, To, and By properties may be used together or separately to determine an animation's target values.
Properties specified | Resulting behavior |
---|---|
The animation progresses from the value specified by the From property to the base value of the property being animated or to a previous animation's output value, depending on how the previous animation is configured. | |
The animation progresses from the value specified by the From property to the value specified by the To property. | |
The animation progresses from the value specified by the From property to the value specified by the sum of the From and Byproperties. | |
The animation progresses from the animated property's base value or a previous animation's output value to the value specified by the To property. | |
The animation progresses from the base value of the property being animated or a previous animation's output value to the sum of that value and the value specified by the By property. |
To use other interpolation methods or animate between more than two target values, use a key frame animation. See Key-Frame Animations Overview for more information.
For information about applying multiple animations to a single property, see Key-Frame Animations Overview.
The example below shows the different effects of setting To, By, and From properties on animations.
Example
<!-- This example shows the different effects of setting To, By, and From properties on animations. --> <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel Margin="20"> <!-- Demonstrates the From and To properties used together. --> <Rectangle Name="fromToAnimatedRectangle" Height="10" Width="100" HorizontalAlignment="Left" Margin="10" Fill="#99FF9900" /> <!-- Demonstrates the use of the To property. --> <Rectangle Name="toAnimatedRectangle" Height="10" Width="100" HorizontalAlignment="Left" Margin="10" Fill="#99FF9900" /> <!-- Demonstrates the use of the By property. --> <Rectangle Name="byAnimatedRectangle" Height="10" Width="100" HorizontalAlignment="Left" Margin="10" Fill="#99FF9900" /> <!-- Demonstrates the use of the From and By properties. --> <Rectangle Name="fromByAnimatedRectangle" Height="10" Width="100" HorizontalAlignment="Left" Margin="10" Fill="#99FF9900" /> <!-- Demonstrates the use of the From property. --> <Rectangle Name="fromAnimatedRectangle" Height="10" Width="100" HorizontalAlignment="Left" Margin="10" Fill="#99FF9900" /> <Button> Start Animations <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard FillBehavior="Stop"> <!-- Demonstrates the From and To properties used together. Animates the rectangle's Width property from 50 to 300 over 10 seconds. --> <DoubleAnimation Storyboard.TargetName="fromToAnimatedRectangle" Storyboard.TargetProperty="(Rectangle.Width)" From="50" To="300" Duration="0:0:10" /> <!-- Demonstrates the To property used by itself. Animates the Rectangle's Width property from its base value (100) to 300 over 10 seconds. --> <DoubleAnimation Storyboard.TargetName="toAnimatedRectangle" Storyboard.TargetProperty="(Rectangle.Width)" To="300" Duration="0:0:10" /> <!-- Demonstrates the By property used by itself. Increments the Rectangle's Width property by 300 over 10 seconds. As a result, the Width property is animated from its base value (100) to 400 (100 + 300) over 10 seconds. --> <DoubleAnimation Storyboard.TargetName="byAnimatedRectangle" Storyboard.TargetProperty="(Rectangle.Width)" By="300" Duration="0:0:10" /> <!-- Demonstrates the From and By properties used together. Increments the Rectangle's Width property by 300 over 10 seconds. As a result, the Width property is animated from 50 to 350 (50 + 300) over 10 seconds. --> <DoubleAnimation Storyboard.TargetName="fromByAnimatedRectangle" Storyboard.TargetProperty="(Rectangle.Width)" From="50" By="300" Duration="0:0:10" /> <!-- Demonstrates the From property used by itself. Animates the rectangle's Width property from 50 to its base value (100) over 10 seconds. --> <DoubleAnimation Storyboard.TargetName="fromAnimatedRectangle" Storyboard.TargetProperty="(Rectangle.Width)" From="50" Duration="0:0:10" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> </StackPanel> </Page>
How to: Define a Name Scope
To animate with Storyboard in code, you must create a NameScope and register the target objects' names with the element that owns that name scope. In the following example, a NameScope is created for myMainPanel. Two buttons, button1 and button2, are added to the panel, and their names registered. Several animations and a Storyboard are created. The storyboard's Begin method is used to start the animations.
Because button1, button2, and myMainPanel all share the same name scope, any one of them can be used with the Storyboard Begin method to start the animations.
Example
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; namespace Microsoft.Samples.Animation.AnimatingWithStoryboards { public class ScopeExample : Page { private Storyboard myStoryboard; private StackPanel myMainPanel; private Button button1, button2; public ScopeExample() { this.Background = Brushes.White; myMainPanel = new StackPanel(); // Create a name scope for the stackpanel. NameScope.SetNameScope(myMainPanel, new NameScope()); myMainPanel.Background = Brushes.Orange; button1 = new Button(); button1.Name = "Button1"; // Register button1's name with myMainPanel. myMainPanel.RegisterName(button1.Name, button1); button1.Content = "Button 1"; button1.Click += new RoutedEventHandler(button1Clicked); myMainPanel.Children.Add(button1); button2 = new Button(); button2.Name = "Button2"; // Register button2's name with myMainPanel. myMainPanel.RegisterName(button2.Name, button2); button2.Content = "Button 2"; button2.Click += new RoutedEventHandler(button2Clicked); myMainPanel.Children.Add(button2); // Create some animations and a storyboard. DoubleAnimation button1WidthAnimation = new DoubleAnimation(300, 200, new Duration(TimeSpan.FromSeconds(5))); Storyboard.SetTargetName(button1WidthAnimation, button1.Name); Storyboard.SetTargetProperty(button1WidthAnimation, new PropertyPath(Button.WidthProperty)); DoubleAnimation button2WidthAnimation = new DoubleAnimation(300, 200, new Duration(TimeSpan.FromSeconds(5))); Storyboard.SetTargetName(button2WidthAnimation, button2.Name); Storyboard.SetTargetProperty(button2WidthAnimation, new PropertyPath(Button.WidthProperty)); DoubleAnimation heightAnimationWithoutTarget = new DoubleAnimation(300, 200, new Duration(TimeSpan.FromSeconds(5))); Storyboard.SetTargetProperty(heightAnimationWithoutTarget, new PropertyPath(FrameworkElement.HeightProperty)); myStoryboard = new Storyboard(); myStoryboard.Children.Add(button1WidthAnimation); myStoryboard.Children.Add(button2WidthAnimation); myStoryboard.Children.Add(heightAnimationWithoutTarget); this.Content = myMainPanel; } private void button1Clicked(object sender, RoutedEventArgs args) { // Starts the animations. The animation without a specified // target name, heightAnimationWithoutTarget, is applied to // myMainPanel. myStoryboard.Begin(myMainPanel); } private void button2Clicked(object sender, RoutedEventArgs args) { // Starts the animations. The animation without a specified // target name, heightAnimationWithoutTarget, is applied to // button2. myStoryboard.Begin(button2); } } }
How to: Receive Notification When a Clock's State Changes
A clock's CurrentStateInvalidated event occurs when its CurrentState becomes invalid, such as when the clock starts or stops. You can register for this event with directly using a Clock, or you can register using a Timeline.
In the following example, a Storyboard and two DoubleAnimation objects are used to animate the width of two rectangles. The CurrentStateInvalidated event is used to listen for clock state changes.
Example
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Microsoft.Samples.Animation.TimingBehaviors.StateExample" Background="LightGray"> <StackPanel Margin="20"> <TextBlock Name="ParentTimelineStateTextBlock"></TextBlock> <TextBlock Name="Animation1StateTextBlock"></TextBlock> <Rectangle Name="Rectangle01" Width="100" Height="50" Fill="Orange" /> <TextBlock Name="Animation2StateTextBlock"></TextBlock> <Rectangle Name="Rectangle02" Width="100" Height="50" Fill="Gray" /> <Button Content="Start Animations" Margin="20"> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard RepeatBehavior="2x" AutoReverse="True" CurrentStateInvalidated="parentTimelineStateInvalidated" > <DoubleAnimation Storyboard.TargetName="Rectangle01" Storyboard.TargetProperty="Width" From="10" To="200" Duration="0:0:9" BeginTime="0:0:1" CurrentStateInvalidated="animation1StateInvalidated"/> <DoubleAnimation Storyboard.TargetName="Rectangle02" Storyboard.TargetProperty="Width" From="10" To="200" Duration="0:0:8" BeginTime="0:0:1" CurrentStateInvalidated="animation2StateInvalidated" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> </StackPanel> </Page>
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; namespace Microsoft.Samples.Animation.TimingBehaviors { public partial class StateExample : Page { private void parentTimelineStateInvalidated(object sender, EventArgs args) { Clock myClock = (Clock)sender; ParentTimelineStateTextBlock.Text += myClock.CurrentTime.ToString() + ":" + myClock.CurrentState.ToString() + " "; } private void animation1StateInvalidated(object sender, EventArgs args) { Clock myClock = (Clock)sender; Animation1StateTextBlock.Text += myClock.Parent.CurrentTime.ToString() + ":" + myClock.CurrentState.ToString() + " "; } private void animation2StateInvalidated(object sender, EventArgs args) { Clock myClock = (Clock)sender; Animation2StateTextBlock.Text += myClock.Parent.CurrentTime.ToString() + ":" + myClock.CurrentState.ToString() + " "; } } }
The following illustration shows the different states the animations enter as the parent timeline (Storyboard) progresses.
The following table shows the times at which Animation1's CurrentStateInvalidated event fires:
Time (Seconds) | 1 | 10 | 19 | 21 | 30 | 39 |
State | Active | Active | Stopped | Active | Active | Stopped |
The following table shows the times at which Animation2's CurrentStateInvalidated event fires:
Time (Seconds) | 1 | 9 | 11 | 19 | 21 | 29 | 31 | 39 |
State | Active | Filling | Active | Stopped | Active | Filling | Active | Stopped |
Notice that Animation1's CurrentStateInvalidated event fires at 10 seconds, even though its state remains Active. That's because its state changed at 10 seconds, but it changed from Active to Filling and then back to Active in the same tick.
How to: Repeat an Animation
This example shows how to use the RepeatBehavior property of a Timeline in order to control the repeat behavior of an animation.
Example
The RepeatBehavior property of a Timeline controls how many times an animation repeats its simple duration. By using RepeatBehavior, you can specify that a Timeline repeats for a certain number of times (an iteration count) or for a specified time period. In either case, the animation goes through as many beginning-to-end runs that it needs in order to fill the requested count or duration.
By default, timelines have a repeat count of 1.0, which means they play one time and do not repeat. However, if you set the RepeatBehavior property of a Timeline to Forever, the timeline repeats indefinitely.
The following example shows how to use the RepeatBehavior property to control the repeat behavior of an animation. The example animates the Width property of five rectangles with each rectangle using a different type of repeat behavior.
<!-- RepeatBehaviorExample.xaml This example shows how to use the RepeatBehavior property to make a timeline repeat. --> <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowTitle="RepeatBehavior Example"> <Border HorizontalAlignment="Stretch"> <StackPanel Margin="20"> <!-- Create several rectangles to animate. --> <Rectangle Name="ForeverRepeatingRectangle" Fill="Orange" Width="50" Height="20" /> <Rectangle Name="FourSecondsRepeatingRectangle" Fill="Orange" Width="50" Height="20" /> <Rectangle Name="TwiceRepeatingRectangle" Fill="Orange" Width="50" Height="20" /> <Rectangle Name="HalfRepeatingRectangle" Fill="Orange" Width="50" Height="20" /> <Rectangle Name="OneSecondRepeatingRectangle" Fill="Orange" Width="50" Height="20" /> <!-- Create buttons to restart and stop the animations. --> <StackPanel Orientation="Horizontal" Margin="0,20,0,0"> <Button Name="restartButton">Start Animations</Button> <Button Name="stopButton" Background="#669900FF">Stop</Button> <StackPanel.Triggers> <EventTrigger SourceName="restartButton" RoutedEvent="Button.Click"> <BeginStoryboard Name="myBeginStoryboard"> <Storyboard> <!-- Create an animation that repeats indefinitely. --> <DoubleAnimation Storyboard.TargetName="ForeverRepeatingRectangle" Storyboard.TargetProperty="Width" From="50" To="300" Duration="0:0:2" RepeatBehavior="Forever" /> <!-- Create an animation that repeats for four seconds. As a result, the animation repeats twice. --> <DoubleAnimation Storyboard.TargetName="FourSecondsRepeatingRectangle" Storyboard.TargetProperty="Width" From="50" To="300" Duration="0:0:2" RepeatBehavior="0:0:4" /> <!-- Create an animation that repeats twice. --> <DoubleAnimation Storyboard.TargetName="TwiceRepeatingRectangle" Storyboard.TargetProperty="Width" From="50" To="300" Duration="0:0:2" RepeatBehavior="2x" /> <!-- Create an animation that repeats 0.5 times. The resulting animation plays for one second, half of its Duration. It animates from 50 to 150. --> <DoubleAnimation Storyboard.TargetName="HalfRepeatingRectangle" Storyboard.TargetProperty="Width" From="50" To="300" Duration="0:0:2" RepeatBehavior="0.5x" /> <!-- Create an animation that repeats for one second. The resulting animation plays for one second, half of its Duration. It animates from 50 to 150. --> <DoubleAnimation Storyboard.TargetName="OneSecondRepeatingRectangle" Storyboard.TargetProperty="Width" From="50" To="300" Duration="0:0:2" RepeatBehavior="0:0:1" /> </Storyboard> </BeginStoryboard> </EventTrigger> <EventTrigger SourceName="stopButton" RoutedEvent="Button.Click"> <StopStoryboard BeginStoryboardName="myBeginStoryboard" /> </EventTrigger> </StackPanel.Triggers> </StackPanel> </StackPanel> </Border> </Page>
For the complete sample, see Animation Timing Behavior Sample.
How to: Seek a Storyboard
The following example shows how to use the Seek method of a Storyboard to jump to any position in a storyboard animation.
Example
Below is the XAML markup for the sample.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="SDKSample.SeekStoryboardExample"> <StackPanel Margin="20" > <Rectangle Name="myRectangle" Width="10" Height="20" Fill="#AA3333FF" HorizontalAlignment="Left" > <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard Name="myBeginStoryboard"> <Storyboard Name="myStoryboard" Duration="0:0:4"> <DoubleAnimation Storyboard.TargetName="myRectangle" Storyboard.TargetProperty="Width" Duration="0:0:4" From="10" To="500"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle> <!-- Use this slider to seek to different points of the Storyboard Duration (in milliseconds). --> <Slider Name="SeekSlider" ValueChanged="OnSliderValueChanged" Height="Auto" Width="500" Minimum="0" Maximum="4000" HorizontalAlignment="Left" /> </StackPanel> </Page>
Example
Below is the code used with the XAML code above.
using System; using System.Media; using System.Windows; using System.Windows.Controls; using System.Windows.Media.Animation; namespace SDKSample { public partial class SeekStoryboardExample : Page { private void OnSliderValueChanged(object sender, RoutedEventArgs e) { int sliderValue = (int)SeekSlider.Value; // Use the value of the slider to seek to a duration value of the Storyboard (in milliseconds). myStoryboard.Seek(myRectangle, new TimeSpan(0, 0, 0, 0, sliderValue), TimeSeekOrigin.BeginTime); } } }
How to: Seek a Storyboard Synchronously
The following example shows how to use the SeekAlignedToLastTick method of a Storyboard to seek to any position in a storyboard animation synchronously.
Example
The following is the XAML markup for the sample.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="SDKSample.SeekStoryboardSynchronouslyExample"> <StackPanel Margin="20" > <Rectangle Name="myRectangle" Width="10" Height="20" Fill="#AA3333FF" HorizontalAlignment="Left" > <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard Name="myBeginStoryboard"> <Storyboard Name="myStoryboard" Duration="0:0:4"> <DoubleAnimation Storyboard.TargetName="myRectangle" Storyboard.TargetProperty="Width" Duration="0:0:4" From="10" To="500"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle> <!-- Use this slider to seek to different points of the Storyboard Duration (in milliseconds). --> <Slider Name="SeekSlider" ValueChanged="OnSliderValueChanged" Height="Auto" Width="500" Minimum="0" Maximum="4000" HorizontalAlignment="Left" /> <!-- TextBlock displays the current time position of the Storyboard in milliseconds. --> <TextBlock Name="PositionTextBlock"/> </StackPanel> </Page>
Example
The following is the code used with the XAML code above.
using System; using System.Media; using System.Windows; using System.Windows.Controls; using System.Windows.Media.Animation; namespace SDKSample { public partial class SeekStoryboardSynchronouslyExample : Page { private void OnSliderValueChanged(object sender, RoutedEventArgs e) { int sliderValue = (int)SeekSlider.Value; // The SeekAlignedToLastTick method should be used to seek a Storyboard synchronously. myStoryboard.SeekAlignedToLastTick(myRectangle, new TimeSpan(0, 0, 0, 0, sliderValue), TimeSeekOrigin.BeginTime); PositionTextBlock.Text = sliderValue.ToString(); } } }
How to: Set a Duration for an Animation
A Timeline represents a segment of time and the length of that segment is determined by the timeline's Duration. When a Timeline reaches the end of its duration, it stops playing. If the Timeline has child timelines, they stop playing as well. In the case of an animation, the Duration specifies how long the animation takes to transition from its starting value to its ending value.
You can specify a Duration with a specific, finite time or the special values Automatic or Forever. An animation's duration must always be a time value, because an animation must always have a defined, finite length—otherwise, the animation would not know how to transition between its target values. Container timelines (TimelineGroup objects), such as Storyboard and ParallelTimeline, have a default duration of Automatic, which means they automatically end when their last child stops playing.
In the following example, the width, height and fill color of a Rectangle is animated. Durations are set on animation and container timelines resulting in animation effects including controlling the perceived speed of an animation and overriding the duration of child timelines with the duration of a container timeline.
Example
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel Margin="20"> <Rectangle Width="100" Height="100" Name="myRectangle"> <Rectangle.Fill> <SolidColorBrush x:Name="MyAnimatedBrush" Color="Black" /> </Rectangle.Fill> <Rectangle.Triggers> <!-- Animates the rectangle fill to yellow and width to 300. --> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <!-- By default, TimelineGroup objects like Storyboard and ParallelTimeline have a Duration of "Automatic". A TimelineGroup's automatic duration encompasses its last-ending child. In this example, there is only one child of the Storyboard, the ParallelTimeline, so when the ParallelTimeline ends, the Storyboard duration will automatically end. --> <Storyboard> <!-- This ParallelTimeline has overriden its default duration of "Automatic" with a finite duration of half a second. This will force this Timeline to end after half a second even though its child Timelines have a longer duration (2 and 4 seconds respectively). This cuts off the animation prematurely and the rectangle's fill will not go all the way to yellow nor will the rectangle width get all the way to 300. Again, the default duration of a ParallelTimeline is "Automatic" so if you remove the finite duration, the ParallelTimeline will wait for its child timelines to end before it ends. --> <!-- Note: To specify a finite time in XAML, use the syntax of "days:hours:seconds". As mentioned, this ParallelTimeline has a duration of half a second. --> <ParallelTimeline Duration="0:0:0.5"> <!-- For Animation Timelines like DoubleAnimation, the duration is one factor that determines the rate at which an animation appears to progress. For example, the DoubleAnimation below that animates the rectangle height will complete in only one second while the animation that animates the width willwill complete in 2 seconds which is relatively fast compared to the DoubleAnimation which animates the rectangle width over 4 seconds. --> <DoubleAnimation Storyboard.TargetName="myRectangle" Storyboard.TargetProperty="Height" To="300" Duration="0:0:1" /> <DoubleAnimation Storyboard.TargetName="myRectangle" Storyboard.TargetProperty="Width" To="300" Duration="0:0:4" /> <ColorAnimation Storyboard.TargetName="MyAnimatedBrush" Storyboard.TargetProperty="Color" To="Yellow" Duration="0:0:2" /> </ParallelTimeline> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle> </StackPanel> </Page>
How to: Set a Property After Animating It with a Storyboard
In some cases, it might appear that you can't change the value of a property after it has been animated.
Example
In the following example, a Storyboard is used to animate the color of a SolidColorBrush. The storyboard is triggered when the button is clicked. The Completed event is handled so that the program is notified when the ColorAnimation completes.
<Button Content="Animate and Then Set Example 1"> <Button.Background> <SolidColorBrush x:Name="Button1BackgroundBrush" Color="Red" /> </Button.Background> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetName="Button1BackgroundBrush" Storyboard.TargetProperty="Color" From="Red" To="Yellow" Duration="0:0:5" FillBehavior="HoldEnd" Completed="setButton1BackgroundBrushColor" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button>
Example
After the ColorAnimation completes, the program attempts to change the brush's color to blue.
private void setButton1BackgroundBrushColor(object sender, EventArgs e) { // Does not appear to have any effect: // the brush remains yellow. Button1BackgroundBrush.Color = Colors.Blue; }
The previous code doesn't appear to do anything: the brush remains yellow, which is the value supplied by the ColorAnimation that animated the brush. The underlying property value (the base value) is actually changed to blue. However, the effective, or current, value remains yellow because the ColorAnimation is still overriding the base value. If you want the base value to become the effective value again, you must stop the animation from influencing the property. There are three ways to do this with storyboard animations:
Set the animation's FillBehavior property to Stop
Remove the entire Storyboard.
Remove the animation from the individual property.
Set the animation's FillBehavior property to Stop
By setting FillBehavior to Stop, you tell the animation to stop affecting its target property after it reaches the end of its active period.
<Button Content="Animate and Then Set Example 2"> <Button.Background> <SolidColorBrush x:Name="Button2BackgroundBrush" Color="Red" /> </Button.Background> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetName="Button2BackgroundBrush" Storyboard.TargetProperty="Color" From="Red" To="Yellow" Duration="0:0:5" FillBehavior="Stop" Completed="setButton2BackgroundBrushColor" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button>
Remove the entire storyboard
By using a RemoveStoryboard trigger or the Storyboard.Remove method, you tell the storyboard animations to stop affecting their target properties. The difference between this approach and setting the FillBehavior property is that you can remove the storyboard at anytime, while the FillBehavior property only has an effect when the animation reaches the end of its active period.
<Button Name="Button3" Content="Animate and Then Set Example 3"> <Button.Background> <SolidColorBrush x:Name="Button3BackgroundBrush" Color="Red" /> </Button.Background> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard Name="MyBeginStoryboard"> <Storyboard x:Name="MyStoryboard"> <ColorAnimation Storyboard.TargetName="Button3BackgroundBrush" Storyboard.TargetProperty="Color" From="Red" To="Yellow" Duration="0:0:5" FillBehavior="HoldEnd" Completed="setButton3BackgroundBrushColor" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button>
Remove an animation from an individual property
Another technique to stop an animation from affecting a property is to use the BeginAnimation(DependencyProperty, AnimationTimeline) method of the object being animated. Specify the property being animated as the first parameter and null as the second.
<Button Name="Button4" Content="Animate and Then Set Example 4"> <Button.Background> <SolidColorBrush x:Name="Button4BackgroundBrush" Color="Red" /> </Button.Background> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetName="Button4BackgroundBrush" Storyboard.TargetProperty="Color" From="Red" To="Yellow" Duration="0:0:5" FillBehavior="HoldEnd" Completed="setButton4BackgroundBrushColor" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button>
This technique also works for non-storyboard animations.
How to: Simplify Animations by Using Child Timelines
This example shows how to simplify animations by using child ParallelTimeline objects. A Storyboard is a type of Timeline that provides targeting information for the timelines it contains. Use a Storyboard to provide timeline targeting information, including object and property information.
To begin an animation, use one or more ParallelTimeline objects as nested child elements of a Storyboard. These ParallelTimeline objects can contain other animations and therefore, can better encapsulate the timing sequences in complex animations. For example, if you are animating a TextBlock and several shapes in the same Storyboard, you can separate the animations for the TextBlock and the shapes, putting each into a separate ParallelTimeline. Because each ParallelTimeline has its own BeginTime and all child elements of the ParallelTimeline begin relative to this BeginTime, timing is better encapsulated.
The following example animates two pieces of text ( TextBlock objects) from within the same Storyboard. A ParallelTimeline encapsulates the animations of one of the TextBlock objects.
Performance Note: Although you can nest Storyboard timelines inside each other, ParallelTimelines are more suitable for nesting because they require less overhead. (The Storyboard class inherits from the ParallelTimeline class.)
Example
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Canvas > <!-- TextBlock with text "ParallelTimelines are..." that gets animated. --> <TextBlock Name="FirstTextBlock" Canvas.Top="30" Canvas.Left="300" FontSize="24" > ParallelTimelines are... <TextBlock.RenderTransform> <TransformGroup> <SkewTransform x:Name="FirstTextBlockSkew" CenterX="25" CenterY="25" AngleX="0" AngleY="0" /> </TransformGroup> </TextBlock.RenderTransform> </TextBlock> <!-- TextBlock with text "Useful" that gets animated. --> <TextBlock Name="SecondTextBlock" Opacity="0" Canvas.Top="30" Canvas.Left="585" FontSize="24" > Useful <TextBlock.RenderTransform> <TransformGroup> <SkewTransform x:Name="SecondTextBlockSkew" CenterX="25" CenterY="25" AngleX="0" AngleY="0" /> <ScaleTransform x:Name="SecondTextBlockScale" CenterX="0" CenterY="24" /> </TransformGroup> </TextBlock.RenderTransform> </TextBlock> <!-- Event Trigger that controls all animations on the page. --> <Canvas.Triggers> <EventTrigger RoutedEvent="Canvas.Loaded"> <BeginStoryboard> <Storyboard> <!-- "ParallelTimelines are..." fades into view. --> <DoubleAnimation Storyboard.TargetName="FirstTextBlock" Storyboard.TargetProperty="Opacity" Duration="0:0:2" From="0" To="1" /> <!-- "ParallelTimelines are..." skews to the left. --> <DoubleAnimation Storyboard.TargetName="FirstTextBlockSkew" Storyboard.TargetProperty="AngleX" Duration="0:0:1" BeginTime="0:0:2" From="0" To="45" /> <!-- This ParallelTimeline contains all the animations for the TextBlock with the text "Useful" in it. This ParallelTimeline begins 4 seconds after the Storyboard timeline begins and all child animations begin relative to this parent timeline. --> <ParallelTimeline BeginTime="0:0:4"> <!-- "Useful" fades into view. --> <DoubleAnimation Storyboard.TargetName="SecondTextBlock" Storyboard.TargetProperty="Opacity" Duration="0:0:2" From="0" To="1" /> <!-- "Useful" slides in from the right. --> <DoubleAnimation Storyboard.TargetName="SecondTextBlockSkew" Storyboard.TargetProperty="AngleX" Duration="0:0:2" From="90" To="180" /> <!-- "Useful" skews to the right. --> <DoubleAnimation Storyboard.TargetName="SecondTextBlockSkew" Storyboard.TargetProperty="AngleX" BeginTime="0:0:3" Duration="0:0:0.2" From="0" To="-60" /> <!-- "Useful" Gets taller. --> <DoubleAnimation Storyboard.TargetName="SecondTextBlockScale" Storyboard.TargetProperty="ScaleY" BeginTime="0:0:3" Duration="0:0:0.2" From="1" To="3" /> </ParallelTimeline> </Storyboard> </BeginStoryboard> </EventTrigger> </Canvas.Triggers> </Canvas> </Page>
How to: Specify HandoffBehavior Between Storyboard Animations
This example shows how to specify handoff behavior between storyboard animations. The HandoffBehavior property of BeginStoryboard specifies how new animations interact with any existing ones that are already applied to a property.
Example
The following example creates two buttons that enlarge when the mouse cursor moves over them and become smaller when the cursor moves away. If you mouse over a button and then quickly remove the cursor, the second animation will be applied before the first one is finished. It is when two animations overlap like this that you can see the difference between the HandoffBehavior values of Compose and SnapshotAndReplace. A value of Compose combines the overlapping animations causing a smoother transition between animations while a value of SnapshotAndReplace causes the new animation to immediately replace the earlier overlapping animation.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Page.Resources> <!-- This Style specifies mouseover and mouseout behaviors. The button gets larger when the cursor moves over it and smaller when the cursor moves away. Note that the same Properties (ScaleX and ScaleY) are being targeted by both animations. The BeginStoryboard for each animation uses a HandoffBehavior of "Compose" which causes the old animation to interpolate more gradually into the new one. --> <Style x:Key="ButtonWithCompose" TargetType="{x:Type Button}"> <Setter Property="Button.RenderTransform"> <Setter.Value> <ScaleTransform CenterX="50" CenterY="50" ScaleX="1" ScaleY="1" /> </Setter.Value> </Setter> <Style.Triggers> <EventTrigger RoutedEvent="Mouse.MouseEnter"> <EventTrigger.Actions> <BeginStoryboard > <Storyboard> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleX" To="3" /> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleY" To="3" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseLeave"> <EventTrigger.Actions> <BeginStoryboard HandoffBehavior="Compose"> <Storyboard> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleX" /> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleY" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style> <!-- For this button style, BeginStoryboard uses the default HandoffBehavior of "SnapShotAndReplace" --> <Style x:Key="ButtonWithSnapShotAndReplace" TargetType="{x:Type Button}"> <Setter Property="Button.RenderTransform"> <Setter.Value> <ScaleTransform CenterX="50" CenterY="50" ScaleX="1" ScaleY="1" /> </Setter.Value> </Setter> <Style.Triggers> <EventTrigger RoutedEvent="Mouse.MouseEnter"> <EventTrigger.Actions> <BeginStoryboard > <Storyboard> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleX" To="3" /> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleY" To="3" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseLeave"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleX" /> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleY" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style> </Page.Resources> <Canvas> <Button Style="{StaticResource ButtonWithSnapShotAndReplace}" Canvas.Top="200" Canvas.Left="200" Width="100" Height="100"> SnapShotAndReplace </Button> <Button Style="{StaticResource ButtonWithCompose}" Canvas.Top="200" Canvas.Left="400" Width="100" Height="100"> Compose </Button> </Canvas> </Page>
How to: Specify the FillBehavior for a Timeline that has Reached the End of Its Active Period
This example shows how to specify the FillBehavior for the inactive Timeline of an animated property.
Example
The FillBehavior property of a Timeline determines what happens to the value of an animated property when it is not being animated, that is, when the Timeline is inactive but its parent Timeline is inside its active or hold period. For example, does an animated property remain at its end value after the animation ends or does it revert back to the value it had before the animation started?
The following example uses a DoubleAnimation to animate the Width of two rectangles. Each rectangle uses a different Timeline object.
One Timeline has a FillBehavior that is set to Stop, which causes the width of the rectangle to revert back to its non-animated value when the Timeline ends. The other Timeline has a FillBehavior of HoldEnd, which causes the width to remain at its end value when the Timeline ends.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <StackPanel Margin="20"> <Border Background="#99FFFFFF"> <TextBlock Margin="20"> This example shows how the FillBehavior property determines how an animation behaves after it reaches the end of its duration. </TextBlock> </Border> <TextBlock>FillBehavior="Deactivate"</TextBlock> <Rectangle Name="deactiveAnimationRectangle" Width="20" Height="20" Fill="#AA3333FF" HorizontalAlignment="Left" > <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <!-- The animated rectangle's width reverts back to its non-animated value after the animation ends. --> <DoubleAnimation Storyboard.TargetName="deactiveAnimationRectangle" Storyboard.TargetProperty="Width" From="100" To="400" Duration="0:0:2" FillBehavior="Stop" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle> <TextBlock Margin="0,20,0,0">FillBehavior="HoldEnd" </TextBlock> <Rectangle Name="holdEndAnimationRectangle" Width="20" Height="20" Fill="#AA3333FF" HorizontalAlignment="Left" > <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <!-- The animated rectangle's width remains at its end value after the animation ends. --> <DoubleAnimation Storyboard.TargetName="holdEndAnimationRectangle" Storyboard.TargetProperty="Width" From="100" To="400" Duration="0:0:2" FillBehavior="HoldEnd" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle> </StackPanel> </Page>
For the complete sample, see Animation Example Gallery.
How to: Specify Whether a Timeline Automatically Reverses
A timeline's AutoReverse property determines whether it plays in reverse after it completes a forward iteration. The following example shows several animations with identical duration and target values, but with different AutoReverse settings. To demonstrate how the AutoReverse property behaves with different RepeatBehavior settings, some animations are set to repeat. The last animation shows how the AutoReverse property works on nested timelines.
Example
<!-- AutoReverseExample.xaml This example shows how to use the AutoReverse property to make a timeline play backwards at the end of each iteration. Several rectangles are animated by DoubleAnimations with identical durations and target values, but with different AutoReverse and RepeatBehavior settings.--> <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowTitle="AutoReverse Example"> <StackPanel Margin="20"> <!-- Create some rectangles to animate. --> <Rectangle Name="withoutAutoReverseRectangle" Width="100" Height="20" Fill="Blue" /> <Rectangle Name="autoReverseRectangle" Width="100" Height="20" Fill="Blue" /> <Rectangle Name="autoReverseRectangleWithRepeats" Width="100" Height="20" Fill="Blue" /> <Rectangle Name="complexAutoReverseExample" Width="100" Height="20" Fill="Blue" /> <!-- Use a button to restart the animations. --> <Button Margin="30" Content="Start Animations"> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <!-- Create an animation that does not automatically play in reverse. This animation plays for a total of 2 seconds. --> <DoubleAnimation Storyboard.TargetName="withoutAutoReverseRectangle" Storyboard.TargetProperty="Width" Duration="0:0:2" From="100" To="400" AutoReverse="False" /> <!-- Create an animation that automatically reverses at the end of each iteration. This animation plays for a total of 4 seconds. --> <DoubleAnimation Storyboard.TargetName="autoReverseRectangle" Storyboard.TargetProperty="Width" Duration="0:0:2" From="100" To="400" AutoReverse="True" /> <!-- Create an animation that automatically reverses at the end of each iteration. Set the animation to repeat twice. As a result, then animation plays forward, the backward, then forward, and then backward again. This animation plays for a total of 8 seconds. --> <DoubleAnimation Storyboard.TargetName="autoReverseRectangleWithRepeats" Storyboard.TargetProperty="Width" Duration="0:0:2" From="100" To="400" AutoReverse="True" RepeatBehavior="2x" /> <!-- Set the parent timeline's AutoReverse property to True and set the animation's RepeatBehavior to 2x. As a result, the animation plays forward twice and then backwards twice. This animation plays for a total of 8 seconds. --> <ParallelTimeline AutoReverse="True"> <DoubleAnimation Storyboard.TargetName="complexAutoReverseExample" Storyboard.TargetProperty="Width" Duration="0:0:2" From="100" To="400" RepeatBehavior="2x" /> </ParallelTimeline> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> </StackPanel> </Page>
How to: Trigger an Animation When a Property Value Changes
This example shows how to use a Trigger to start a Storyboard when a property value changes. You can use a Trigger inside a Style, ControlTemplate, or DataTemplate.
Example
The following example uses a Trigger to animate the Opacity of a Button when its IsMouseOver property becomes true.
<!-- PropertyTriggerExample.xaml Shows how to use property triggers to start animations. --> <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowTitle="Animate Properties with Storyboards"> <Page.Resources> <Style x:Key="PropertyTriggerExampleButtonStyle" TargetType="{x:Type Button}"> <Setter Property="Opacity" Value="0.25" /> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Trigger.EnterActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:1" /> </Storyboard> </BeginStoryboard> </Trigger.EnterActions> <Trigger.ExitActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.25" Duration="0:0:1" /> </Storyboard> </BeginStoryboard> </Trigger.ExitActions> </Trigger> </Style.Triggers> </Style> </Page.Resources> <StackPanel Margin="20"> <Button Style="{StaticResource PropertyTriggerExampleButtonStyle}"> Move the mouse over me. </Button> </StackPanel> </Page>
Animations applied by property Trigger objects behave in a more complex fashion than EventTrigger animations or animations started using Storyboard methods. They "handoff" with animations defined by other Trigger objects, but compose with EventTrigger and method-triggered animations.
How to: Use Event Triggers to Control a Storyboard After It Starts
This example shows how to control a Storyboard after it starts. To start a Storyboard by using XAML, use BeginStoryboard, which distributes the animations to the objects and properties they animate and then starts the storyboard. If you give BeginStoryboard a name by specifying its Name property, you make it a controllable storyboard. You can then interactively control the storyboard after it starts.
Use the following storyboard actions together with EventTrigger objects to control a storyboard.
PauseStoryboard: Pauses the storyboard.
ResumeStoryboard: Resumes a paused storyboard.
SetStoryboardSpeedRatio: Changes the storyboard speed.
SkipStoryboardToFill: Advances a storyboard to the end of its fill period, if it has one.
StopStoryboard: Stops the storyboard.
RemoveStoryboard: Removes the storyboard, freeing resources.
Example
The following example uses controllable storyboard actions to interactively control a storyboard.
Note: To see an example of controlling a storyboard by using code, see How to: Control a Storyboard After It Starts.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowTitle="Controlling a Storyboard" > <StackPanel Margin="20" > <!-- This rectangle is animated. --> <Rectangle Name="myRectangle" Width="100" Height="20" Margin="12,0,0,5" Fill="#AA3333FF" HorizontalAlignment="Left" /> <!-- This StackPanel contains all the Buttons. --> <StackPanel Orientation="Horizontal" Margin="0,30,0,0"> <Button Name="BeginButton">Begin</Button> <Button Name="PauseButton">Pause</Button> <Button Name="ResumeButton">Resume</Button> <Button Name="SeekButton">Seek</Button> <Button Name="SkipToFillButton">Skip To Fill</Button> <Button Name="SetSpeedRatioButton">Triple Speed</Button> <Button Name="StopButton">Stop</Button> <StackPanel.Triggers> <!-- Begin the Storyboard --> <EventTrigger RoutedEvent="Button.Click" SourceName="BeginButton"> <BeginStoryboard Name="MyBeginStoryboard"> <Storyboard > <DoubleAnimation Storyboard.TargetName="myRectangle" Storyboard.TargetProperty="Width" Duration="0:0:5" From="100" To="500" /> </Storyboard> </BeginStoryboard> </EventTrigger> <!-- Pause the Storyboard --> <EventTrigger RoutedEvent="Button.Click" SourceName="PauseButton"> <PauseStoryboard BeginStoryboardName="MyBeginStoryboard" /> </EventTrigger> <!-- Resume the Storyboard --> <EventTrigger RoutedEvent="Button.Click" SourceName="ResumeButton"> <ResumeStoryboard BeginStoryboardName="MyBeginStoryboard" /> </EventTrigger> <!-- Seek one second into the storyboard's active period. --> <EventTrigger RoutedEvent="Button.Click" SourceName="SeekButton"> <SeekStoryboard BeginStoryboardName="MyBeginStoryboard" Offset="0:0:1" Origin="BeginTime" /> </EventTrigger> <!-- Skip to Fill --> <EventTrigger RoutedEvent="Button.Click" SourceName="SkipToFillButton"> <SkipStoryboardToFill BeginStoryboardName="MyBeginStoryboard" /> </EventTrigger> <!-- Stop the Storyboard --> <EventTrigger RoutedEvent="Button.Click" SourceName="StopButton"> <StopStoryboard BeginStoryboardName="MyBeginStoryboard" /> </EventTrigger> <!-- Triple the speed of the Storyboard --> <EventTrigger RoutedEvent="Button.Click" SourceName="SetSpeedRatioButton"> <SetStoryboardSpeedRatio SpeedRatio="3" BeginStoryboardName="MyBeginStoryboard" /> </EventTrigger> </StackPanel.Triggers> </StackPanel> </StackPanel> </Page>
For additional examples, see the Animation Example Gallery.
Clocks How-to Topics
Key-Frame How-to Topics
Path Animation How-to Topics
'프로그래밍 > WPF' 카테고리의 다른 글
Graphics and Multimedia - Multimedia, Visual Layer (0) | 2017.01.14 |
---|---|
Data Binding (0) | 2017.01.10 |
Graphics and Multimedia (0) | 2016.11.24 |
Documents (0) | 2016.11.19 |
Control Library 3 (0) | 2016.11.18 |
For more information about animating properties with storyboards, see Storyboards Overview.