Playing a series of storyboards in canon using WPF

When developing user interfaces in WPF, there might be times in an application where you want to play a number of storyboards in canon, that is, in succession, one after the other. One such case would be if you have different views in a single window application, with separate storyboards to open and close each view. When the user raises the command to open a particular view, you want to first play the storyboard that closes the current view, followed by playing the storyboard that opens the requested view. This way you don’t need separate storyboards to cater for the move from one view to another, or extra steps in your storyboards that first close the other views.

The usual way to start storyboards in WPF is with a BeginStoryboard action inside a property or event trigger. This can be declared in XAML so that when a particular trigger is fired a storyboard, or number of storyboards, is started. Unfortunately, there is no way to instruct WPF to play the storyboards in succession, they will simply be started at the same time.

The following is a class you can use in your form’s code to play a series of storyboards in succession.

using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Media.Animation; using System.Diagnostics; namespace seemyrisk { /// <summary>  /// Plays a series of storyboards in a canon sequence, one after the other  /// </summary>  class CanonStoryboards { private List<Storyboard> _storyboards = new List<Storyboard>(); private int _count = -1; /// <summary>  /// The FrameworkElement that contains the storyboards  /// </summary>  private FrameworkElement _containingObject; public FrameworkElement ContainingObject { get { return _containingObject; } set { _containingObject = value; } } /// <summary>  /// Initializes a new instance of the CanonStoryboards class  /// </summary>  public CanonStoryboards() { } /// <summary>  /// Initializes a new instance of the CanonStoryboards class and  /// sets the containing object used when starting the storyboards  /// </summary>  /// <param name="containingObject">The FrameworkElement that contains  /// the storyboards</param>  public CanonStoryboards(FrameworkElement containingObject) : this() { _containingObject = containingObject; } /// <summary>  /// Adds a storyboard to the canon sequence.  /// Storyboards are played in the order they are added.  /// </summary>  /// <param name="storyboard">The storyboard to add</param>  public void AddStoryboard(Storyboard storyboard) { if (storyboard == null) throw new ArgumentNullException("storyboard"); _storyboards.Add(storyboard); } /// <summary>  /// Begins the canon sequence of storyboards  /// </summary>  public void Begin() { if (_containingObject == null) throw new InvalidOperationException( "ContainingObject must be set before Begin can be called"); if (_storyboards.Count == 0) throw new InvalidOperationException( "Storyboards must be added before Begin can be called"); // start the first storyboard  BeginStoryboard(_storyboards[0]); } /// <summary>  /// Handler for storyboards' Completed event  /// </summary>  /// <param name="sender">The sender</param>  /// <param name="e">The event args</param>  void Storyboard_Completed(object sender, EventArgs e) { Debug.WriteLine("CanonStoryboards: storyboard completed"); // get the next storyboard in the series  Storyboard nextStoryboard = GetNextStoryboard(); if (nextStoryboard != null) { BeginStoryboard(nextStoryboard); } else { Debug.WriteLine("CanonStoryboards: all storyboards completed"); } } /// <summary>  /// Begins a storyboard in the canon sequence  /// </summary>  /// <param name="storyboard">The storyboard to begin</param>  void BeginStoryboard(Storyboard storyboard) { // wire up hanlder to completed event  storyboard.Completed += new EventHandler(Storyboard_Completed); _count++; // increment storyboard counter  storyboard.Begin(_containingObject); Debug.WriteLine("CanonStoryboards: storyboard begun, " + storyboard.Name); } /// <summary>  /// Gets the next storyboard in the canon sequence  /// </summary>  /// <returns>The next storyboard in the sequence,  /// null if no other storyboards to play</returns>  Storyboard GetNextStoryboard() { if (_count >= 0 && _storyboards.Count > _count + 1) { return _storyboards[_count + 1]; } return null; } } }

You can use the class in your code like this. In this example the storyboards are being started in the handler for a command execution. The storyboard in the window’s resource dictionary with the key “closeAbout” will play first, followed by the “openHome” storyboard:

void OnLoadHomeCommand(object sender, ExecutedRoutedEventArgs e) { // Start the show home storyboard CanonStoryboards cs = new CanonStoryboards(this); cs.AddStoryboard(((Storyboard)Resources["closeAbout"])); cs.AddStoryboard(((Storyboard)Resources["openHome"])); cs.Begin(); }

You might also find it useful to be able to play a number of storyboards in parallel and then play a final storyboard:

void OnLoadHomeCommand(object sender, ExecutedRoutedEventArgs e) { // Start the show home storyboard CanonStoryboards cs = new CanonStoryboards(this); cs.AddStoryboard(((Storyboard)Resources["closeAbout"])); cs.AddStoryboard(((Storyboard)Resources["openHome"])); ((Storyboard)Resources["closeFaq"]).Begin(this); cs.Begin(); }

Note that the last sample will result in the “openHome” storyboard being played after the “closeAbout” storyboard has completed, with no regard to the completion time of the “closeFaq” storyboard. As long as the closeAbout storybard is as least as long as the closeFaq storyboard, the openHome storyboard will play after the closeFaq storyboard. You could extend the functionality of the CanonStoryboards class to allow the grouping of storyboards into parallel and serial sets so that you could have more control over the sequence that the storyboards play in, but I’ll leave that as an exercise to the reader.



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