BizzySpinner 2 – A WPF Spinning Busy State Indicator (with source)
I’ve made a key improvement to my BizzySpinner control form my last post. It now starts and stop smoothly instead of abruptly. This is quite visually attractive. But it was somewhat non-trivial to do.
In my first example, I had a single simple animation that was either running or stopped. The animation behavior is now like this:
I tried to do this all in XAML but it proved quite complex to handle in in that language. This was complicated as there are two key dependency properties to deal with that leads to several states: The Spin property controls the actually spinning state. And the IsEnabled property inherited from UIElement. Both must work correctly together.
This gave me two significant problems:
- The XAML was really complicated and difficult to read. I don’t know about you, but to me, XAML is very verbose and difficult to read. Syntax highlighting helps, but only to a degree. There is also no such thing as a XAML debugger so a lot of trial and error is involved with large amounts of XAML. Kaxaml is really helpful, but this was hard even with that tool.
- I couldn’t get a fully correct state machine in XAML: No matter what I tried I could toggle the IsEnabled and Spin dependency properties and cause the spinner to “jump” unattractively.
So, I simply used C# code to do this. This approach was much simpler. It was easy to implement the two cooperating state machines needed to implement all cases. The spinning state machine is shown to the right. It has four states and six transitions – not at all difficult to implement.
This state machine is implemented in the ControlSpinning() function on line 432 in BizzySpinner.xaml.cs.
The transitions caused by the SpinUp and SpinDown values are straight forward and implemented using a couple of switch statements
The transitions labeled Acceleration Complete and Deceleration Complete are more interesting: they are implemented using the Completed event on the DoubleAnimation using to spin the control.
For example when the spinner goes form the Not Spinning state to the Accelerating state, the code simply attaches a function to the animation’s Completed event like this:
Code Snippet
spinAnimation.From = SpinAngle;
spinAnimation.To = (SpinAngle + (OneRotation / 8));
spinAnimation.Duration = new Duration(TimeSpan.FromSeconds(SpinRate/4));
spinAnimation.DecelerationRatio = 0.0;
spinAnimation.AccelerationRatio = 1.0;
spinAnimation.Completed += SpinContinuously;
theSpinState = SpinState.Accelerating;
this.BeginAnimation(SpinAngleProperty, spinAnimation);
This is the code that causes the spinner to accelerate to its normal spinning rate. The key line is #7. When the spinner has finished accelerating the SpinContinuously member is called. It looks like this:
Code Snippet
void SpinContinuously(object sender, EventArgs e)
{
spinAnimation.Completed -= SpinContinuously;
spinAnimation.From = SpinAngle;
spinAnimation.To = SpinAngle + OneRotation;
spinAnimation.Duration = new Duration(TimeSpan.FromSeconds(SpinRate));
spinAnimation.DecelerationRatio = 0.0;
spinAnimation.AccelerationRatio = 0.0;
spinAnimation.RepeatBehavior = RepeatBehavior.Forever;
theSpinState = SpinState.Running;
this.BeginAnimation(SpinAngleProperty, spinAnimation);
}
Note line 3 which disconnects the SpinContinuously from the animations Completed event. This is super important as we don’t want this called again because the next state transition when the animation completes is a different one.
The other important state machine is the one that handles the IsEnabled and Spin dependency properties. This is what sends the SpinUp and SpinDown commands to the spinning state machine.
This is simpler than it looks: there are only four important transitions – the ones in green where the SpinUp and SpinDown commands are sent to the spinning state machine.
This is all handled easily in the IsEnabledChanged event handler like this:
Code Snippet
private void IsEnabledChangedHandler(Object sender, DependencyPropertyChangedEventArgs e)
{
if ( !(bool)e.NewValue)
{
//
// Going enabled
//
Background = BackgroundBrushSave;
LeaderBrush = LeaderBrushSave;
TailBrush = TailBrushSave;
// The control is enabled, turn on spinning if the Spin property is ture
ControlSpinning(Spin ? SpinCommand.SpinUp : SpinCommand.SpinDown);
} else {
//
// Going disabled
//
if (theSpinState == SpinState.NotSpinning)
{
SetDisabledBrushes();
}
else
{
ControlSpinning(SpinCommand.SpinDown);
}
}
}
The resulting animation is very pleasing – the control spins up and down smoothly. Its rotational rate can also be controlled dynamically.
You will need NUnit 2.5.3 (download) as I’ve added assertions. The code builds for C# 3.0 and .NET 3.5 using the client profile. As with the last post, the project is for the Visual Studio 2010 BETA which you can download for free..
Installing and building the project is easy: Just download and run the installer. It will put the project on your desktop. Note, it doesn’t really ‘install’ anything – there are no shortcuts and it doesn’t write to the registry. You can move the sample directory wherever you would like, or just delete it when you are done. No un-installing is needed. Also note that the contents are licensed with the Microsoft Public License.