Export (0) Print
Expand All

Working with Animations Programmatically

Silverlight

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. You can manipulate animations dynamically by using procedural code (for example, C# or Visual Basic).

This topic contains the following sections.

You should be familiar with Silverlight animations. For an introduction, see Animation Overview.

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, an event handler changes the To property of the PointAnimation object when the Canvas is clicked, and then it starts the animation.

Run this sample


<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 is left out of the XAML
             because 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>



private 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();
}


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.

<Canvas x:Name="parentCanvas" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  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>
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, and values are dynamically set on the keyframes so that the ellipse animates to the proper location.

Run this sample


      <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>



// Global variables that keep track of the end point
// of the last animation.
double lastX = 200;
double lastY = 100;
private 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;
}


NoteNote:

Storyboard has a Children property that enables you to access all the animation objects in 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 might be displaying rows of images and want to use an animation to highlight the image that currently has the mouse pointer over it. It is inconvenient and messy to create separate Storyboard objects for each image. It is 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.

Run this sample


<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>



private 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();
}


In the preceding code, notice that you must 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 event handler looks for a Storyboard that is not currently in use and uses that one to create the animation.

Run this sample


<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>



bool storyboard1Active = false;
bool storyboard2Active = false;
bool storyboard3Active = false;

private 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;
    }
}

private 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;
    }
}


In the preceding 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. For an example of creating storyboard objects in code, see the next section.

You can also create an animation completely in procedural code. The following example shows how to create an animation that animates Canvas.Top and Canvas.Left attached properties of a rectangle.

Run this sample


private 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(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();
}


Note Note:

Do not attempt to call Storyboard members (for example, the Begin method) in the constructor of the page. This will cause your animation to fail silently.

Community Additions

ADD
Show:
© 2014 Microsoft