How to know when your Animation completes and ...
The AnimationTimeline classes in the Windows Presentation Foundation such as DoubleAnimation and ColorAnimation all have a common event Completed. I often use completed to know when an animation I have started in code has finished. If there's only one animation which is often the case, I can easily map the animation Completed event to the animated UIElement logically. However, if I start multiple animations using BeginAnimation, the Completed event unfortunately does not provide any information as to the control that was animated. (Bummer!)
Instead, the Completed event's "sender" is the timeline's Clock. The second parameter to the event is EventArgs, which of course doesn't contain any useful data. So, how might you connect the Clock to the actual target UIElement? There are a couple of ways actually, and I'll present one of them here.
My example uses a DoubleAnimation. Here's the code before:
DoubleAnimation anim = new DoubleAnimation();
anim.Duration = new Duration(TimeSpan.FromSeconds(0.5));
anim.To = 0.1;
anim.FillBehavior = FillBehavior.Stop;
anim.Completed += new EventHandler(AnimationCompleted);
myControl.BeginAnimation(UIElement.OpacityProperty, anim);
Unfortunately, there's no way to tie the PointAnimation instance (anim) to the UIElement, myControl. So, in a typical simple object oriented solution, I created a new derived class, PointAnimationPlus:
DoubleAnimationPlus anim = new DoubleAnimationPlus();
anim.Duration = new Duration(TimeSpan.FromSeconds(0.5));
anim.To = 0.1;
anim.FillBehavior = FillBehavior.Stop;
anim.Completed += new EventHandler(AnimationCompleted);
anim.TargetElement = myControl;
myControl.BeginAnimation(UIElement.OpacityProperty, anim);
Here's my implementation of DoubleAnimationPlus:
internal class DoubleAnimationPlus : DoubleAnimation
{
private UIElement _target;
public UIElement TargetElement
{
get { return _target; }
set { _target = value; }
}
protected override Freezable CreateInstanceCore()
{
DoubleAnimationPlus p = new DoubleAnimationPlus();
p.TargetElement = this.TargetElement;
return p;
}
}
static void AnimationCompleted(object sender, EventArgs e)
{
Clock c = sender as Clock;
DoubleAnimationPlus anim = c.Timeline as DoubleAnimationPlus;
if (anim != null)
{
if (anim.TargetElement != null)
{
Console.WriteLine("DoubleAnimation TargetElement is set!");
}
}
}
As you can see, very simple. The only real trick is that you must override the method CreateInstanceCore. Since an animation is a Freezable object, when the animation completes, a Clone (or CloneCurrentValue) is made (which eventually creates a new instance of the DoubleAnimationPlus class). In my simple implementation, my code creates a new instance of the DoubleAnimationPlus class and then copies the reference to the TargetElement. If you try this code and neglect to override the CreateInstanceCore, you'll immediately note that TargetElement property isn't set in the Completed event -- that's because you didn't actually create an instance properly (and copy the new values).
That's all there is. The other method I considered was to use dependency properties -- but I wanted the explicitness of this technique for now.
Comments
when you say the other method was to use DependencyProperty, did you mean attached properties? in theory you could use the exising Storyboard.TargetName and Storyboard.TargetProperty to define the UIElement as attached properties on the animation, as though you were using a storyboard. then when the Completed event fires, you walk the tree looking for the element with that name.
of course, this will only work with elements in a given tree, but presumably since you started the animation from code, you'll know how to get to the control you are looking for so that you can look it up by name.
Posted by: eburke | October 2, 2006 10:33 PM
Yes, more specifically I was referring to creating an attached dependency properties.
I'd create a new property rather than reuse the existing ones so that I could maintain a real reference later (so as to not use a name lookup) and to make it more clear what was happening.
I've found that there are lots of animation scenarios in particular that need to be wired up in code to make them work well. I wish it wasn't that way, but it's V1.
Posted by: Aaron | October 3, 2006 7:12 AM