Animations require some kind of timing mechanism that is responsible for updating the relevant property values at the right time. In WPF, this timing mechanism is catered for by the abstract Timeline class, which in short, represents a period of time. All of the available animation classes extend this class and add their own animation functionality.
When a Timeline class is used for animations, an internal copy is made and frozen, so that it is immutable. Additionally, a Clock object is created to preserve the runtime timing state of the Timeline object and is responsible for the actual timing of the animated property updates. The Timeline object itself does little other than define the relevant period of time.
The Clock object will be automatically created for us when we define a Storyboard object, or call one of the Animatable.BeginAnimation methods. Note that we do not typically need to concern ourselves with these Clock objects directly, but it can be helpful to know about them in order to understand the bigger picture.
There are a number of different types of Timeline objects, from the AnimationTimeline class to the TimelineGroup and ParallelTimeline classes. However, for animation purposes, we predominantly utilize the Storyboard class, which extends the ParallelTimeline and the TimelineGroup classes and adds animation-targeting properties and methods for controlling the timeline. Let's first investigate the main properties of the base Timeline class.
The Duration property specifies the time that is represented by the associated Timeline object. However, a timeline can have repetitions, so a more accurate description of the Duration property might be that it specifies the time of a single iteration of the associated Timeline object.
The duration property is of type Duration, which contains a TimeSpan property that contains the actual time that specifies the value of the duration. However, WPF includes a type converter that enables us to specify this TimeSpan value in XAML in the following formats, where the square brackets highlight optional segments:
Duration="[Days.]Hours:Minutes:Seconds[.FractionalSeconds]" Duration="[Days.]Hours:Minutes"
However, the Duration structure also accepts other values in addition to the TimeSpan duration. There is a value of Automatic, which is the default value for component timelines that contain other timelines. In these cases, this value simply means that the parent timeline's duration will be as long as the longest duration of its children timelines. There is little purpose for us to explicitly use this value.
However, there is one further value that is very useful to us. The Duration structure also defines a Forever property that represents an infinite period of time. We can use this value to make an animation continue indefinitely, or more accurately, as long as its related View is being displayed:
Duration="Forever"
A Timeline object will stop playing when it reaches the end of its duration. If it has any child timelines associated with it, then they will also stop playing at this point. However, the natural duration of a timeline can be extended or shortened using other properties, as we will see shortly.
Some timelines, such as the ParallelTimeline and Storyboard classes, are able to contain other timelines and can affect their durations by setting their own values for the Duration property, which will override those set by the child timelines. Let's alter an earlier animation example from Chapter 5, Using the Right Controls for the Job to demonstrate this:
<Rectangle Width="0" Height="0" Fill="Orange"> <Rectangle.Triggers> <EventTrigger RoutedEvent="Loaded"> <BeginStoryboard> <Storyboard Duration="0:0:2.5"> <DoubleAnimation Storyboard.TargetProperty="Width" To="300.0" Duration="0:0:2.5" /> <DoubleAnimation Storyboard.TargetProperty="Height" To="300.0" Duration="0:0:5" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle>
In this preceding example, we have a Rectangle object with its dimensions initially set to zero. The Storyboard object contains two separate animation objects that will animate its dimensions from zero to three hundred pixels. The animation object that will animate the rectangle's width has a duration of two and a half seconds, while the animation object that will animate the height has a duration of five seconds.
However, the containing Storyboard object has a duration of two and a half seconds and so this will stop the timelines of the two child animation objects after two and a half seconds, regardless of their declared durations. The result of this will be that after the animation is complete, our Rectangle object will appear as a rectangle, instead of a square with equal height and width values.
If we had changed the duration of the storyboard to match that of the longer child animation, or changed that animation duration to match that of the shorter child animation, then our animated shape would end as a square, rather than as a rectangle.
Another way to adjust the assigned duration of an animation element is to set its AutoReverse property. In effect, setting this property to True will usually double the length of time that is specified by the Duration property, as the timeline will play in reverse after it has completed its normal forwards iteration. Let's alter the storyboard from the previous example to demonstrate this:
<Storyboard Duration="0:0:5"> <DoubleAnimation Storyboard.TargetProperty="Width" To="300.0" Duration="0:0:2.5" AutoReverse="True" /> <DoubleAnimation Storyboard.TargetProperty="Height" To="300.0" Duration="0:0:5" /> </Storyboard>
Now, both child timelines will have the same overall duration, as the first, previously shorter, timeline has effectively been doubled in length. However, this will result in the first timeline animating the width of the rectangle to three hundred pixels and then back to zero, so it will be invisible when the animations have completed. Also note that we had to set the parent storyboard duration to five seconds in order to see the difference in the child timelines.
Note again that properties set on timelines that contain other timelines will affect the values of those properties on the child timelines. As such, setting the AutoReverse property to True on the parent timeline (the Storyboard object) will double the total length of time that the child animations will run for; in our case, using the following example, the rectangle will now be animated for ten seconds in total:
<Storyboard Duration="0:0:5" AutoReverse="True"> <DoubleAnimation Storyboard.TargetProperty="Width" To="300.0" Duration="0:0:2.5" AutoReverse="True" /> <DoubleAnimation Storyboard.TargetProperty="Height" To="300.0" Duration="0:0:5" /> </Storyboard>
The RepeatBehavior property is of type RepeatBehavior and can also affect the overall duration of a timeline. Unlike the AutoReverse property, it can also shorten the overall duration as well as lengthen it. Using the RepeatBehavior property, we can specify the value in a number of ways using different behaviors.
The most simple is to provide a count of how many times we would like to multiply the original duration of the timeline. A pre-existing XAML type converter enables us to set the repeat count in XAML by specifying an x after the count, as can be seen in the following example. Note that we can also specify numbers with decimal places here, including values less than one:
<Storyboard Duration="0:0:5" AutoReverse="True" RepeatBehavior="2x"> <DoubleAnimation Storyboard.TargetProperty="Width" To="300.0" Duration="0:0:2.5" AutoReverse="True" /> <DoubleAnimation Storyboard.TargetProperty="Height" To="300.0" Duration="0:0:5" /> </Storyboard>
In this example, the normal duration would be five seconds, but the AutoReverse property is set to True and so that duration is doubled. However, the RepeatBehavior property is set to 2x and this will multiply the doubled ten seconds to twenty seconds. This multiplier value of two will be stored in the Count property of the RepeatBehavior structure.
An alternative to using the count option is to simply set the duration that we would like the animation to last for. The same XAML syntax that is used to set the Duration property can also be used to set the RepeatBehavior property. Similarly, the RepeatBehavior structure also defines a Forever property that represents an infinite period of time and we can use this value to make an animation continue indefinitely.
One further property that can affect the duration of an animation is the SpeedRatio property. This value is multiplied by the other related duration properties and so can both speed up and slow down the associated timeline. Let's update our example again to help to explain this property now:
<Storyboard Duration="0:0:5" AutoReverse="True" SpeedRatio="0.5"> <DoubleAnimation Storyboard.TargetProperty="Width" To="300.0" Duration="0:0:2.5" AutoReverse="True" /> <DoubleAnimation Storyboard.TargetProperty="Height" To="300.0" Duration="0:0:5" SpeedRatio="2" /> </Storyboard>
Again, the normal duration here would be five seconds and the AutoReverse property is set to True, so the duration is doubled. However, the SpeedRatio property is set to 0.5 and so the doubled duration is again doubled to twenty seconds. Note that a SpeedRatio value of 0.5 represents half the normal speed and therefore twice the normal duration.
The second child timeline also sets the SpeedRatio property, but it is set to 2 and so its speed is doubled and its duration halved. As its specified duration is twice that of its sibling timeline and its speed is now twice as fast, this has the effect of re-synchronizing the two child animations, so that the two dimensions now grow together, as a square, rather than as a rectangle.
There are two more speed-related properties that we can use to fine-tune our animations: the AccelerationRatio and DecelerationRatio properties. These properties adjust the proportion of time that the related animation takes to speed up and slow down respectively. While this effect can be subtle at times, it can also give our animations that professional touch when used correctly.
Acceptable values for both of these properties exist between zero and one. If both properties are used together, then the total sum of their values must still remain between zero and one. Failure to adhere to this rule will result in the following exception being thrown at runtime:
The sum of AccelerationRatio and DecelerationRatio must be less than or equal to one.
Entering values outside the acceptable range on either of these properties individually will also result in an error, although doing this will cause a compilation error instead:
Property value must be between 0.0 and 1.0.
Let's look at an example that highlights the difference between the different values of these two properties:
<StackPanel Margin="20"> <StackPanel.Triggers> <EventTrigger RoutedEvent="Loaded"> <BeginStoryboard> <Storyboard RepeatBehavior="Forever" Duration="0:0:1.5" SpeedRatio="0.5" Storyboard.TargetProperty="Width"> <DoubleAnimation Storyboard.TargetName="RectangleA" AccelerationRatio="1.0" From="0" To="300" /> <DoubleAnimation Storyboard.TargetName="RectangleB" AccelerationRatio="0.8" DecelerationRatio="0.2"
From="0" To="300" /> <DoubleAnimation Storyboard.TargetName="RectangleC" AccelerationRatio="0.6" DecelerationRatio="0.4"
From="0" To="300" /> <DoubleAnimation Storyboard.TargetName="RectangleD" AccelerationRatio="0.5" DecelerationRatio="0.5"
From="0" To="300" /> <DoubleAnimation Storyboard.TargetName="RectangleE" AccelerationRatio="0.4" DecelerationRatio="0.6"
From="0" To="300" /> <DoubleAnimation Storyboard.TargetName="RectangleF" AccelerationRatio="0.2" DecelerationRatio="0.8"
From="0" To="300" /> <DoubleAnimation Storyboard.TargetName="RectangleG" DecelerationRatio="1.0" From="0" To="300" /> </Storyboard> </BeginStoryboard> </EventTrigger> </StackPanel.Triggers> <Rectangle Name="RectangleA" Fill="#FF0000" Height="30" /> <Rectangle Name="RectangleB" Fill="#D5002B" Height="30" /> <Rectangle Name="RectangleC" Fill="#AB0055" Height="30" /> <Rectangle Name="RectangleD" Fill="#800080" Height="30" /> <Rectangle Name="RectangleE" Fill="#5500AB" Height="30" /> <Rectangle Name="RectangleF" Fill="#2B00D5" Height="30" /> <Rectangle Name="RectangleG" Fill="#0000FF" Height="30" /> </StackPanel>
This code defines a number of Rectangle objects in a StackPanel control, each with its own associated DoubleAnimation element, that increases its width from zero to three hundred pixels over one and a half seconds.
Here, we've used the Storyboard.TargetName and Storyboard.TargetProperty properties to target the rectangles from a single EventTrigger to reduce the amount of code in the preceding example. We'll cover these Attached Properties in detail shortly, but for now, we'll just say that they are used to specify the target element and property to animate.
Each animation targets a different rectangle and has different values set for the AccelerationRatio and DecelerationRatio properties. The top rectangle's animation has its AccelerationRatio property set to 1.0 and the animation for the bottom rectangle has its DecelerationRatio property set to 1.0.
The animations for the rectangles in between have varying values. The higher the rectangle, the higher the values for the AccelerationRatio property and the lower the values for the DecelerationRatio property and the lower the rectangle, the lower the values of the AccelerationRatio property and the higher the values for the DecelerationRatio property.
When this example is run, we can clearly see the differences between the various ratio values. At one point near the start of each iteration, we can see that the top rectangles that are animated with higher AccelerationRatio values have not grown in size as much as the lower rectangles that are animated with higher DecelerationRatio values; however, all rectangles reach 300 pixels at approximately the same time:

