Animations are usually just a cheap visual effect that is based on
an element's properties being changed. So, for instance, if an element
moves from the top-left to the bottom-right corner of the canvas, its
Canvas.Left and
Canvas.Top properties
are changed a few times per second. If an element fades in
from out of nowhere, its
Opacity value
is
animated, from 100% to 0%. So, theoretically, you could rely
solely on C# and its access to Silverlight elements' properties to create
animations. Of course, it is much more convenient to use the built-in
animation support. Setting up an animation requires quite a number of steps, but the
result can be very rewarding.
1. Setting Up an Animation
Creating an animation requires several steps and a couple of lines
of markup, so IntelliSense
is really handy here. For example, to smoothly move an
element to another position you need to animate the element. For reasons
that will become clear in a minute, you should name that element:
Code View:
<TextBlock FontFamily="Arial" FontSize="56" Canvas.Left="25" Canvas.Top="40"
Foreground="Black" Text="Silverlight" x:Name="MyTextBlock">
...
</TextBlock>
Within this element, define a trigger
using a
<TextBlock.Triggers> element
(if you were to animate a rectangle, you would use
<Rectangle.Triggers>, etc.). An actual trigger
(represented by
<EventTrigger>) is
activated when an event is fired. This event is provided
in the
RoutedEvent attribute of
<EventTrigger>. Currently, Silverlight supports only
one event,
Element.Loaded, where
Element is the name of the object that
contains the trigger (here it is
TextBlock):
Code View:
<TextBlock FontFamily="Arial" FontSize="56" Canvas.Left="25" Canvas.Top="40"
Foreground="Black" Text="Silverlight" x:Name="MyTextBlock">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="TextBlock.Loaded">
...
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
Within the event trigger, a
storyboard
is
created.
<BeginStoryboard> and
<Storyboard> are the two necessary
elements.
A storyboard is a set of one or more animations. You can
try to compare what a storyboard does for animations to what the
<TransformGroup> element
does to transformations, which is to group several of
them together. An animation can consist of several individual
animations, but more on that in a minute.
Code View:
<TextBlock FontFamily="Arial" FontSize="56" Canvas.Left="25" Canvas.Top="40"
Foreground="Black" Text="Silverlight" x:Name="MyTextBlock">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="TextBlock.Loaded">
<BeginStoryboard>
<Storyboard>
...
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
Silverlight supports several animations, and we will cover all of
them in the next section. To complete the current example, let's
introduce the most common animation:
. This
animation "animates" a value from a start value to an end
value—for instance, from 1 to 10. Every animation runs a certain amount
of time. Within that interval, the associated animation value is
gradually changed, from the start value to the end value. When the value
goes from 1 to 10, these values might be 1, 1.1, 1.2, and so on until
10, depending on the duration of the animation. If the value that is
animated is the
x coordinate of the element to be
animated, this creates the visual effect of the element smoothly moving
from one point to another.
When using an animation, you will usually need several of these
properties:
-
AutoReverse
-
Reverses the animation if it has ended (i.e., moves the element
back to where it started)
- Duration
-
The duration of an animation, using the syntax
hh:mm:ss (hours, minutes, seconds)
- From
-
The start value for the animation
- To
-
The end value for the animation
- By
-
A relative value indicating by how much to change the
animation (an alternative approach for using
To)
- RepeatBehavior
-
What to do if the animation has ended; you can provide a (total)
duration, a number of times to repeat, or mark it
Forever if the animation should repeat
endlessly
- Storyboard.TargetName
-
The name of the element that needs to be animated
(therefore, we needed to assign a name)
- Storyboard.TargetProperty
-
The property of the element that needs to be animated
NOTE
The value of Storyboard.TargetProperty is the name
of the property that receives the animated values. If the property
includes a dot (such as in Canvas.Left and
Canvas.Top), you need to enclose the complete property
name in parentheses—for example, (Canvas.Left) and
(Canvas.Top).
Adding a concludes the code,
which is shown in Example 1. Both the rectangle and
the text are moved 300 pixels to the right, using the default animation
duration (here it is one second). Visual Studio 2008 may complain that
it does not know and , but it works
nevertheless, as Figure 1 shows.
Example 1. Using , the XAML file (Page.xaml, project DoubleAnimation)
<UserControl x:Class="DoubleAnimation.Page" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="600" Height="400"> <Grid x:Name="LayoutRoot" Background="White"> <Canvas>
<Rectangle Width="300" Height="150" Stroke="Orange" StrokeThickness="15" x:Name="MyRectangle"> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation From="0" To="300.456" Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="(Canvas.Left)" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle> <TextBlock FontFamily="Arial" FontSize="56" Canvas.Left="25" Canvas.Top="40" Foreground="Black" Text="Silverlight" x:Name="MyTextBlock"> <TextBlock.Triggers> <EventTrigger RoutedEvent="TextBlock.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation From="25" To="325.456" Storyboard.TargetName="MyTextBlock" Storyboard.TargetProperty="(Canvas.Left)" /> </Storyboard> </BeginStoryboard> </EventTrigger> </TextBlock.Triggers> </TextBlock>
</Canvas> </Grid> </UserControl>
|
If you set up an animation that way, it will start immediately
after the trigger has been activated. Silverlight allows you to change
this behavior. Every animation also supports the BeginTime attribute, where you define the
time (again using the hh:mm:ss syntax) when the animation
starts. The code from Example 2 combines two elements: the first
one moves the element to the right, and the second one moves it to the
bottom. The second animation starts after three seconds, which happens
to be the exact time when the first animation has ended. Figure 2 shows the second phase of the storyboard: the
element is moving down.
Example 2. Combining animations and starting them later, the XAML file
(Page.xaml, project
DoubleAnimations)
<UserControl x:Class="DoubleAnimations.Page" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="600" Height="400"> <Grid x:Name="LayoutRoot" Background="White"> <Canvas>
<Rectangle Width="300" Height="150" Stroke="Orange" StrokeThickness="15" x:Name="MyRectangle"> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation From="0" To="300.456" Duration="0:0:3" Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="(Canvas.Left)" /> <DoubleAnimation From="0" To="150" BeginTime="0:0:3" Duration="0:0:3" Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="(Canvas.Top)" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle> <TextBlock FontFamily="Arial" FontSize="56" Canvas.Left="25" Canvas.Top="40" Foreground="Black" Text="Silverlight" x:Name="MyTextBlock"> <TextBlock.Triggers> <EventTrigger RoutedEvent="TextBlock.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation From="25" To="325.456" Duration="0:0:3" Storyboard.TargetName="MyTextBlock" Storyboard.TargetProperty="(Canvas.Left)" /> <DoubleAnimation From="40" To="190" BeginTime="0:0:3" Duration="0:0:3" Storyboard.TargetName="MyTextBlock" Storyboard.TargetProperty="(Canvas.Top)" /> </Storyboard> </BeginStoryboard> </EventTrigger> </TextBlock.Triggers> </TextBlock>
</Canvas> </Grid> </UserControl>
|
NOTE
If you omit the BeginTime attribute in Example 2, both animations run at the same time, making
the elements move diagonally.
2. Animation Types
Apart from
, Silverlight also comes with
support for two additional animations with a more specific
purpose:
ColorAnimation (animates a color value—e.g.,
Orange)
PointAnimation (animates a point—e.g.,
0,0)
Animating a color works wherever you have a Color
property. That means you
cannot animate, say, the
Stroke property. However, you can use a SolidColorBrush and animate the
Color property there.
The rest is easy. Use the
element, set a From and a To color, and
Silverlight takes care of the
rest, as Example 3 shows (see Figure 3 for the browser output).
Example 3. Animating a color, the XAML file (Page.xaml, project ColorAnimation)
<UserControl x:Class="ColorAnimation.Page" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="600" Height="400"> <Grid x:Name="LayoutRoot" Background="White"> <Canvas> <Rectangle Width="300" Height="150" StrokeThickness="15" x:Name="MyRectangle"> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <ColorAnimation From="Green" To="Orange" Duration="0:0:5" Storyboard.TargetName="MyBrush" Storyboard.TargetProperty="Color" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> <Rectangle.Stroke> <SolidColorBrush x:Name="MyBrush" /> </Rectangle.Stroke> </Rectangle> <TextBlock FontFamily="Arial" FontSize="56" Canvas.Left="25" Canvas.Top="40" Foreground="Black" Text="Silverlight" /> </Canvas> </Grid> </UserControl>
|
NOTE
A point animation (<PointAnimation> in
Silverlight's XAML) animates a point from a start point to
an end point (or by a certain distance in the coordinate system using
By). To use it, you need to animate a property that
requires a point as a value. One example for that is the
<LinearGradientBrush>: its StartPoint and EndPoint
attributes are both points. So, let's assume we have such a
brush:
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1" x:Name="MyGradient">
<GradientStop Color="Red" Offset="0.0" />
<GradientStop Color="Green" Offset="0.5" />
<GradientStop Color="Blue" Offset="1.0" />
</LinearGradientBrush>
To animate this brush's StartPoint property, a
<PointAnimation> element such as the following would
do:
<PointAnimation
From="0,0" To="1,0" Duration="0:0:4"
Storyboard.TargetName="MyGradient"
Storyboard.TargetProperty="StartPoint" />
The code in Example 4 combines two animations:
the start point of the gradient is moved from the top-left to the
top-right corner and the end point of the gradient is moved from the
bottom-left to the bottom-right corner. Figure 4
shows the Silverlight application
in the browser during this animation: the gradient is moving.
Example 4. Animating a point, the XAML file (Page.xaml, project PointAnimation)
<UserControl x:Class="PointAnimation.Page" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="600" Height="400"> <Grid x:Name="LayoutRoot" Background="White"> <Canvas>
<Rectangle Width="300" Height="150" StrokeThickness="15" x:Name="MyRectangle"> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <PointAnimation From="0,0" To="1,0" Duration="0:0:4" Storyboard.TargetName="MyGradient" Storyboard.TargetProperty="StartPoint" /> <PointAnimation From="1,1" To="0,1" Duration="0:0:4" Storyboard.TargetName="MyGradient" Storyboard.TargetProperty="EndPoint" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> <Rectangle.Stroke> <LinearGradientBrush StartPoint="0,0" EndPoint="1,1" x:Name="MyGradient"> <GradientStop Color="Red" Offset="0.0" /> <GradientStop Color="Green" Offset="0.5" /> <GradientStop Color="Blue" Offset="1.0" /> </LinearGradientBrush> </Rectangle.Stroke> </Rectangle> <TextBlock FontFamily="Arial" FontSize="56" Canvas.Left="25" Canvas.Top="40" Foreground="Black" Text="Silverlight" />
</Canvas> </Grid> </UserControl>
|
3. Keyframe Animations
All of the animations so far were quite flexible in terms of the
beginning and end times, but there was one severe restriction: every
animation only took care of exactly one value that was animated. A bit
more complex, but also a bit more flexible, are so-called keyframe
animations. The term keyframe is used heavily in
Adobe Flash and identifies a frame during the course of a Flash movie
where a certain state in the application must be reached (e.g., objects
need to have specific positions). Silverlight uses keyframes only as
part of special animations, but the approach is comparable: when a
keyframe is reached, a certain object value must be met.
All three animation types we know so far
(>,
, and
) can also be used with keyframes.
Then the names of the elements change (to ,
, and
). Within each element, you provide the number of keyframes.
Every keyframe needs at least two values, or attributes:
-
KeyTime
-
The time when the keyframe will come into effect
- Value
-
The value that needs to be reached at the given time
When you have one keyframe after two seconds (providing a value of
10), and another one after four seconds (providing a value of 20), the
value that you assign in these two frames will be animated between them.
How this value is animated from 10 to 20 will be defined by the second
keyframe. There are different ways to interpolate the value, and the
second keyframe needs to confirm which of these methods is used.
Silverlight currently supports three methods:
Linear
-
The value is linearly interpolated.
Discrete
-
There are no values between the start and end values; when the
next keyframe is reached, the new value will be assigned.
Spline
-
The values will be animated along a cubic Bézier curve (the two
control points are then provided in the KeySpline
attribute.
Every keyframe can come in different flavors: different type of
value that is animated (Double, Color,
Point) and a different type of interpolation method (Linear, Discrete,
Spline). The name of the keyframe element in XAML is the
concatenation of interpolation
type, value type, and KeyFrame. Therefore, a keyframe that
uses a spline interpolation of colors would be represented by
the element.
Apart from that, keyframe animations just work as animations
without keyframes. So, without further ado, have a look at Example 5 where the rectangle and the text block are moved
around the canvas. The animations for the x and the
y coordinate consist of four subanimations each. We
are using a splined animation each time. Figure 5 shows the application in the animations.
Example 7-11. Animating using keyframes, the XAML file (Page.xaml, project
KeyFrameAnimation)
<UserControl x:Class="KeyFrameAnimation.Page" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="600" Height="400"> <Grid x:Name="LayoutRoot" Background="White"> <Canvas>
<Rectangle Width="300" Height="150" Stroke="Orange" StrokeThickness="15" x:Name="MyRectangle"> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="(Canvas.Left)" Duration="0:0:8"> <SplineDoubleKeyFrame Value="300" KeyTime="0:0:2" KeySpline="0.25,0.75 0.75,0.25" /> <SplineDoubleKeyFrame Value="100" KeyTime="0:0:4" KeySpline="0.75,0.25 0.25,0.75" /> <SplineDoubleKeyFrame Value="50" KeyTime="0:0:6" KeySpline="0.25,0.75 0.75,0.25" /> <SplineDoubleKeyFrame Value="200" KeyTime="0:0:8" KeySpline="0.75,0.25 0.25,0.75" /> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="(Canvas.Top)" Duration="0:0:8"> <SplineDoubleKeyFrame Value="50" KeyTime="0:0:2" KeySpline="0.25,0.75 0.75,0.25" /> <SplineDoubleKeyFrame Value="250" KeyTime="0:0:4" KeySpline="0.75,0.25 0.25,0.75" /> <SplineDoubleKeyFrame Value="50" KeyTime="0:0:6" KeySpline="0.25,0.75 0.75,0.25" /> <SplineDoubleKeyFrame Value="100" KeyTime="0:0:8" KeySpline="0.75,0.25 0.25,0.75" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle> <Canvas Canvas.Left="25" Canvas.Top="40"> <TextBlock FontFamily="Arial" FontSize="56" Foreground="Black" Text="Silverlight" x:Name="MyTextBlock"> <TextBlock.Triggers> <EventTrigger RoutedEvent="TextBlock.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="MyTextBlock" Storyboard.TargetProperty="(Canvas.Left)" Duration="0:0:8"> <SplineDoubleKeyFrame Value="300" KeyTime="0:0:2" KeySpline="0.25,0.75 0.75,0.25" /> <SplineDoubleKeyFrame Value="100" KeyTime="0:0:4" KeySpline="0.75,0.25 0.25,0.75" /> <SplineDoubleKeyFrame Value="50" KeyTime="0:0:6" KeySpline="0.25,0.75 0.75,0.25" /> <SplineDoubleKeyFrame Value="200" KeyTime="0:0:8" KeySpline="0.75,0.25 0.25,0.75" /> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="MyTextBlock" Storyboard.TargetProperty="(Canvas.Top)" Duration="0:0:8"> <SplineDoubleKeyFrame Value="50" KeyTime="0:0:2" KeySpline="0.25,0.75 0.75,0.25" /> <SplineDoubleKeyFrame Value="250" KeyTime="0:0:4" KeySpline="0.75,0.25 0.25,0.75" /> <SplineDoubleKeyFrame Value="50" KeyTime="0:0:6" KeySpline="0.25,0.75 0.75,0.25" /> <SplineDoubleKeyFrame Value="100" KeyTime="0:0:8" KeySpline="0.75,0.25 0.25,0.75" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </TextBlock.Triggers> </TextBlock> </Canvas>
</Canvas> </Grid> </UserControl>
|
4. Coding Animation
The final example in this chapter will show you how animations are
exposed in C# code. This allows you to control animations from script code and
will also provide a means to overcome the limitation of animations
always starting when the XAML files were loaded. The example will start
the animation when the mouse hovers over the elements on the canvas,
pause it when the mouse leaves the canvas, and resume it again when the
mouse pointer is back.
Since we need C# code in the example, we will use a XAML
code-behind C# file. The Visual Studio project template has already
created the file Page.xaml.cs for
us. We have looked at these .xaml.cs files before , but just to make sure, have a look at Example 6, where you'll see the file as it comes with the
template.
Example 6. Scripting animations, the original C# file (Page.xaml.cs, project
AnimationResources)
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes;
namespace AnimationResources { public partial class Page : UserControl { public Page() { // Required to initialize variables InitializeComponent(); } } }
|
As you can see, the two animations change the
x coordinate of a rectangle (MyRectangle) and a text block
(MyTextBlock).
|
You can take the process of separating a storyboard from the
animated element using <Canvas.Resources> one step
further by omitting the Storyboard.TargetName property and setting
it dynamically from C#. However, you can do that only if the animation
has not yet started
|
|
Example 7 shows the complete XAML markup. Note
that there are two mouse event handlers in the
<Canvas> element, but no triggers on the page.
Example 7. Scripting animations, the XAML file (Page.xaml, project
AnimationResources)
<UserControl x:Class="AnimationResources.Page" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="600" Height="400"> <Grid x:Name="LayoutRoot" Background="White"> <Canvas MouseEnter="beginAnimation" MouseLeave="pauseAnimation"> <Canvas.Resources> <Storyboard x:Name="MyRectangleStoryboard"> <DoubleAnimation From="0" To="300.456" Storyboard.TargetName="MyRectangle" Storyboard.TargetProperty="(Canvas.Left)" AutoReverse="True" RepeatBehavior="Forever"/> </Storyboard> <Storyboard x:Name="MyTextBlockStoryboard"> <DoubleAnimation From="0" To="300.456" Storyboard.TargetName="MyTextBlock" Storyboard.TargetProperty="(Canvas.Left)" AutoReverse="True" RepeatBehavior="Forever"/> </Storyboard> </Canvas.Resources> <Rectangle Width="300" Height="150" Stroke="Orange" StrokeThickness="15" x:Name="MyRectangle"/> <Canvas Canvas.Left="25" Canvas.Top="40"> <TextBlock FontFamily="Arial" FontSize="56" Foreground="Black" Text="Silverlight" x:Name="MyTextBlock"/> </Canvas> </Canvas> </Grid> </UserControl>
|
Accessing such a storyboard is easy. Just use the well-known
FindName() method
and provide the name of the <Storyboard> element, or
alternatively access the storyboards by their names. You can then
control the animations using these five methods:
-
Begin()
-
Starts an animation at the beginning
- Pause()
-
Pauses an animation
- Resume()
-
Resumes a paused animation
- Stop()
-
Stops an animation
- Seek(offset)
-
Jumps to a given position (using the hh:mm:ss
syntax) in the animation
We now need to work on the event handlers. When the mouse leaves
the canvas, we pause the animations using the pause() method. Here is the code:
private void pauseAnimation(object sender, MouseEventArgs e)
{
MyRectangleStoryboard.Pause();
MyTextBlockStoryboard.Pause();
}
Starting the animations is a bit more complicated because the
Begin() method starts an animation (or a storyboard, to be
exact) at the very beginning, but does not resume paused animations at
their current position. On the other hand, the Resume()
method resumes a paused animation, but does not start a stopped one.
Therefore, the C# property hasBegun
needs to remember whether the animation has already been started. Once
an animation has been started, it will not be stopped (only eventually
paused), since RepeatBehavior has been set to
Forever. This allows us to implement the MouseHover event handler. Example 8 shows the complete code.
Example 8. Scripting animations, the XAML C# file (Page.xaml.cs, project
AnimationResources)
Code View: using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media.Animation;
namespace AnimationResources { public partial class Page : UserControl { private bool hasBegun = false;
public Page() { // Required to initialize variables InitializeComponent(); }
private void beginAnimation(object sender, MouseEventArgs e) { if (hasBegun) { MyRectangleStoryboard.Resume(); MyTextBlockStoryboard.Resume(); } else { MyRectangleStoryboard.Begin(); MyTextBlockStoryboard.Begin(); hasBegun = true; } }
private void pauseAnimation(object sender, MouseEventArgs e) { MyRectangleStoryboard.Pause(); MyTextBlockStoryboard.Pause(); } } }
|
Figure 8 shows the application in action (but
you'll get the real experience when you try the code on your own). When
the mouse hovers over the canvas, the animation starts. If you move the
mouse pointer off the canvas, the animation stops, but if the mouse
pointer returns, the animation continues at the same position at which
it previously stopped.
Transformations and animations are quite different concepts, but
both can achieve impressive effects with little effort. With bigger
projects, you will probably resort to Microsoft Expression Blend 2 to
get these effects up and running, but this chapter also showed you how
to use code to provide additional functionality.