Community Pick: Many members of our community have endorsed this article.
Editor's Choice: This article has been selected by our editors as an exceptional contribution.

Silverlight Circular Progress Control

Wayne Taylor (webtubbs)
CERTIFIED EXPERT
Published:
For a while now I'v been searching for a circular progress control, much like the one you get when first starting your Silverlight application. I found a couple that were written in WPF and there were a few written in Silverlight, but all appeared overly complex, so I decided to write my own.

Design

So, to start with I added a Silverlight UserControl to my project and named it 'CircularProgressControl', and added a Canvas control to the Layout Grid. I then added 12 ellipses and arranged them in a circle around the canvas. I then set the Fill property to a RadialGradientBrush with 3 gradient stops and modified a gradient offset of each dot to appear slightly less intense as the previous. The result looked like this...

Dot Layout
To add a little flexability to the control and allow me to easily change the base color to something other than Blue, I added some property binding, which involved 3 things.

In the code behind of the UserControl, I added a property called DotColor....
    ''' <summary>
                          ''' The base color for each of the dots on the progress control
                          ''' </summary>
                          Public Property DotColor As Color

Open in new window

I then added property binding to each of the dots in the control, so this....
            <Ellipse StrokeThickness="0" Width="15" Height="15" Canvas.Left="30" Canvas.Top="0" Name="Ellipse1">
                                      <Ellipse.Fill>
                                          <RadialGradientBrush>
                                              <GradientStop Color="Blue" Offset="0" />
                                              <GradientStop Color="Transparent" Offset="1" />
                                              <GradientStop Color="Blue" Offset="0.9" />
                                          </RadialGradientBrush>
                                      </Ellipse.Fill>
                                  </Ellipse>

Open in new window

...ended up like this....
            <Ellipse StrokeThickness="0" Width="15" Height="15" Canvas.Left="30" Canvas.Top="0" Name="Ellipse1">
                                      <Ellipse.Fill>
                                          <RadialGradientBrush>
                                              <GradientStop Color="{Binding DotColor}" Offset="0" />
                                              <GradientStop Color="Transparent" Offset="1" />
                                              <GradientStop Color="{Binding DotColor}" Offset="0.9" />
                                          </RadialGradientBrush>
                                      </Ellipse.Fill>
                                  </Ellipse>

Open in new window

I then needed to ensure the binding knew where to look, so I added this...
DataContext="{Binding RelativeSource={RelativeSource Self}}"

Open in new window

...in the XAML declaration. The final XAML looks like this....
<UserControl x:Class="TestProject.CircularProgressControl"
                          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                          xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
                          mc:Ignorable="d"
                          Visibility="Collapsed"
                          d:DesignHeight="75" d:DesignWidth="75"
                          DataContext="{Binding RelativeSource={RelativeSource Self}}">
                      
                          <Grid x:Name="LayoutRoot" Background="Transparent">
                              <Canvas Name="Body">
                                  <Ellipse StrokeThickness="0" Width="15" Height="15" Canvas.Left="30" Canvas.Top="0" Name="Ellipse1">
                                      <Ellipse.Fill>
                                          <RadialGradientBrush>
                                              <GradientStop Color="{Binding DotColor}" Offset="0" />
                                              <GradientStop Color="Transparent" Offset="1" />
                                              <GradientStop Color="{Binding DotColor}" Offset="0.9" />
                                          </RadialGradientBrush>
                                      </Ellipse.Fill>
                                  </Ellipse>
                                  <Ellipse Canvas.Left="45" Canvas.Top="4" Height="15" StrokeThickness="0" Width="15" Name="Ellipse2">
                                      <Ellipse.Fill>
                                          <RadialGradientBrush>
                                              <GradientStop Color="{Binding DotColor}" Offset="0" />
                                              <GradientStop Color="Transparent" Offset="1" />
                                              <GradientStop Color="{Binding DotColor}" Offset="0.8" />
                                          </RadialGradientBrush>
                                      </Ellipse.Fill>
                                  </Ellipse>
                                  <Ellipse Canvas.Left="56" Canvas.Top="15" Height="15" StrokeThickness="0" Width="15" Name="Ellipse3">
                                      <Ellipse.Fill>
                                          <RadialGradientBrush>
                                              <GradientStop Color="{Binding DotColor}" Offset="0" />
                                              <GradientStop Color="Transparent" Offset="1" />
                                              <GradientStop Color="{Binding DotColor}" Offset="0.7" />
                                          </RadialGradientBrush>
                                      </Ellipse.Fill>
                                  </Ellipse>
                                  <Ellipse Canvas.Left="60" Canvas.Top="30" Height="15" StrokeThickness="0" Width="15" Name="Ellipse4">
                                      <Ellipse.Fill>
                                          <RadialGradientBrush>
                                              <GradientStop Color="{Binding DotColor}" Offset="0" />
                                              <GradientStop Color="Transparent" Offset="1" />
                                              <GradientStop Color="{Binding DotColor}" Offset="0.6" />
                                          </RadialGradientBrush>
                                      </Ellipse.Fill>
                                  </Ellipse>
                                  <Ellipse Canvas.Left="56" Canvas.Top="44" Height="15" StrokeThickness="0" Width="15" Name="Ellipse5">
                                      <Ellipse.Fill>
                                          <RadialGradientBrush>
                                              <GradientStop Color="{Binding DotColor}" Offset="0" />
                                              <GradientStop Color="Transparent" Offset="1" />
                                              <GradientStop Color="{Binding DotColor}" Offset="0.5" />
                                          </RadialGradientBrush>
                                      </Ellipse.Fill>
                                  </Ellipse>
                                  <Ellipse Canvas.Left="45" Canvas.Top="56" Height="15" StrokeThickness="0" Width="15" Name="Ellipse6">
                                      <Ellipse.Fill>
                                          <RadialGradientBrush>
                                              <GradientStop Color="{Binding DotColor}" Offset="0" />
                                              <GradientStop Color="Transparent" Offset="1" />
                                              <GradientStop Color="{Binding DotColor}" Offset="0.4" />
                                          </RadialGradientBrush>
                                      </Ellipse.Fill>
                                  </Ellipse>
                                  <Ellipse Canvas.Left="30" Canvas.Top="60" Height="15" StrokeThickness="0" Width="15" Name="Ellipse7">
                                      <Ellipse.Fill>
                                          <RadialGradientBrush>
                                              <GradientStop Color="{Binding DotColor}" Offset="0" />
                                              <GradientStop Color="Transparent" Offset="1" />
                                              <GradientStop Color="{Binding DotColor}" Offset="0.3" />
                                          </RadialGradientBrush>
                                      </Ellipse.Fill>
                                  </Ellipse>
                                  <Ellipse Canvas.Left="15" Canvas.Top="56" Height="15" StrokeThickness="0" Width="15" Name="Ellipse8">
                                      <Ellipse.Fill>
                                          <RadialGradientBrush>
                                              <GradientStop Color="{Binding DotColor}" Offset="0" />
                                              <GradientStop Color="Transparent" Offset="1" />
                                              <GradientStop Color="{Binding DotColor}" Offset="0.2" />
                                          </RadialGradientBrush>
                                      </Ellipse.Fill>
                                  </Ellipse>
                                  <Ellipse Canvas.Left="4" Canvas.Top="45" Height="15" StrokeThickness="0" Width="15" Name="Ellipse9">
                                      <Ellipse.Fill>
                                          <RadialGradientBrush>
                                              <GradientStop Color="{Binding DotColor}" Offset="0" />
                                              <GradientStop Color="Transparent" Offset="1" />
                                              <GradientStop Color="{Binding DotColor}" Offset="0.1" />
                                          </RadialGradientBrush>
                                      </Ellipse.Fill>
                                  </Ellipse>
                                  <Ellipse Canvas.Left="0" Canvas.Top="30" Height="15" StrokeThickness="0" Width="15" Name="Ellipse10">
                                      <Ellipse.Fill>
                                          <RadialGradientBrush>
                                              <GradientStop Color="{Binding DotColor}" Offset="0" />
                                              <GradientStop Color="Transparent" Offset="1" />
                                              <GradientStop Color="{Binding DotColor}" Offset="0.05" />
                                          </RadialGradientBrush>
                                      </Ellipse.Fill>
                                  </Ellipse>
                                  <Ellipse Canvas.Left="4" Canvas.Top="15" Height="15" StrokeThickness="0" Width="15" Name="Ellipse11">
                                      <Ellipse.Fill>
                                          <RadialGradientBrush>
                                              <GradientStop Color="{Binding DotColor}" Offset="0" />
                                              <GradientStop Color="Transparent" Offset="1" />
                                              <GradientStop Color="{Binding DotColor}" Offset="0.03" />
                                          </RadialGradientBrush>
                                      </Ellipse.Fill>
                                  </Ellipse>
                                  <Ellipse Canvas.Left="15" Canvas.Top="4" Height="15" StrokeThickness="0" Width="15" Name="Ellipse12">
                                      <Ellipse.Fill>
                                          <RadialGradientBrush>
                                              <GradientStop Color="{Binding DotColor}" Offset="0" />
                                              <GradientStop Color="Transparent" Offset="1" />
                                              <GradientStop Color="{Binding DotColor}" Offset="0.01" />
                                          </RadialGradientBrush>
                                      </Ellipse.Fill>
                                  </Ellipse>
                              </Canvas>
                          </Grid>
                      </UserControl>

Open in new window

Note "x:Class" in the XAML declaration. My project name when designing this control was called "TestProject", so for it to work in your project, change "TestProject" to the Namespace of your own project.

Animation

Now for the animation. For this I decided to use a StoryBoard. I could also have used a StoryBoard within the XAML, but decided for the programatic route so I could stop and start the animation easily.

The code to run the animation is pretty straight-forward. Here it is in it's entirety...
 
Imports System.Windows.Threading
                      
                      Partial Public Class CircularProgressControl
                          Inherits UserControl
                      
                          Private WithEvents _storyBoard As Storyboard
                      
                          Public Sub New()
                      
                              InitializeComponent()
                      
                              'initialize the StoryBoard object
                              _storyBoard = New Storyboard
                              _storyBoard.Duration = TimeSpan.FromMilliseconds(100)
                      
                          End Sub
                      
                          ''' <summary>
                          ''' The base color for each of the dots on the progress control
                          ''' </summary>
                          Public Property DotColor As Color
                      
                          Public Sub StartProgress()
                              Me.Visibility = Windows.Visibility.Visible
                              _storyBoard.Begin()
                          End Sub
                      
                          Public Sub StopProgress()
                              _storyBoard.Stop()
                              Me.Visibility = Windows.Visibility.Collapsed
                          End Sub
                      
                          Private Sub _storyBoard_Completed(ByVal sender As Object, ByVal e As System.EventArgs) Handles _storyBoard.Completed
                              'move the fill properties anti-clockwise
                              Ellipse12.Fill = Ellipse1.Fill
                              Ellipse1.Fill = Ellipse2.Fill
                              Ellipse2.Fill = Ellipse3.Fill
                              Ellipse3.Fill = Ellipse4.Fill
                              Ellipse4.Fill = Ellipse5.Fill
                              Ellipse5.Fill = Ellipse6.Fill
                              Ellipse6.Fill = Ellipse7.Fill
                              Ellipse7.Fill = Ellipse8.Fill
                              Ellipse8.Fill = Ellipse9.Fill
                              Ellipse9.Fill = Ellipse10.Fill
                              Ellipse10.Fill = Ellipse11.Fill
                              Ellipse11.Fill = Ellipse12.Fill
                              _storyBoard.Begin()
                          End Sub
                      End Class

