Navigate back home
GalaSoft Laurent Bugnion
WPF: Synchronizing animations (part 1: Using built-in features)
Introduction

One of the most exciting features of WPF is the animation system, which provides the UI developer with the possibility to create exciting effects easily. A previous article explains various ways to define and trigger animations using XAML or code-behind.

In this article and the nexts, we will see different technique allowing to synchronize animations so that they run in a coordinated manner.

Next article: (2) Asynchronous by nature

BeginTime and Duration

Animations in WPF are asynchronous in nature. It means that they will be triggered independently from each other, and will run without regarding what other animations are running. There are built-in ways to coordinate animations, for example placing parallel animations in a Storyboard, and using the BeginTime property to specify when a given animation must start.

For example, the animation defined hereunder is a group of 3 animations running parallelly. The first animation starts at 0 seconds, runs for 5 seconds (ScaleX). The second animation starts at 5 seconds, and runs for 5 seconds (ScaleY). Additionally, a rotation starts at 0 seconds and runs for 12 seconds.

Since we set AutoReverse to "true", we can see that these animations run as a group, because the reverse animation starts only after 12 seconds, which is the total duration of the animation group. This is one way of coordinating animations, in a limited way, but quite elegant since it is pure XAML and we didn't write a single line of C# code.

<Window.Triggers> <EventTrigger RoutedEvent="Window.MouseLeftButtonDown"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard AutoReverse="True"> <DoubleAnimation BeginTime="00:00:00" Duration="0:0:5" Storyboard.TargetName="scaleTransform" Storyboard.TargetProperty="ScaleX" To="2"/> <DoubleAnimation BeginTime="00:00:05" Duration="0:0:5" Storyboard.TargetName="scaleTransform" Storyboard.TargetProperty="ScaleY" To="2"/> <DoubleAnimation BeginTime="00:00:00" Duration="0:0:12" Storyboard.TargetName="rotateTransform" Storyboard.TargetProperty="Angle" To="180"/> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Window.Triggers> <Grid x:Name="LayoutRoot"> <Button x:Name="myButton" Width="200" Height="100" RenderTransformOrigin="0.5,0.5" Margin="50,50,50,50" Content="Button"> <Button.RenderTransform> <TransformGroup> <ScaleTransform x:Name="scaleTransform" ScaleX="1" ScaleY="1"/> <RotateTransform x:Name="rotateTransform" Angle="0"/> </TransformGroup> </Button.RenderTransform> </Button> </Grid>
Using events

In the code behind, there are additional ways to synchronize animations, by using events. For example, the Completed event will be fired when an animation finishes running. This provides a way to cascade animations in a very reliable way, not having to use the "BeginTime" property, but instead by observing exactly when the animation finishes running. For example, let's define 2 animations:

<Window.Resources> <DoubleAnimation x:Key="scaleAnimation" Duration="0:0:5" To="2" Completed="ScaleAnimation_Completed"/> <DoubleAnimation x:Key="rotateAnimation" Duration="0:0:5" To="180"/> </Window.Resources> <Grid x:Name="LayoutRoot"> <Button x:Name="myButton" Width="200" Height="100" RenderTransformOrigin="0.5,0.5" Margin="50,50,50,50" Content="Button"> <Button.RenderTransform> <TransformGroup> <ScaleTransform x:Name="scaleTransform" ScaleX="1" ScaleY="1"/> <RotateTransform x:Name="rotateTransform" Angle="0"/> </TransformGroup> </Button.RenderTransform> </Button> </Grid>

In that case, the animations are stored as resources in the XAML, and are not combined in a Storyboard. They are independant of each other, and we use code to start them successively:

public Window1() { InitializeComponent(); this.MouseLeftButtonDown += new MouseButtonEventHandler(Window1_MouseLeftButtonDown); } void Window1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { DoubleAnimation animation = FindResource("scaleAnimation") as DoubleAnimation; ScaleTransform myTransform = FindName("scaleTransform") as ScaleTransform; myTransform.BeginAnimation(ScaleTransform.ScaleXProperty, animation); } void ScaleAnimation_Completed(object sender, EventArgs e) { DoubleAnimation animation = FindResource("rotateAnimation") as DoubleAnimation; RotateTransform myTransform = FindName("rotateTransform") as RotateTransform; myTransform.BeginAnimation(RotateTransform.AngleProperty, animation); }

By doing so, however, we lose the possibility to automatically reverse the animation group, because the animations are not linked together. Generally speaking, animations are much easier to define than in code-behind!

Conclusion

While these built in features allow some level of synchronization, they do not take in account asynchronous triggers. For example, if an animation on a button starts when it is clicked, and if we have multiple such buttons, the animations will not be coordinated and will give an unclean finish to your application. The next articles will deal about synchronizing animations in a single thread using a controller, and then about synchronizing animations across threads.

Next article: (2) Asynchronous by nature