10
votes

I am using a DoubleAnimation to anamiate the Angle property of a RotationTransform. Several times per second, I need to change the rate of the rotation in response to external data so that the rotation speeds up and/or slows down (smoothly) over time. I am currently doing this by using a DoubleAnimation that repeats forever from 0.0 to 360.0 with duration X, then several times per second:

  • Grab a new value from external data
  • Modify the rate on the DoubleAnimation to that value
  • Re-Apply the DoubleAnimation to the Angle property again

Note: I did find that I had to change the To and From properties on the animation to "current angle" and "current angle+360" - lucky for me RotationTransform has no trouble with angles > 360 degrees - to prevent starting the rotation over again from zero angle.

My question is: Is this reasonable? It does not seem so. Continously applying new DoubleAnimations to the Angle property on a rotation transform seems wrong - sort of like I am letting WPF animate the rotation, while I am animating the rotation speed myself.

Is there a better way?

1

1 Answers

9
votes

On the storyboard there is a SpeedRatio setting which is a multiplier to the duration. You cannot bind to this however as it is not a dependency property.

To get around this you can use the SetSpeedRatio function on the storyboard. Note this only works if the story board is started in code (other wise you get an error).

The code below is an full example of how you would raise event in an object to effect the speed of the animation of a spinning rectangle. The purpose of the textbox and data bindings are to update the background object. The button is just so the textbox looses focus and updates the object.

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
      <Rectangle Margin="50" Width="50" Height="50" Fill="Red" x:Name="rc">
        <Rectangle.RenderTransform>
          <RotateTransform x:Name="TransRotate"
                           CenterX="25" CenterY="25" Angle="0" />
        </Rectangle.RenderTransform>
        <Rectangle.Resources>
          <Storyboard x:Key="spin">
            <DoubleAnimation x:Name="da" 
                             Storyboard.TargetName="TransRotate" 
                             Storyboard.TargetProperty="Angle"
                             By="360" 
                             Duration="0:0:10"  
                             AutoReverse="False" 
                             RepeatBehavior="Forever" />
          </Storyboard>
        </Rectangle.Resources>
      </Rectangle>
      <TextBox Text="{Binding Speed}" />
      <Button>Update Speed</Button>
    </StackPanel>
</Window>

Then the C# code

{
    public Window1()
    {
        InitializeComponent();

        //create new  object
        BackgroundObject bo = new BackgroundObject();

        //binding only needed for the text box to change speed value
        this.DataContext = bo;

        //Hook up event
        bo.SpeedChanged += bo_SpeedChanged;

        //Needed to prevent an error
        Storyboard sb = (Storyboard)rc.FindResource("spin");
        sb.Begin(); 
    }

    //Change Speed
    public void bo_SpeedChanged(  object sender, int newSpeed)
    {
        Storyboard sb = (Storyboard)rc.FindResource("spin");
        sb.SetSpeedRatio(newSpeed);
    }
}

public delegate void SpeedChangedEventHandler(object sender, int newSpeed);

public class BackgroundObject
{
    public BackgroundObject()
    {
        _speed = 10;
    }

    public event SpeedChangedEventHandler SpeedChanged;

    private int _speed;
    public int Speed
    { 
        get { return _speed; }
        set { _speed = value; SpeedChanged(this,value); }
    }
}

I am sure you can adapt to your usage.