Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF: What is the correct way to wait for a storyboard animation to complete before performing an operation

I have a storyboard which I reuse to animate some pictures, I wanna perform some operation after each animation, which includes some calculations, and then running another animation, so I believe I should be using the StoryBoard's Completed Event MyStoryboard.Completed += storyboard_Com​pleted;

What I'm curious about is, should I start the next animation in the current StoryBoard's Storyboard_Completed Event? And, are there any implications if I started the first animation in a separate thread using the Application.Current.Dispatcher Object?

If I called a StoryBoard.Begin() in a separate thread using the Application.Current.Dispatcher, does the Storyboard_Completed Event also get invoked in the UI thread? In this case, do I still need to wrap the Next Animation within another Dispatcher Invoke?

private void Story_Completed(object sender, EventArgs e)
{
    Application.Current.Dispatcher.Invoke((Action)delegate()
    {
       SomeNewStoryBoardAnimation.Begin();
    }
}

Is this correct? Or is there a better way to check if a storyboard has ended and start the next set of calculations & storyboard animation right after that?

I've thought of using a single background worker to handler all animations and calculations in sequence, but I'm also wondering how to "wait" for the animation to complete before starting on the next set of calculations and animations. Is it normal for a BackGroundWorker to have Thread.sleep while waiting for animation to complete?

like image 996
black eyed pea Avatar asked Feb 04 '13 16:02

black eyed pea


2 Answers

You could wrap the Storyboard in a Task object and await its completion.

Here is an excellent bit of sample code illustrating how to do just that, taken from a blog post by Morten Nielsen:

public static class StoryboardExtensions
{
    public static Task BeginAsync(this Storyboard storyboard)
    {
        System.Threading.Tasks.TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
        if (storyboard == null)
            tcs.SetException(new ArgumentNullException());
        else
        {
            EventHandler<object> onComplete = null;
            onComplete = (s, e) => {
                storyboard.Completed -= onComplete; 
                tcs.SetResult(true); 
            };
            storyboard.Completed += onComplete;
            storyboard.Begin();
        }
        return tcs.Task;
    }
}

Essentially you're creating an extension method, which returns a Task object signalling the completion of the Storyboard. In this way, you get some nice fluid syntax like this:

//Start the storyboard and asynchronously await completion...
await myStoryboard.BeginAsync();

//Do my other stuff here, after the storyboard completes...
like image 103
BTownTKD Avatar answered Nov 15 '22 21:11

BTownTKD


Using the Storyboard.Completed event should work for your purposes. The Storyboard.Completed event handler should fire on the UI thread, so you should not need to call Application.Current.Dispatcher.Invoke to fire off the second Storyboard.

There should be no implications if you call the original Storyboard.Begin using Application.Current.Dispatcher.Invoke. This won't launch the storyboard animation on a new thread. It will asynchronously invoke the animation on the main UI thread. Whether you call Begin on the UI thread yourself or whether you use Application.Current.Dispatcher.Invoke to do it, the final result should be the same. Your completed event handler will fire when the storyboard finishes, and you can perform your calculations and fire off the next storyboard.

See the following question for some discussion of storyboard having being used in the past as a timer because of the fact that it runs on the UI thread:

What is the point of using Storyboard as timer?

Also, this is probably overkill for the specific case you are describing, but if you need to orchestrate a bunch of sequential, asynchronous operations, you could use Reactive Extensions:

http://msdn.microsoft.com/en-us/data/gg577609.aspx

The following article includes a sequential storyboard example (though the article is old enough that the syntax has probably changed):

http://www.wintellect.com/cs/blogs/jlikness/archive/2010/08/22/coroutines-for-asynchronous-sequential-workflows-using-reactive-extensions-rx.aspx

like image 33
dandubious Avatar answered Nov 15 '22 22:11

dandubious