I was investigating a Silverlight Toolkit bug this morning and got into a spirited discussion about Storyboard behavior with Jafar. We disagreed on a couple of points, so I [grudgingly :) ] coded up a quick test application to help sort things out. Unfortunately, it turns out that the situation is a little weirder than either of us realized... I knew I'd never be able to remember the specifics, so I'm documenting my findings here for everyone's benefit.

StoryboardBehavior on WPF

At the heart of the matter is the way Storyboards fit into the Silverlight/WPF property system. What's generally expected is that when a Storyboard is actively animating a property, the animation value takes precedence over the local value. It should still be possible to change the local value, but such changes shouldn't be visible until the Storyboard gives up control of the property. That said, there are a few ways in which WPF and Silverlight seem to disagree - as the following table illustrates. To help keep things clear, I've colored things green or red according to whether I believe the behavior to be correct or incorrect (respectively) based on my current understanding. (Note: All tests were performed with the default HoldEnd FillBehavior.)

Scenario WPF Silverlight 2 /
Silverlight 3 Beta
[DoubleAnimation.From specified]
  1. Start animation from value A to value B
  2. Set value C during animation
  3. Stop animation when complete
Property has value C Property has value A
[DoubleAnimation.From specified]
  1. Start animation from value A to value B
  2. Set value C after animation completes
Property has value B Property has value C
[DoubleAnimation.From not specified]
  1. Start animation from value A to value B
  2. Set value C during animation
Animation "jitters" when value C is set No "jitter"

You can experiment with these behaviors using the sample application I built for this purpose. It works quite simply: the width of the orange rectangle starts at 200 pixels and is changed by either animating it wider to 300 pixels or by directly setting it narrower to 50 pixels. The animation is performed by a 2 second Storyboard, so there's plenty of time to change things during the animation. (The relevant code is included below.)

StoryboardBehavior on Silverlight

As far as I can tell, the WPF behavior is the most correct. Granted, the animation "jitter" is visually jarring (and doesn't fit with my understanding of the property system hierarchy), but things quickly work themselves out and the final results on that platform make the most sense to me. Of course, I'll be passing all this information on to the folks who actually own this functionality and will help get bugs get opened for any behavior that turns out to be wrong. And with luck, these inconsistencies will be gone in a future release of Silverlight and/or WPF! :)

 

[Click here to download the StoryboardBehavior test application source code for WPF and Silverlight.]

 

public partial class DemoControl : UserControl
{
    public DemoControl()
    {
        InitializeComponent();
    }

    public bool SetFrom { get; set; }

    private Storyboard Storyboard
    {
        get
        {
            if (null == _storyboard)
            {
                _storyboard = new Storyboard();
                Storyboard.SetTarget(_storyboard, Indicator);
                Storyboard.SetTargetProperty(_storyboard, new PropertyPath("Width"));
                DoubleAnimation doubleAnimation = new DoubleAnimation();
                doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(2));
                if (SetFrom)
                {
                    doubleAnimation.From = 200;
                }
                doubleAnimation.To = 300;
                _storyboard.Children.Add(doubleAnimation);
            }
            return _storyboard;
        }
    }
    private Storyboard _storyboard;

    private void StartGrowingAnimation(object sender, RoutedEventArgs e)
    {
        Storyboard.Begin();
    }

    private void StopGrowingAnimation(object sender, RoutedEventArgs e)
    {
        Storyboard.Stop();
    }

    private void SetSmallerSize(object sender, RoutedEventArgs e)
    {
        Indicator.Width = 50;
    }
}