Removing the value applied by an animation

By default, the final value of an animation will indefinitely apply to the property it is animating.  This is because the default value of an animation’s FillBehavior property is ‘HoldEnd.’  Since only a dependency property (DP) can be animated in WPF, and the DP value resolution algorithm treats an animation’s value as a higher priority source than a local value, if you try to set an animated property to a certain value in code, your value will be ignored.

For example, suppose I want to animate the Height property of a Rectangle element to 200.  I could create the following XAML to accomplish that task:

When that Rectangle is loaded into the UI, it would grow in height from 10 to 200 logical pixels over the course of two seconds.  Since the DoubleAnimation applied to the Height property is using the default FillBehavior, which continues to apply the value 200 to Height after the animation finishes running, setting the element’s Height in code would have no effect.  What I would need to do is remove that animation from the property, so that the value resolution mechanism built into the DP system would use my locally set value.

The trick to removing an animation’s value from a DP is to begin a “null animation” against that property.  This is quite simple to do, as it only requires one method call:

After running those two lines of code, the Rectangle would immediately become 100 logical pixels tall.  UIElement and ContentElement both define the BeginAnimation method, so all elements in WPF have this ability built in.

12 Responses to Removing the value applied by an animation

  1. Ryan says:

    This is a pretty interesting technique. I’ve been setting FillBehavior to FillBehavior.Stop and then using an anonymous delegate on the animation’s Completed event. However, I really like the fact that your method is just 1 line of code. Great job.

  2. Eric says:

    Excellent as always, Josh!

    Quick question: if one omitted the “Height = 100” assignment in the event handler, would the Height value revert to its original value (10, as set in the xaml)?

    In my case, the original value is actually a binding, so I think I need something more like this:

    ZoomSlider.BeginAnimation(Slider.ValueProperty, null);
    ZoomSlider.SetBinding(Slider.ValueProperty, new Binding(…));

    I had kind of thought (hoped!) that once the animation was cancelled, the original value (binding or otherwise) would prevail.

    Thoughts?

  3. Parx says:

    This works well for code-behind, but for those using MVC or who liberally use DataTemplates, I guess an attached property would be needed to generically clear out the animation on some property.

  4. Rob Burke says:

    Ah, nice trick, thanks for this post. Although I’m one of those who liberally use DataTemplates (as Parx suggests) – so for the most part I try to avoid getting myself into situations where something like this is necessary.

  5. Vassi says:

    I actually have a tangentially related problem with just this kind of animation. Why is it that when I programatically apply a DoubleAnimation to opacity it works so long as I never explicitly assign an opacity value? For instance, if an image is visible by default and I run an animation on it to make it ‘pulse’ everything works fine.

    However, if I make the image transparent to 0, and then make it opaque (opacity = 100) – just before applying the exact same animation as before – it does not animate, though it does ‘blink’ for a split second as the animation completes. It’s kind of driving me bonkers.

  6. Josh Smith says:

    Parx and Rob,

    I don’t understand why you say that using DataTemplates is somehow mutually exclusive to working with animations in code. A DataTemplate can contain any elements, including a UserControl or custom Control that has a code-behind.

    Vassi,

    That’s strange behavior. I’m not sure why you’re seeing that. You might want to ask on the WPF Forum, perhaps someone there has an explanation for you.

    Josh

  7. Vassi says:

    Not that any of you were sitting on the edge of your seat, but I wanted to mention the resolution to the problem. The gist of it is that if you create a new, blank, WPF project and put any element with an opacity property in it then put the following code in the constructor (or elsewhere) you get the problem I described: (I named the element Pulser)

    System.Windows.Media.Animation.DoubleAnimation doub = new System.Windows.Media.Animation.DoubleAnimation();
    Pulser.Opacity = 100;
    doub.To = 0;
    doub.AutoReverse = true;
    doub.Duration = new Duration(TimeSpan.FromSeconds(2));
    doub.RepeatBehavior = System.Windows.Media.Animation.RepeatBehavior.Forever;

    Pulser.BeginAnimation(Button.OpacityProperty, doub);

    If you comment the Pulser.Opacity = 100 everything works as expected. The workaround, and it feels very much like a workaround, is to call Pulser.ClearValue(Button.OpacityProperty) just before you call BeginAnimation. This makes it so that I can set the opacity to 0 when not in use and still be able to do the simple animation. I don’t really get why DoubleAnimation can’t do this for me, especially if I were to set the From property to 100, but there you go.

    (Sorry for being so verbose, Josh, I enjoy reading all of your posts!)


    V

  8. Daniel G. says:

    Opacity runs from 0 to 1. If you’re running an animation from 100 to 0 and back, the animation only runs for 1/100th of the time and otherwise is completely opaque (any opacity value > 1)

  9. Vassi says:

    Actually, that makes complete sense.

    I blame Blend for having their sliders display 0 to 100 values. Heh. I’ll have to try that.


    V

  10. Neil Mosafi says:

    I have used a RemoveStoryboard action to do something similar.

    So if you named the BeginStoryboard which you use for the loaded event:

    …….

    you can then add an animation on a button click event to remove it:

  11. Neil Mosafi says:

    Sorry the XAML got removed from the last comment, wanted to say if you named the BeginStoryboard which you use for the loaded event like this:

    BeginStoryboard Name=”loadedStoryboard”

    you can then add an animation on a button click event to remove it:

    Button
    Button.Triggers
    EventTrigger RoutedEvent=”Button.Click”
    EventTrigger.Actions
    RemoveStoryboard BeginStoryboardName=”loadedStoryboard”

  12. Rob Burke says:

    Josh – I definitely don’t think that using DataTemplates is somehow mutually exclusive to working with animations in code. From a code maintenance perspective I find I prefer to keep my animations all handled by the triggering framework. But there are also times when using code makes more sense – for sure.