Printer Friendly Version      Send     
Click to Rate and Give Feedback
MSDN
MSDN Library
Web Development
Silverlight
 Working with Animations Programmati...
Silverlight 2
Working with Animations Programmatically (Silverlight 2)
[This topic is pre-release documentation and is subject to change in future releases. Blank topics are included as placeholders.]

There are times when you may want to change the properties of an animation dynamically (on the fly). For example, you might want to adjust the behavior of an animation that is applied to an object, depending on the object's current location in the layout, what kind of content the object contains, and so on. This overview introduces some basic techniques for manipulating animations dynamically by using procedural code (e.g. C#, VB .NET, etc).

This topic contains the following sections.

To understand this topic, you should be familiar with Microsoft Silverlight animations. For an introduction, see the Animation Overview (Silverlight 2).

The most direct way to access an animation object to change its properties is to give the animation object a name, and then reference it by that name in code. The following example consists of an Ellipse that animates to wherever you click on the screen. To accomplish this animation, the sample dynamically changes the To property of the PointAnimation object whenever the Canvas is clicked, after which the animation is started.

XAML
<Canvas MouseLeftButtonDown="Handle_MouseDown"
 Background="Gray" Width="600" Height="500">
    <Canvas.Resources>
      <Storyboard x:Name="myStoryboard">

        <!-- The PointAnimation has a name so it can be accessed
             from code. The To property in is left out of the XAML
             since the value of To is determined in code. -->
        <PointAnimation
         x:Name="myPointAnimation"
         Storyboard.TargetProperty="Center"
         Storyboard.TargetName="MyAnimatedEllipseGeometry"
         Duration="0:0:2"/>
      </Storyboard>
    </Canvas.Resources>

    <Path Fill="Blue">
      <Path.Data>

        <!-- Describes an ellipse. -->
        <EllipseGeometry x:Name="MyAnimatedEllipseGeometry"
         Center="200,100" RadiusX="15" RadiusY="15" />
      </Path.Data>
    </Path>

</Canvas>

C#
public void Handle_MouseDown(object sender, MouseButtonEventArgs e)
{
    // Retrieve current mouse coordinates.
    double newX = e.GetPosition(null).X;
    double newY = e.GetPosition(null).Y;
    Point myPoint = new Point();
    myPoint.X = newX;
    myPoint.Y = newY;
    myPointAnimation.To = myPoint;
    myStoryboard.Begin();
}

Run this sample.

It might not always be practical or convenient to have unique names for all your animations. Alternatively, you can access animations or keyframes of animations by using collections. For example, if you want to programmatically access all the keyframes within a DoubleAnimationUsingKeyFrames object, you could use code similar to the following.

XAML
<Canvas x:Name="parentCanvas" xmlns="http://schemas.microsoft.com/client/2007" 
  Width="600" Height="500" Background="Gray">
  <Canvas.Resources>
    <Storyboard x:Name="myStoryboard">
      <PointAnimationUsingKeyFrames
       x:Name="myPointAnimationUsingKeyFrames"
       Storyboard.TargetProperty="Center"
       Storyboard.TargetName="MyAnimatedEllipseGeometry"
       Duration="0:0:3">

         <!-- Set of keyframes -->
         <DiscretePointKeyFrame KeyTime="0:0:0" />
         <LinearPointKeyFrame KeyTime="0:0:0.5" />
         <SplinePointKeyFrame KeySpline="0.6,0.0 0.9,0.00" KeyTime="0:0:3" />
     </PointAnimationUsingKeyFrames>
    </Storyboard>

  </Canvas.Resources>

  <Path Fill="Blue">
    <Path.Data>

      <!-- Describes an ellipse. -->
      <EllipseGeometry x:Name="MyAnimatedEllipseGeometry"
       Center="200,100" RadiusX="15" RadiusY="15" />
    </Path.Data>
  </Path>
</Canvas>
C#
        public void Handle_MouseDown(object sender, MouseEventArgs e)
        {
            int i;
            for (i = 0; i < myPointAnimationUsingKeyFrames.KeyFrames.Count; i++)
            {
                // Do something with each keyframe, for example, set values.
            }
        }

