views:

333

answers:

1

How can I remove a storyboard in XAML (i.e. RemoveStoryboard action in a DataTrigger) but keep the value that was animated. Similar to the Animatable.BeginAnimation:

If the animation's BeginTime is null, any current animations will be removed and the current value of the property will be held.

+1  A: 

RemoveStoryboard's primary use is to remove the animated values and set them back to their un-animated state. In most cases you can just switch the call to PauseStoryboard or StopStoryboard instead, depending on the specific case. The only exception is when you need to free up resources held by the storyboard or use it for another purpose.

If you really want to remove the storyboard and keep the property values, you must set the animated values directly on the properties. This can be done by setting each value to the animated value, something like this:

void CopyAnimatedValuesToLocalValues(DependencyObject obj)
{
  // Recurse down tree
  for(int i=0; i<VisualTreeHelper.GetChildrenCount(obj); i++)
    CopyAnimatedValuesToLocalValues(VisualTreeHelper.GetChild(obj, i));

  var enumerator = obj.GetLocalValueEnumerator();
  while(enumerator.MoveNext())
  {
    var prop = enumerator.Current.Property;
    var value = enumerator.Current.Value as Freezable;

    // Recurse into eg. brushes that may be set by storyboard, as long as they aren't frozen
    if(value!=null && !value.IsFrozen)
      CopyAnimatedValuesToLocalValues(value);

    // *** This is the key bit of code ***
    if(DependencyPropertyHelper.GetValueSource(obj, prop).IsAnimated)
      obj.SetValue(prop, obj.GetValue(prop));

  }
}

Call this right before you remove the storyboard to copy the animated values.

Edit A comment was made that this code may be unnecessary because calling BeginAnimation with BeginTime=null achieves a similar effect.

While it is true that BeginAnimation with BeginTime=null makes it looks as if the values were copied to local, a later call to RemoveStoryboard will cause the values to revert. This is because BeginAnimation with BeginTime=null causes the prior animation to hold its values pending the start of the new animation, but does nothing to affect the local values.

The code above actually overwrites local values, so all animations can be removed and the objects will still have their new values. So if you really want to call RemoveStoryboard and still keep your values, you will need the code I wrote above or something like it.

Ray Burns
As I mentioned in my question I would like to to it in XAML. The main goal here to start a new animation based on a DataTrigger that starts with the current value. AFAIK as I know that can not be achieved by pausing or stopping the current storyboard because it would either hold or reset the current value. Also note your code is not needed in WPF. You can simply call Animatable.BeginAnimation with an animation that has the BeginTime set to null. It will have the same effect the value is copied to local.
bitbonk
I'm not sure I understand your need. If you just want to start a new animation, why not just do so without calling doing a RemoveAnimation action at all? WPF is designed to start with the current values in that situation. Is it not doing so?
Ray Burns
It is not true that calling BeginAnimation with BeginTime=null has the same effect as my code. Specificallly, it does not copy values to local. If you call BeginAnimation with BeginTime=null and later call RemoveAnimation, the values will revert to original because the local values were never changed. But if you use my code the values will stick because they are actually copied to local. I was explaining what was necessary to get the local values to stick even after RemoveAnimation, and code like I wrote IS necessary. Does that make sense?
Ray Burns
You are right, it is not the same. About your question: If I remove a storyboard before starting a new one, the animated value jumps back to the default (unanimated) value. I don't want that to happen I want to start with the value where the other animation stopped. plus I need a XAML-only solution. But with Datatrigger (wich seems to be the only way to trigger animations based on ViewModel state) I always have to remove the old animation in DataTrigger.ExitAction.
bitbonk
Lets make a simple exercise (that seems really hard to accomplish): Write a datatemplate that slowly animates its color to green when the bound ViewModel's state goes to "active". When the state goes to "inactive" it animates its color to gray starting with whatever color it currently has. When the state changed to "unkown" start to animate its color to yellow, again starting with whatever color the object currently has. It is such a simple scenario, but it is really giving me a hard time. I tend to think that this is where the WPF animation system falls short.
bitbonk