Another useful property in the Timeline class is the BeginTime property. As the name suggests, it sets the time to begin the animation; it can be thought of as a delay time that delays the start of its animation with relation to parent and sibling timelines.
The default value of this property is zero seconds and when it is set with a positive value, the delay occurs just once at the start of the timeline and is not affected by other properties that may be set on it. It is often used to delay the start of one or more animations until another animation has completed. Let's adjust our earlier example again to demonstrate this:
<Rectangle Width="0" Height="1" Fill="Orange"> <Rectangle.Triggers> <EventTrigger RoutedEvent="Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Width" To="300.0" Duration="0:0:2" /> <DoubleAnimation Storyboard.TargetProperty="Height" To="300.0" Duration="0:0:2" BeginTime="0:0:2" /> <DoubleAnimation Storyboard.TargetProperty="Width" To="0.0" Duration="0:0:2" BeginTime="0:0:4" /> <DoubleAnimation Storyboard.TargetProperty="Height" To="0.0" Duration="0:0:2" BeginTime="0:0:4" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle>
In this example, we have a single pixel high rectangle with a width that grows outward until it is three hundred pixels wide and then grows vertically until it is three hundred pixels high. At that point, its dimensions equally reduce in size until the shape shrinks to nothing.
This is achieved by delaying the last three animations while the width-increasing animation runs and then delaying the last two animations while the height-increasing animation runs. The BeginTime properties of the last two animations are set to the same value, so that they both start and run in synchronization with each other.
The last really useful timeline property is the FillBehavior property, which specifies what should happen to the data bound property value when the timeline reaches the end of its total duration, or its fill period. This property is of type FillBehavior and has just two values.
If we set this property to a value of HoldEnd, the data bound property value will remain at the final value that was reached just before the animation ended. Conversely, if we set this property to a value of Stop, which is the default value, the data bound property value will revert to the value that the property originally had before the animation started. Let's illustrate this with a simple example:
<StackPanel Margin="20"> <StackPanel.Triggers> <EventTrigger RoutedEvent="Loaded"> <BeginStoryboard> <Storyboard Duration="0:0:1.5" SpeedRatio="0.5" Storyboard.TargetProperty="Opacity"> <DoubleAnimation Storyboard.TargetName="RectangleA" To="0.0" FillBehavior="HoldEnd" /> <DoubleAnimation Storyboard.TargetName="RectangleB" To="0.0" FillBehavior="Stop" /> </Storyboard> </BeginStoryboard> </EventTrigger> </StackPanel.Triggers> <Rectangle Name="RectangleA" Fill="Orange" Height="100" HorizontalAlignment="Stretch" Margin="0,0,0,20" /> <Rectangle Name="RectangleB" Fill="Orange" Height="100" HorizontalAlignment="Stretch" /> </StackPanel>
In this example, the difference between the two FillBehavior enumeration instances is clearly demonstrated. We have two rectangles of identical size that have identical timelines set up to animate their Opacity property values, with the exception of their FillBehavior property values.
Both rectangles fade from being opaque to being invisible in the same amount of time, but once the two timelines are complete, the rectangle with the FillBehavior property set to Stop immediately becomes visible again, as it was prior to the start of the animation, while the other with the FillBehavior property set to HoldEnd remains invisible, as it was at the end of the animation.
While this covers the main properties that are exposed by the Timeline class directly, there are a few more properties that are declared by many of the animation classes that extend the Timeline class and are essential to fully understand. They are the From, By and To properties, which specify the start and end points of the animations.
Because the animation classes generate property values, there are different types of animation classes for different property types. For example, the animation class that generates Point values is called the PointAnimation class and all of the normal animation classes follow the same naming pattern, using the name of the related type in the form of <TypeName>Animation, for example ColorAnimation.
The normal animation classes, often referred to as the From, By and To animations, usually require two values to be specified, although one of these can sometimes be implicitly provided. The relevant property will then be animated along a path of automatically interpolated values between the two specified values.
It is most common to provide a starting value using the From property and an ending value using the To property. However, we can also specify a single starting, ending, or offset value and the second value will be taken from the current value of the animated property. We can set the offset value using the By property and this represents the exact amount the property value will change over the duration.
Specifying values for these different properties can have dramatically different effects on the resulting animations. Using the From property alone will start the animation at the desired value and will animate the property until it reaches the property's base value.
Using the To property alone will start animating the property from its current value and end at the specified value. Using only the By property will animate the property from its current value until the sum of that value with the specified offset amount has been reached.
Combinations of the three properties can be used to target just the right range of property values. Setting the From and By properties will start the animations from the value specified by the From property and animate the property until the offset specified by the By property has been reached.
Setting the From and To properties together will start the animations from the value specified by the From property and animate the property until the value specified by the To property. As the By and To properties both specify the ending value of the animation, the value specified by the By property will be ignored if they are both set on an animation element.
While these more common animations use one or two of the From, By, and To properties together to specify the range of values of the related property to be animated, there is another way to specify the target values. Let's now take a look at key-frame animations.