The following example is similar to the previous one in that the ellipse follows where the user clicks on the screen, except that keyframes are used in this example. The collection of keyframes is iterated through and values are dynamically set on the keyframes so that the ellipse animates to the proper location.

XAML
<Canvas MouseLeftButtonDown="Handle_MouseDown"
 Width="600" Height="500" Background="Gray">
    <Canvas.Resources>
        <Storyboard x:Name="myStoryboard">
            <PointAnimationUsingKeyFrames
       x:Name="myPointAnimationUsingKeyFrames"
       Storyboard.TargetProperty="Center"
       Storyboard.TargetName="MyAnimatedEllipseGeometry"
       Duration="0:0:3">

                <DiscretePointKeyFrame KeyTime="0:0:0" />
                <LinearPointKeyFrame KeyTime="0:0:0.5" />
                <SplinePointKeyFrame KeySpline="0.6,0.0 0.9,0.00" KeyTime="0:0:2" />

            </PointAnimationUsingKeyFrames>

        </Storyboard>
    </Canvas.Resources>

    <Path Fill="Blue">
        <Path.Data>

            <!-- Describes an ellipse. -->
            <EllipseGeometry x:Name="MyAnimatedEllipseGeometry"
      Center="200,100" RadiusX="15" RadiusY="15" />
        </Path.Data>
    </Path>
</Canvas>

C#
// Global variables that keep track of the end point
// of the last animation.
double lastX = 200;
double lastY = 100;
public void Handle_MouseDown(object sender, MouseEventArgs e)
{
    // Retrieve current mouse coordinates.
    double newX = e.GetPosition(null).X;
    double newY = e.GetPosition(null).Y;

    int i;
    for (i = 0; i < myPointAnimationUsingKeyFrames.KeyFrames.Count; i++)
    {
        PointKeyFrame keyFrame = myPointAnimationUsingKeyFrames.KeyFrames[i];
        if (keyFrame.GetType().Name == "DiscretePointKeyFrame")
        {
            keyFrame.SetValue(DiscretePointKeyFrame.ValueProperty, new Point(lastX, lastY));
        }
        else if (keyFrame.GetType().Name == "LinearPointKeyFrame")
        {

            // The LinearKeyFrame has a value that is part way to the 
            // final end point. In addition, this value has to be on
            // the correct line, therefore, you need to use the line 
            // formula y = mx + b to find the values of x and y.

            // Calculate the slope
            double m = (newY - lastY) / (newX - lastX);

            // Calculate the y-intercept.
            double b = newY - (m * newX);

            // Set X to a third of the way to the end point.
            double intermediateX = lastX + (newX - lastX) / 3;

            // Find the value Y from X and the line formula.
            double intermediateY = (m * intermediateX) + b;

            // Set the keyframe value to the intermediate x and y value.
            keyFrame.SetValue(LinearPointKeyFrame.ValueProperty, new Point(intermediateX, intermediateY));
        }
        else if (keyFrame.GetType().Name == "SplinePointKeyFrame")
        {
            keyFrame.SetValue(SplinePointKeyFrame.ValueProperty, new Point(newX, newY));
        }
    }
    myStoryboard.Stop();
    myStoryboard.Begin();
    lastX = newX;
    lastY = newY;
}

Run this sample.

Note:

Storyboard has a Children property that enables you to access all the animation objects within a given Storyboard.

The most common scenario for dynamically changing the Storyboard.TargetName property is when you want to apply the same animation to more than one object. This is especially useful when you have a large number of objects that have similar animations applied to them. For example, you are displaying rows of images and you want to use an animation to highlight the image that currently has the mouse pointer over it. It is inconvenient and messy to have to create separate Storyboard objects for each image. It would be better to reuse the same Storyboard.

The following example has a number of rectangles that fade out and back into sight when you click them. All of these rectangles use the same Storyboard, because the DoubleAnimation that animates the Opacity changes its TargetName to whichever rectangle is clicked.

