Navigate back home
GalaSoft Laurent Bugnion
WPF: Synchronizing animations (part 2: Asynchronous by nature)
Introduction

In the first article of this serie, we saw that animations in WPF are asynchronous by nature, which means that they will start as soon as the start condition (event, data trigger, etc...) is activated, without any consideration for other running animations. In this article, we will demonstrate that fact first, using a small test application which we will modify in the next article to make the animations run synchronously.

ItemsControl to present the UI elements

It is very easy to demonstrate the asynchronous character of WPF animations. First let's create an ItemsControl hosting a number of buttons (for more information about ItemsControl and how to bind the content to a collection, see this article). The code hereunder creates an ItemsControl looking like a horizontal StackPanel, and databound to an ObservableCollection defined in the Page class (see below), and named "Data" (original name!!). The collection contains instances of a custom objects which I named DataItem.

Note that we handle the "Click" events of all the buttons in a generic way, using the attached "Button.Click" event handler. The reason is that I don't recommend defining event handlers in a DataTemplate. Templates in WPF are often stored in external resource files, and if possible there should be no link to methods stored in the code behind.

<ItemsControl ItemsSource="{Binding ElementName=MyPage, Path=Data}" ItemTemplate="{StaticResource dataTemplate}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal" Button.Click="Item_Click"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemContainerStyle> <Style> <Setter Property="Control.Margin" Value="18"/> </Style> </ItemsControl.ItemContainerStyle> </ItemsControl>
Data layer

In the code-behind, we populate the "Data" collection with 5 DataItem instances. This data object is a very simple class with two DependencyProperties (DO): The first DP "IsBlink" is the one starting and stopping the animation. The second DP "Description" is simply a text which will be displayed in the button representing the DataItem (strictly speaking, this second DP could be a simple CLR property, since it never changes in that application).

public class DataItem : DependencyObject { // Dependency property responsible for starting/stopping the animation. public bool IsBlink { get { return (bool) GetValue(IsBlinkProperty); } set { SetValue(IsBlinkProperty, value); } } public static readonly DependencyProperty IsBlinkProperty = DependencyProperty.Register("IsBlink", typeof(bool), typeof(DataItem), new UIPropertyMetadata(false)); // Just a description for the item. public string Description { get { return (string) GetValue(DescriptionProperty); } set { SetValue(DescriptionProperty, value); } } public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(DataItem), new UIPropertyMetadata("-none-")); }
Data template

Additionally, we define a DataTemplate making the data item look like a WPF button (again, for more information about this, see this article).

The data template's only interest is that it defines a ScaleTransform and a DoubleAnimation, which we will trigger when the button is clicked. Actually, to make things more dynamic, we will start and stop the animation on each click of the button. Let's see the transform first. It simply defines the ScaleTransform which will be animated later. For the moment, we define it with ScaleX = ScaleY = 1, which is the default value:

<Button.RenderTransform> <ScaleTransform ScaleX="1" ScaleY="1"/> </Button.RenderTransform>

The next part is more interesting, though it's classical WPF: Define a DoubleAnimation triggered by a DependencyProperty (DP) exposed by the DataItem instance.

The XAML animation is defined in the Button style, to allow the triggering. There are multiple ways to define animations, and this is just an example. The animation targets the following PropertyPath: "(Button.RenderTransform).(ScaleTransform.ScaleX)". Note that the animation is also stopped using the same DP, by using a StopStoryboard class.

<Button.Style> <Style> <Style.Triggers> <DataTrigger Binding="{Binding Path=IsBlink}" Value="True"> <DataTrigger.EnterActions> <BeginStoryboard x:Name="scaleAnimation"> <Storyboard> <DoubleAnimation From="1" To="1.3" Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever" Timeline.DesiredFrameRate="40" Storyboard.TargetProperty="(Button.RenderTransform).(ScaleTransform.ScaleX)"/> </Storyboard> </BeginStoryboard> </DataTrigger.EnterActions> </DataTrigger> <DataTrigger Binding="{Binding Path=IsBlink}" Value="False"> <DataTrigger.EnterActions> <StopStoryboard BeginStoryboardName="scaleAnimation"/> </DataTrigger.EnterActions> </DataTrigger> </Style.Triggers> </Style> </Button.Style>
Creating the items, handling the events

The last tasks we need to perform is to populate the ObservableCollection with DataItems, and also handle the "Click" event. This all takes place in the Page's code behind:

public partial class Page0 : System.Windows.Controls.Page { // Create the ObservableCollection containing the DataItems private ObservableCollection<DataItem> data = new ObservableCollection<DataItem>(); public ObservableCollection<DataItem> Data { get { return data; } } // Populate the ObservableCollection. public Page0() { for (int index = 0; index < 4; index++) { DataItem item = new DataItem(); item.Description = "Item # " + index; data.Add(item); } InitializeComponent(); } // Handle the Click event. private void Item_Click(object sender, RoutedEventArgs e) { // Get the clicked button. Button source = e.OriginalSource as Button; // Get the DataItems for this button. DataItem item = source.DataContext as DataItem; // Start/Stop the animation by setting the DP. item.IsBlink = !item.IsBlink; } }
Demo, download

The code we just studied is available for download. Additionally, I created and published an XBAP application where we can see what we just did. After opening the XBAP in Internet Explorer, try and click on the buttons at random times. You will see that the animations run in an uncoordinated manner.

The next article in this serie will show how to modify this small application to make animations run in a coordinated manner.