A while back I posted a blog post about a simple Circular Progress Bar that I did for WPF. The original post is right here : http://sachabarber.net/?p=429
It turns out that was not the best thing to do, as the old approach used a never ending animation, that was even running when the controls Visibility changed. I did notice this pretty quickly, when we profiled our app, and noticed this hot spot exactly where the progress bar was. So what we did to fix that is just remove the control when it should stop showing progress. Anyway that was the old way.
I am pleased to announce that I have a new improved Circular Progress Bar that no longer uses a never ending animation, in fact it is a lot simpler and just uses a DispatcherTimer and some elementary trigonometry, and it actually looks more like the style of progress bar we are all used to seeing on the web. Without further ado here is the code:
The xaml for the CircularProgressBar.xaml
<UserControl x:Class="ThreadingComponent.CircularProgressBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="Auto" Width="Auto" Background="Transparent"
IsVisibleChanged="HandleVisibleChanged">
<Grid x:Name="LayoutRoot" Background="Transparent"
ToolTip="Searching...."
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Canvas RenderTransformOrigin="0.5,0.5"
HorizontalAlignment="Center"
VerticalAlignment="Center" Width="120"
Height="120" Loaded="HandleLoaded"
Unloaded="HandleUnloaded" >
<Ellipse x:Name="C0" Width="20" Height="20"
Canvas.Left="0"
Canvas.Top="0" Stretch="Fill"
Fill="Black" Opacity="1.0"/>
<Ellipse x:Name="C1" Width="20" Height="20"
Canvas.Left="0"
Canvas.Top="0" Stretch="Fill"
Fill="Black" Opacity="0.9"/>
<Ellipse x:Name="C2" Width="20" Height="20"
Canvas.Left="0"
Canvas.Top="0" Stretch="Fill"
Fill="Black" Opacity="0.8"/>
<Ellipse x:Name="C3" Width="20" Height="20"
Canvas.Left="0"
Canvas.Top="0" Stretch="Fill"
Fill="Black" Opacity="0.7"/>
<Ellipse x:Name="C4" Width="20" Height="20"
Canvas.Left="0"
Canvas.Top="0" Stretch="Fill"
Fill="Black" Opacity="0.6"/>
<Ellipse x:Name="C5" Width="20" Height="20"
Canvas.Left="0"
Canvas.Top="0" Stretch="Fill"
Fill="Black" Opacity="0.5"/>
<Ellipse x:Name="C6" Width="20" Height="20"
Canvas.Left="0"
Canvas.Top="0" Stretch="Fill"
Fill="Black" Opacity="0.4"/>
<Ellipse x:Name="C7" Width="20" Height="20"
Canvas.Left="0"
Canvas.Top="0" Stretch="Fill"
Fill="Black" Opacity="0.3"/>
<Ellipse x:Name="C8" Width="20" Height="20"
Canvas.Left="0"
Canvas.Top="0" Stretch="Fill"
Fill="Black" Opacity="0.2"/>
<Canvas.RenderTransform>
<RotateTransform x:Name="SpinnerRotate"
Angle="0" />
</Canvas.RenderTransform>
</Canvas>
</Grid>
</UserControl>
And here is the CircularProgressBar.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Windows.Input;
using System.Windows.Shapes;
namespace ThreadingComponent
{
public partial class CircularProgressBar
{
#region Data
private readonly DispatcherTimer animationTimer;
#endregion
#region Constructor
public CircularProgressBar()
{
InitializeComponent();
animationTimer = new DispatcherTimer(
DispatcherPriority.ContextIdle, Dispatcher);
animationTimer.Interval = new TimeSpan(0, 0, 0, 0, 75);
}
#endregion
#region Private Methods
private void Start()
{
Mouse.OverrideCursor = Cursors.Wait;
animationTimer.Tick += HandleAnimationTick;
animationTimer.Start();
}
private void Stop()
{
animationTimer.Stop();
Mouse.OverrideCursor = Cursors.Arrow;
animationTimer.Tick -= HandleAnimationTick;
}
private void HandleAnimationTick(object sender, EventArgs e)
{
SpinnerRotate.Angle = (SpinnerRotate.Angle + 36) % 360;
}
private void HandleLoaded(object sender, RoutedEventArgs e)
{
const double offset = Math.PI;
const double step = Math.PI * 2 / 10.0;
SetPosition(C0, offset, 0.0, step);
SetPosition(C1, offset, 1.0, step);
SetPosition(C2, offset, 2.0, step);
SetPosition(C3, offset, 3.0, step);
SetPosition(C4, offset, 4.0, step);
SetPosition(C5, offset, 5.0, step);
SetPosition(C6, offset, 6.0, step);
SetPosition(C7, offset, 7.0, step);
SetPosition(C8, offset, 8.0, step);
}
private void SetPosition(Ellipse ellipse, double offset,
double posOffSet, double step)
{
ellipse.SetValue(Canvas.LeftProperty, 50.0
+ Math.Sin(offset + posOffSet * step) * 50.0);
ellipse.SetValue(Canvas.TopProperty, 50
+ Math.Cos(offset + posOffSet * step) * 50.0);
}
private void HandleUnloaded(object sender, RoutedEventArgs e)
{
Stop();
}
private void HandleVisibleChanged(object sender,
DependencyPropertyChangedEventArgs e)
{
bool isVisible = (bool)e.NewValue;
if (isVisible)
Start();
else
Stop();
}
#endregion
}
}
And to use it you can simply make it any size you like by putting it into a ViewBox like so:
<UserControl x:Class="ThreadingComponent.BusyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ThreadingComponent"
Height="Auto" Width="Auto"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Viewbox Width="200" Height="200"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<local:CircularProgressBar />
</Viewbox>
</Grid>
</UserControl>
And here is what it looks like when its running
All the code is here is a cut and pastable format, so no ZIP file this time, just cut and paste this code, if you don’t know how to do that, step away from the XAML.