XAML
<StackPanel Orientation="Horizontal">
    <StackPanel.Resources>
        <Storyboard x:Name="myStoryboard">
            <DoubleAnimation x:Name="myDoubleAnimation"
             Storyboard.TargetProperty="Opacity"
             From="1.0" To="0.0" Duration="0:0:2"
             AutoReverse="True" />
        </Storyboard>
    </StackPanel.Resources>
    <Rectangle
   x:Name="MyAnimatedRectangle1"
   Margin="3" Width="100" Height="100" Fill="Blue"
   MouseLeftButtonDown="Start_Animation" />

    <Rectangle
   x:Name="MyAnimatedRectangle2"
   Margin="3" Width="100" Height="100" Fill="Blue"
   MouseLeftButtonDown="Start_Animation" />

    <Rectangle
   x:Name="MyAnimatedRectangle3"
   Margin="3" Width="100" Height="100" Fill="Blue"
   MouseLeftButtonDown="Start_Animation" />

    <Rectangle
   x:Name="MyAnimatedRectangle4"
   Margin="3" Width="100" Height="100" Fill="Blue"
   MouseLeftButtonDown="Start_Animation" />
</StackPanel>

C#
public void Start_Animation(object sender, MouseEventArgs e)
{

    // If the Storyboard is running and you try to change
    // properties of its animation objects programmatically, 
    // an error will occur.
    myStoryboard.Stop();

    // Get a reference to the rectangle that was clicked.
    Rectangle myRect = (Rectangle)sender;

    // Change the TargetName of the animation to the name of the
    // rectangle that was clicked.
    myDoubleAnimation.SetValue(Storyboard.TargetNameProperty, myRect.Name);

    // Begin the animation.
    myStoryboard.Begin();
}

Run this sample.

In the previous code, notice that you need to stop the Storyboard before you dynamically change the properties of its animation objects; otherwise, an error will occur. In this example, it might not be desirable to stop an animation on one rectangle so that the animation can start on another rectangle. Perhaps you want both animations to run at the same time. However, you cannot use the same animation object to run two separate animations at the same time, because there is only one TargetName. This does not mean that you are back to creating a separate Storyboard for every object. Instead, you need one Storyboard for each animation that you want to run concurrently (synchronously). The following example is similar to the previous one, except that it contains three Storyboard objects instead of one. When you click a rectangle, the script looks for a Storyboard that is not currently in use and uses that one to create the animation.

XAML
<StackPanel Orientation="Horizontal">
  <StackPanel.Resources>
    <Storyboard x:Name="myStoryboard1" Completed="Storyboard_Completed">
      <DoubleAnimation x:Name="myDoubleAnimation1"
       Storyboard.TargetProperty="Opacity"
       From="1.0" To="0.0" Duration="0:0:2" AutoReverse="True" />
    </Storyboard>
    <Storyboard x:Name="myStoryboard2" Completed="Storyboard_Completed">
      <DoubleAnimation x:Name="myDoubleAnimation2"
       Storyboard.TargetProperty="Opacity"
       From="1.0" To="0.0" Duration="0:0:2"
       AutoReverse="True" />
     </Storyboard>
     <Storyboard x:Name="myStoryboard3" Completed="Storyboard_Completed">
       <DoubleAnimation x:Name="myDoubleAnimation3"
        Storyboard.TargetProperty="Opacity"
        From="1.0" To="0.0" Duration="0:0:2"
        AutoReverse="True" />
     </Storyboard>
   </StackPanel.Resources>
   <Rectangle x:Name="MyAnimatedRectangle1"
    Margin="3" Width="100" Height="100" Fill="Blue"
    MouseLeftButtonDown="Start_Animation" />

   <Rectangle x:Name="MyAnimatedRectangle2"
    Margin="3" Width="100" Height="100" Fill="Blue"
    MouseLeftButtonDown="Start_Animation" />

   <Rectangle x:Name="MyAnimatedRectangle3"
     Margin="3" Width="100" Height="100" Fill="Blue"
     MouseLeftButtonDown="Start_Animation" />

   <Rectangle x:Name="MyAnimatedRectangle4"
     Margin="3" Width="100" Height="100" Fill="Blue"
     MouseLeftButtonDown="Start_Animation" />
