Reputation: 407
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
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