16 May 2010

Running UI operations sequentially in Silverlight

I've been playing around with Silverlight recently and have come across a requirement of needing to wait for the UI to do something before continuing. For example I have a UI with elements such and an image and text bound to properties of a model object. When the model object changes the interface updates to reflect this change but I need to perform an "unload" transition just before the model changes and a "load" transition just after it had changes.

Instead of this:

before I want this:

after

The orange arrows representing the transitions.

I considered having BeforeChange and AfterChange events, hooking my transition storyboards up to them and then firing them in the model setter. The trouble with this is that the storyboards will be playing in a separate thread so as soon as the BeforeChange one starts our code will have moved on and fired the AfterChange one. The result will be that we'll never see the "before" transition which will ruin the whole effect.

Mike Taulty posted about this same issue in 2008 highlighting that, to achieve the correct result, we end up needing to chain our code together using the Completed events of our storyboards. His solution was using some classes to wrap this up and I've taken a similar approach apart from that I have the sequence defined fluently and included the option of using visual states rather than explicitly defined storyboards.

private Album CurrentAlbum
{
   get
   {
      return this.DataContext as Album;
   }
   set 
   {
      if (CurrentAlbum != value)
      {
         new Sequence()
            .GoTo(this, LayoutRoot, "VisualStateGroup", "AlbumUnloaded")
            .Execute(() =>
            {
               this.DataContext = value;
            })
            .GoTo(this, LayoutRoot, "VisualStateGroup", "AlbumLoaded")
            .Run();		
      }
   }
}

It ends up being a lot quicker to write the code and I think it's quite obvious by reading it what will happen. If the visual state group or states aren't defined then only the inner assignment occurs.

The source for the Sequence class is a bit big for this post so the gist is here: Sequence.cs 

Considerations

Deferred execution
The storyboard or visual state change Completed event we're waiting for may never happen - do we try to execute the next steps anyway? I’ve taken the approach of firing off the next step in the destructor of the class however it may make more sense to set some arbitrary timeout so if the transition hasn’t completed after say 10 seconds we fire off the next step anyway.
Reuse
Should we allow a sequence to be created once and then reused many times - we could have an overload of Run() that takes a context object and passes it on to each of the steps. Could run into issues with people using closures like I do in the example. I’ve stuck with single use in the class, throwing an exception if Run() is called a second time.

No comments: