Willi
Willi

Reputation: 407

wpf is it possible to block ui thread while animating?

folks, I came across a problem when I was going to show off for my daughter the power of WPF animation on resolving Hanoi problem. The problem related codes are below:

   void MoveSet(Disk[] disks, int maxIndex, char from, char via, char to)
   {
        if (maxIndex > 0) MoveSet(disks, maxIndex - 1, from, to, via);
        MoveOne(disks[maxIndex], from, to);
        if (maxIndex > 0) MoveSet(disks, maxIndex - 1, via, from, to);
   }

The above codes move disks recursively. And I added animation in MoveOne method like this:

void MoveOne(Disk disk, char from, char to)
{
    // set animation parameters
    ... upAnimation...
    ... levelShiftAnimation...
    ... downAnimation...

    Storyboard.SetTarget(upAnimation, disk);
    Storyboard.SetTarget(levelShiftAnimation, disk);
    Storyboard.SetTarget(downAnimation, disk);
    storyboard.Begin(disk);
}

The above codes work good. But all animations run almost at the same time. After a few unordered animations all disks changed position. It looks not cool. So I want to show the animation of each disk one by one. I modified the MoveOne method and made such changes:

void MoveOne(Disk disk, char from, char to)
{
    ...
    Storyboard.SetTarget(upAnimation, disk);
    Storyboard.SetTarget(levelShiftAnimation, disk);
    Storyboard.SetTarget(downAnimation, disk);

    AutoResetEvent signaler=new AutoResetEvent(false);
    EventHandler eh = null;
    eh = (s, e) => 
    {
        storyboard.Completed -= eh;
        signaler.Set();
    };
    storyboard.Completed+=eh;
    storyboard.Begin(disk);
    signaler.WaitOne();
}

The above modifications made the whole program stuck. I think the cause is that both animation and MoveOne method are running in the only one UI thread, blocking MoveOne method blocks also the animation. So I tried to create and start(using UI dispatcher to invoke) animation in another newly created task, it didn't work yet. At last, I had straighten up my real demands. I want to run one animation and block other animations and all of them run on the same sole UI thread. It seems to be contradictory. I don't know whether I have a wrong understanding. And, is there any solution for this occasion?

Upvotes: 0

Views: 437

Answers (1)

M.E.
M.E.

Reputation: 2929

You could wrap the execution of the storboard in a Task and await that task.

This could look as described in this answer. Basically, you subscribe to the Completed event of the Storyboard. Then you can easily await the result. (code slightly adopted from the provided link).

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

In your case, all you need to do is, replace the call of storyboard.Begin(disc); in your MoveOn-method with await storyboard.BeginAsync(disc);.

With this change, you turn an event-based approach (using the Completed event) into an awaitable task which makes the handling much easier.

Upvotes: 1

Related Questions