</StackPanel>

C#
bool storyboard1Active = false;
bool storyboard2Active = false;
bool storyboard3Active = false;

public void Start_Animation(object sender, MouseEventArgs e)
{
    // Get a reference to the rectangle that was clicked.
    Rectangle myRect = (Rectangle)sender;
    if (!storyboard1Active)
    {
        myStoryboard1.Stop();
        myDoubleAnimation1.SetValue(Storyboard.TargetNameProperty, myRect.Name);
        myStoryboard1.Begin();
        storyboard1Active = true;
    }
    else if (!storyboard2Active)
    {
        myStoryboard2.Stop();
        myDoubleAnimation2.SetValue(Storyboard.TargetNameProperty, myRect.Name);
        myStoryboard2.Begin();
        storyboard2Active = true;
    }
    else if (!storyboard3Active)
    {
        myStoryboard3.Stop();
        myDoubleAnimation3.SetValue(Storyboard.TargetNameProperty, myRect.Name);
        myStoryboard3.Begin();
        storyboard3Active = true;
    }
}

public void Storyboard_Completed(object sender, EventArgs e)
{
    Storyboard myStoryboard = sender as Storyboard;
    switch (myStoryboard.GetValue(NameProperty).ToString())
    {
        case "myStoryboard1": storyboard1Active = false; break;

        case "myStoryboard2": storyboard2Active = false; break;

        case "myStoryboard3": storyboard3Active = false; break;
    }
}

Run this sample.

In the previous example, only three animations can run at the same time (equal to the number of Storyboard objects). This is fine if you do not anticipate a need for more concurrent animations, which would require more Storyboard objects. If you expect a lot of independent animations to be running at the same time, you might want to create your Storyboard objects dynamically. See the next section for an example of this.

You can also create an animation completely in procedural code (e.g. C#, VB .NET, etc). The following example shows how to create an animation that animates the Canvas.Top and Canvas.Left attached properties of a rectangle.

C#
public void Create_And_Run_Animation(object sender, EventArgs e)
{
    // Create a red rectangle that will be the target
    // of the animation.
    Rectangle myRectangle = new Rectangle();
    myRectangle.Width = 200;
    myRectangle.Height = 200;
    Color myColor = Color.FromArgb(255, 255, 0, 0);
    SolidColorBrush myBrush = new SolidColorBrush();
    myBrush.Color = myColor;
    myRectangle.Fill = myBrush;

    // Add the rectangle to the tree.
    LayoutRoot.Children.Add(myRectangle);

    // Create a duration of 2 seconds.
    Duration duration = new Duration(TimeSpan.FromSeconds(0.2));

    // Create two DoubleAnimations and set their properties.
    DoubleAnimation myDoubleAnimation1 = new DoubleAnimation();
    DoubleAnimation myDoubleAnimation2 = new DoubleAnimation();

    myDoubleAnimation1.Duration = duration;
    myDoubleAnimation2.Duration = duration;

    Storyboard sb = new Storyboard();
    sb.Duration = duration;

    sb.Children.Add(myDoubleAnimation1);
    sb.Children.Add(myDoubleAnimation2);

    Storyboard.SetTarget(myDoubleAnimation1, myRectangle);
    Storyboard.SetTarget(myDoubleAnimation2, myRectangle);

    // Set the attached properties of Canvas.Left and Canvas.Top
    // to be the target properties of the two respective DoubleAnimations
    Storyboard.SetTargetProperty(myDoubleAnimation1, new PropertyPath("(Canvas.Left)"));
    Storyboard.SetTargetProperty(myDoubleAnimation2, new PropertyPath("(Canvas.Top)"));

    myDoubleAnimation1.To = 200;
    myDoubleAnimation2.To = 200;

    // Make the Storyboard a resource.
    LayoutRoot.Resources.Add("unique_id", sb);

    // Begin the animation.
    sb.Begin();
}

Run this sample.

Note:

Do not attempt to call Storyboard APIs (e.g. Begin) within the constructor of the page. This will cause your animation to silently fail.

© 2008 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Page view tracker