Open in new window

You can see straight away how I made the dots appear to move. I simply moved the fill property of each dot to the next one!

Starting and Stopping the progress updating is fairly straight forward as well. I exposed to public methods called "StartProgres" and "StopProgress", and in each I started or stopped the StoryBoard object. I also modified the Visibility of the UserControl to hide it once the progress has ended. If you look back up to the XAML, I have also set the UserControl's visibility to Collapsed so it is only displayed once the progress updating is underway.

Usage

Using the CircularProgressControl in your project is very easy. Once you've built your project, you will now see the control displayed in the Toolbox. It's simply a matter of adding it to your page, control, or wherever you wish to use it.

The XAML for the control will look something like this....
<my:CircularProgressControl x:Name="CircularProgressControl1" DotColor="Blue" />

Open in new window

Note the "DotColor" property. You can change that to any color you want.

Starting or stopping the progress is as easy as calling either of the "StartProgress" or "StopProgress" methods...
'start the progress
                      CircularProgressControl1.StartProgress()
                      
                      'stop the progress
                      CircularProgressControl1.StopProgress()

Open in new window

4
5,297 Views
Wayne Taylor (webtubbs)
CERTIFIED EXPERT

Comments (3)

CERTIFIED EXPERT
Author of the Year 2011
Top Expert 2006

Commented:
Wayne - nice work.
"Yes" vote above.

Vic
Nice control... The BusyIndicator control in the below url also so good..
http://www.silverlight.net/content/samples/sl4/toolkitcontrolsamples/run/default.html
CERTIFIED EXPERT

Author

Commented:
The BusyIndicator is good, and that was what I settled with for a while. But, it's not circular :)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.