Wednesday, March 3, 2010

Animations and View Models: IAnimationDelegate

We often trip over ourselves trying to minimize code behind and abstract behaviors in the UI from the models, etc. This is important for clean separation, but sometimes behaviors may add too much abstraction. The real fact is many applications require some sort of transition or animation based on events, and while we can try to put as many of those as possible into the VisualStateManager, there may be instances such as dynamically created animations or special triggers that end up involving the view model somehow.

What I'm proposing here is a very simple solution to allowing your view model to fire and respond to animations without having to know about those animations. The view model knows it has to fire some event, and may want notification when the event is done, but how that event is implemented is up to you. This way, the live version can contain Storyboard objects while the unit tests can contain mocks.

First, let's create a simple interface that our view model can talk to. It simply begins the animation and provides a callback when the animation is complete. (If you like, you can make it even more generic and call it a transition or an event).

public interface IAnimationDelegate
{
    void BeginAnimation(Action animationComplete);
}

That's pretty simple. Now, in my view model, I can fire the event and react. In this totally contrived example, whenever our first value changes, we fire a transition and only when the transition is complete, we move it with some changes to a second value.

public class MyViewModel : BaseNotify 
{
   public IAnimationDelegate Value1Transition { get; set; }

   private string _value1; 

   public string Value1 
   { 
      get { return _value1; } 
      set {
              _value1 = value;
              Value1Transition.BeginAnimation(_Value1Transitioned);
              RaisePropertyChanged("Value1"); 
          }
    }

    private string _value2; 

    public string Value2 
    {
       get { return _value2; }
       set {
             _value2 = value; 
             RaisePropertyChanged("Value2");
           }       
    }

    private void _Value1Transitioned()
    {
       Value2 = "This was the first value: " + Value1; 
    }
}    

The implementation of the animation delegate class might look like this:

public class AnimationDelegate : IAnimationDelegate
{
    private readonly Storyboard _storyboard;

    private Action _animationComplete;

    public AnimationDelegate(Storyboard storyboard)
    {
        _storyboard = storyboard;
        _storyboard.Completed += StoryboardCompleted;
    }

    void StoryboardCompleted(object sender, EventArgs e)
    {
        if (_animationComplete != null)
        {
            _animationComplete();
        }
    }

    public void BeginAnimation(Action animationComplete)
    {
        _animationComplete = animationComplete;
        _storyboard.Begin();
    }
}

We could unregister the event when completed, add other parameters to stop it when completed, etc, but this is a good staring point.

Now I simply need to wire the delegate. If my animation is called AnimateValue1 then with the Managed Extensibility Framework (MEF), I'd do something like this in the code behind for my control:

[Import]
public MyViewModel ViewModel
{
   get {  return LayoutRoot.DataContext as MyViewModel; }
   set {
          value.Value1Transition = new AnimationDelegate(AnimateValue1); 
          LayoutRoot.DataContext = value;          
       }
}

... and there you have it.

Jeremy Likness

No comments:

Post a Comment