Yet another Circular ProgressBar control for WPF

Here is (yet another) WPF circular busy indicator control based on Sacha Barber's Circular Progress Bar. This type of control is useful to indicate that a part of your user interface is waiting for the result of an asynchronous call. The user control is hosts a Canvas in which 9 circles are displayed, with decreasing opacity. A rotation transformation produces the spinning effect. Here's its default look:

The canvas is embedded in a Viewbox for easy resizing. To keep the circles round, the control is kept square (you might want to read this again ;-). This is done by binding its Width to its Height. It's a two-way binding, so you can set either property to set the control's size:

Width="{Binding RelativeSource={RelativeSource Self}, Path=Height, Mode=TwoWay}"

 

The CPU-consuming rotation is only triggered when the Visibility is set to Visible. The control keeps a low GUI profile: it is by default invisible, its background is transparent, it even has a zero opacity:

Visibility="Hidden"

IsVisibleChanged="HandleVisibleChanged"

Opacity="0"

Background="Transparent"

 

Here's the code that is executed when the Visibility changes:

/// <summary>

/// Visibility property was changed: start or stop spinning.

/// </summary>

/// <param name="sender">Sender of the event.</param>

/// <param name="e">Event arguments.</param>

private void HandleVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)

{

    // Don't give the developer a headache.

    if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))

    {

        return;

    }

 

    bool isVisible = (bool)e.NewValue;

 

    if (isVisible)

    {

        this.StartDelay();

    }

    else

    {

        this.StopSpinning();

    }

}

 

The Fill property of the ellipses is bound to the control's Foreground property, through a Style. This is very intuitive, and requires no extra dependency properties:

<Style

   TargetType="Ellipse">

    <Setter Property="Width" Value="20" />

    <Setter Property="Height" Value="20" />

    <Setter Property="Stretch" Value="Fill" />

    <Setter Property="Fill">

        <Setter.Value>

            <Binding Path="Foreground">

                <Binding.RelativeSource>

                    <RelativeSource

                   Mode="FindAncestor"

                   AncestorType="{x:Type local:CircularProgressBar}" />

                </Binding.RelativeSource>

            </Binding>

        </Setter.Value>

    </Setter>

</Style>

 

Remember that the Foreground property is more than just a color, it can be any type of Brush. Here are some examples:

SolidBrush RadialGradientBrush ImageBrush

To give you control over the spinning speed, I created a dependency property called RotationsPerMinute:

/// <summary>

/// Spinning Speed. Default is 60, that's one rotation per second.

/// </summary>

public static readonly DependencyProperty RotationsPerMinuteProperty =

    DependencyProperty.Register(

        "RotationsPerMinute",

        typeof(double),

        typeof(CircularProgressBar),

        new PropertyMetadata(60.0));

 

There is a similar dependency property that allows to to specify the delay before the control becomes visible: StartupDelay. This is actually why I needed to start with a zero Opacity. When the control becomes visible, we wait for a short while before setting its Opacity to 1. This keeps the user interface nice and easy without too briefly flashing indicators. I (re-)used the animation timer to implement the delay. This way we are sure not to freeze the user interface. To show the user that something was started, we show a wait cursor while ... waiting. When the control becomes really visible, we reset the cursor to its original image:

/// <summary>

/// Startup Delay.

/// </summary>

private void StartDelay()

{

    this.originalCursor = Mouse.OverrideCursor;

    Mouse.OverrideCursor = Cursors.Wait;

 

    // Startup

    this.animationTimer.Interval = new TimeSpan(0, 0, 0, 0, this.StartupDelay);

    this.animationTimer.Tick += this.StartSpinning;

    this.animationTimer.Start();

}

 

/// <summary>

/// Start Spinning.

/// </summary>

/// <param name="sender">Sender of the event.</param>

/// <param name="e">Event Arguments.</param>

private void StartSpinning(object sender, EventArgs e)

{

    this.animationTimer.Stop();

    this.animationTimer.Tick -= this.StartSpinning;

 

    // 60 secs per minute, 1000 millisecs per sec, 10 rotations per full circle:

    this.animationTimer.Interval = new TimeSpan(0, 0, 0, 0, (int)(6000 / this.RotationsPerMinute));

    this.animationTimer.Tick += this.HandleAnimationTick;

    this.animationTimer.Start();

    this.Opacity = 1;

 

    Mouse.OverrideCursor = originalCursor;

}

 

Here's how you use the control (all properties have a default value, so it could be shorter):

<local:CircularProgressBar

   StartupDelay="500"

   Foreground="SteelBlue"

   RotationsPerMinute="120"

   Width="200" />

 

I also built a sample client, so you can experiment with the settings. Here's how it looks like:

 Here's the full source code: U2UConsult.CircularProgressBar.Sample.zip (42,66 kb)

Enjoy !