I’ve been doing a lot of animation sequencing (i.e. creating really big storyboards) in Silverlight lately and it’s not uncommon for that sequence to need to include some kind of non-storyboard-able event (like kicking of some media or calling some function). I might, for example, want to start an audio file a 4.5 seconds into the animation. I’ve approached this a couple of different ways now, but finally came across one that I’m happy with.
The trick to this is to know that Storyboards can actually contain other storyboards. So something like this:
<Storyboard> <Storyboard ... /> <Storyboard ... /> <DoubleAnimation ... /> <DoubleAnimation ... /> </Storyboard>
is completely valid. It’s also just fine to put a duration on a Storyboard even if doesn’t do anything (e.g. contain any animations). Remember? That was the trick we all used to get frame based animation rolling before we had CompositionTarget.Rendering.
So the trick to getting your code to run in sequence with the animation is to insert an empty Storyboard, set its duration and handle the storyboard’s completed event. It looks like this:
<Storyboard> <Storyboard Duration="00:00:04.5" Completed="PlayAudioFile1" /> <Storyboard Duration="00:00:09.0" Completed="PlayAudioFile2" /> <DoubleAnimation ... /> <DoubleAnimation ... /> </Storyboard>
That would, of course, call the PlayAudioFile1 event handler at 4.5 seconds and PlayAudioFile2 at 9 seconds. It works great and it’s super simple to manage.
I like this approach for a few reasons:
First, because it keeps the whole sequence within a single storyboard—super clean. Other approaches I’ve tried have not been so nice.
Second, the storyboard stays reasonably “Blendable.” Blend essentially just ignores the child Storyboards. Ideally you would be able to set the duration on them from within Blend, but for now ignoring is nice. It leaves the rest of the Storyboard Blend-editable. Incidentally, I put the storyboards at the top because Blend tends to insert at the bottom. If you put your child Storyboards at the bottom of the list, they have a tendency to get lost in the middle somewhere once Blend has its way with your XAML.
Finally, it gives a lot of flexibility in how you write your code. You’re essentially just handling the completed event so you can write whatever fancy code you care to.
If you’re interested, some failed attempts have included:
- “Chaining” storyboards, i.e. breaking up the storyboard into parts and then starting part2 (as well as calling my custom code) in the completed handler for part1, etc. This was got messy quickly.
- Creating a “parallel” DispatcherTimer that gets started along with the Storyboard. This one was even messier.
- Sequencing my events using tweening libraries or a custom sequencer helper that I created (instead of using Storyboards). This one worked pretty well but had zero tooling support.
I may be missing a more obvious way to approach this. Seems like a relatively common task, but I couldn’t find good information about how to do it. If you’ve found a better (or different) approach, I’d love to hear about it.