Synchronous WPF animation

8 02 2012

WPF animations run asynchronously, this  is a fact of life I run into a while back. Searching for synchronous WPF animations delivered some stackoverflow questions and the conventional wisdom seems to be “animations are asynchronous, get over it and do stuff in the Completed event.” This is fine if you know what the next code should do and can therefore move it into the Completed handler but I was facing a different problem. I ran the animation from a COM visible method and the COM clients of my object expect the animation to be finished by the time the method returns.

The simple solution would be to add a signal to the Completed handler and then wait on this signal, so a helper function would look like this:

private void RunStoryBoardAndSignal(Storyboard sb, EventWaitHandle completedHandle)
{
    Show();

    EventHandler handler = null;
    handler = (sender, args) =>
    {
        Hide();
        sb.Completed -= handler; // remove self 
        completedHandle.Set(); // signal completion 
    };

    sb.Completed += handler;

    sb.Begin();
}

This method will then be called thus:

    AutoResetEvent animationDone = new AutoResetEvent(false);
    _animation.RunStoryBoardAndSignal(_sb, animationDone);
    animationDone.WaitOne();

The problem is that the animation runs on the thread which ran it, so having the thread suspend itself to wait for the animation to complete prevents the animation from starting to run (also from completing…).

The trick is to create another thread for the animation, now this thread must be a UI thread otherwise no UI can take place and the COM object will have to be able to run code on this thread. Lucky .NET controls remember their owner thread so we only have to hold on to a control created in the new thread and don’t even have to save the thread.

First things first, create the new thread when the COM object is created.

    public MyComObject()
    {
        // Create the animation on a separate thread so we can wait for the animations to finish.
        var worker = new Thread(() =>
        {
            _animation = new AnimationControl();

            // When the window is closed (by Destroy) make sure the message loop will be stopped so the thread can die
            _animation.Closed += (sender, e) => _animation.Dispatcher.InvokeShutdown(); 

            Dispatcher.Run(); // start message loop
        });
        worker.SetApartmentState(ApartmentState.STA);
        worker.Start();
    }

Since we created a thread we’ll have to clean up, this can be done after the animation is finished but since I have a few animations I leave the thread alive and terminate it when we’re done with the COM object.

    public void Destroy()
    {
        _animation.Dispatcher.Invoke(new Action(() =>
        { _animation.Close(); }));
        _animation= null;
    }

And now we’re set up, all we have to do is dispatch the method to other UI thread and wait for it to finish.

    public void Animate()
    {
        AutoResetEvent animationDone = new AutoResetEvent(false);
        _animation.Dispatcher.Invoke(new Action(() =>
        {
            _animation.RunStoryBoardAndSignal(_sb, animationDone);
        }));
        _animation.WaitOne();
    }

Nothing to it, I hope this post will help the next person searching for “synchronous WPF animation.” 

Advertisements

Actions

Information

One response

1 11 2012
Andrew BT

Hey – good post. I’m a bit thick, can you help with a complete code example to do this? I need to use animations to emulate DependencyObject.SetCurrentValue in Silverlight. Here is my blocking code, which of course doesn’t work due to threading issues:

public static void SetCurrentValueTest(this FrameworkElement dependencyObject, DependencyProperty property, object value)
{
var waitHandle = new ManualResetEvent(false);
var storyboard = new Storyboard();
storyboard.Duration = new Duration(TimeSpan.Zero);
var animation = new ObjectAnimationUsingKeyFrames();
animation.KeyFrames.Add(new DiscreteObjectKeyFrame(value, KeyTime.FromTimeSpan(TimeSpan.Zero)));
storyboard.Children.Add(animation);
Storyboard.SetTarget(animation, dependencyObject);
Storyboard.SetTargetProperty(animation, new PropertyPath(property));
storyboard.Completed += (s, e) =>
{
waitHandle.Set();
};
storyboard.Begin(dependencyObject);
waitHandle.WaitOne();
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: