FrameBased Animation
Along with the property-based animation system, Silverlight provides a way to create frame-based animation using nothing but code. All you need to do is respond to the static CompositionTarget.Rendering event, which is fired to get the content for each frame. This is a far lower-level approach, which you won't want to tackle unless you're sure the standard property-based animation model won't work for your scenario (for example, if you're building a simple side-scrolling game, creating physics-based animations, or modeling particle effects such as fire, snow, and bubbles).
The basic technique for building a frame-based animation is easy. You simply need to attach an event handler to the static CompositionTarget.Rendering event. Once you do, Silverlight will begin calling this event handler continuously. (As long as your rendering code executes quickly enough, Silverlight will call it 60 times each second.) In the rendering event handler, it's up to you to create or adjust the elements in the window accordingly. In other words, you need to manage all the work yourself. When the animation has ended, detach the event handler.
Figure 9-9 shows a straightforward example. Here, a random number of circles fall from the top of a Canvas to the bottom. They fall at different speeds (based on a random starting velocity), but they accelerate downward at the same rate. The animation ends when all the circles reach the bottom.
- Figure 9-8. A frame-based animation of falling circles
In this example, each falling circle is represented by an Ellipse element. A custom class named EllipseInfo keeps a reference to the ellipse and tracks the details that are important for the physics model. In this case, there's only one piece of information—the velocity at which the ellipse is moving along the X axis. (You could easily extend this class to include a velocity along the Y axis, additional acceleration information, and so on.)
public class EllipseInfo {
public Ellipse Ellipse {
public double VelocityY {
public EllipseInfo(Ellipse ellipse, double velocityY) {
VelocityY = velocityY; Ellipse = ellipse;
The application keeps track of the Ellipselnfo object for each ellipse using a collection. There are several more window-level fields, which record various details that are used when calculating the fall of the ellipse. You could easily make these details configurable.
private List<EllipseInfc> ellipses = new Lis' < LlipseInf >();
private double accelerationY = 0.1; private int minStartingSpeed = 1; private int maxStartingSpeed = 50; private double speedRatio = 0.1; private int minEllipses = 20; private int maxEllipses = 100; private int ellipseRadius = 10;
private SolidColorBrush ellipseBrush = new SolidColorBrush(Iolors.Green);
When a button is clicked, the collection is cleared, and the event handler is attached to the CompositionTarget.Rendering event:
private bool rendering = false;
private void cmdStart_Clicked(object sender, RoutedEventArg; e) {
ellipses.Clear(); canvas.Children.Clear();
CompositionTarget.Rendering += RenderFrame; rendering = true;
If the ellipses don't exist, the rendering code creates them automatically. It creates a random number of ellipses (currently, between 20 and 100) and gives each of them the same size and color. The ellipses are placed at the top of the Canvas, but they're offset randomly along the X axis.
private void RenderFrame(object sender, EventArgs e) {
// Animation just started. Create the ellipses. int halfCanvasWidth = (int)canvas.ActualWidth / 2;
int ellipseCount = rand.Next(minEllipses, maxEllipses+1);
// Create the ellipse. Ellipse ellipse = new Ellips (); ellipse.Fill = ellipseBrush; ellipse.Width = ellipseRadius; ellipse.Height = ellipseRadius;
// Place the ellipse.
Canvas .SetLeft(ellipse, halfCanvasWidth +
rand.Next(-halfCanvasWidth, halfCanvasWidth)); Canvas .SetTop(ellipse, 0); canvas.Children.Add(ellipse);
// Track the ellipse.
Ellipselnfc info = new EllipseInf (ellipse, speedRatio * rand.Next(minStartingSpeed, maxStartingSpeed)); ellipses.Add(info);
If the ellipses already exist, the code tackles the more interesting job of animating them. Each ellipse is moved slightly using the Canvas.SetTop() method. The amount of movement depends on the assigned velocity.
Ellipselnfc info = ellipses[i];
double top = Canvas.GetTop(info.Ellipse);
Canva:.SetTop(info.Ellipse, top + 1 * info.VelocityY);
To improve performance, the ellipses are removed from the tracking collection as soon as they've reached the bottom of the Canvas. That way, you don't need to process them again. To allow this to work without causing you to lose your place while stepping through the collection, you need to iterate backward, from the end of the collection to the beginning.
If the ellipse hasn't yet reached the bottom of the Canvas, the code increases the velocity. (Alternatively, you could set the velocity based on how close the ellipse is to the bottom of the Canvas for a magnet-like effect.)
if (top >= (canvas.ActualHeight - ellipseRadius*2)) {
// This circle has reached the bottom. // Stop animating it. ellipses.Remove(info);
// Increase the velocity. info.VelocityY += accelerationY;
Finally, if all the ellipses have been removed from the collection, the event handler is removed, allowing the animation to end:
// End the animation.
// There's no reason to keep calling this method // if it has no work to do. CompositionTarget.Rendering -= RenderFrame; rendering = false;
Obviously, you could extend this animation to make the circles bounce, scatter, and so on. The technique is the same—you simply need to use more complex formulas to arrive at the velocity.
There's one caveat to consider when building frame-based animations: they aren't time-dependent. In other words, your animation may run faster on fast computers, because the frame rate will increase and your CompositionTarget.Rendering event will be called more frequently. To compensate for this effect, you need to write code that takes the current time into account.
Post a comment