WPF Busy Spinner
Busy spinner is a must-have in almost every application. It’s easy to implement in the HTML world, simply using an animated GIF. Try this website LOADING.IO, you are able to generate a variety of spinner gifs.
However, in WPF, you are not able to display an animated GIF in an image. Images are static. What a pity! Luckily, some smart guys have solved this pain point for us. I believe nearly all of the WPF busy spinner related topics on Google will lead you to this blog WPF: Circular Progress Bar.
My codes below are based on the above blog too, except that I’ve made two improvements.
-
I use Viewbox as the parent element, so that the spinner could be scaled to different sizes.
-
I provide the methods to show and hide the spinner. When the spinner is shown, the spinning animation is started; while when it’s hidden, the animation is stopped. Animation consumes a lot of CPU. Hence, I feel the need to stop the animation before hidding the spinner. I’m not sure if an animation is rendered when the element is hidden. Probably Not!
<Viewbox x:Class="Spinner.CircularProgressBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Spinner"
Height="20" Width="20" Stretch="Uniform">
<Grid x:Name="LayoutRoot" Background="Transparent"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Grid.RenderTransform>
<ScaleTransform x:Name="SpinnerScale"
ScaleX="1.0" ScaleY="1.0" />
</Grid.RenderTransform>
<Canvas RenderTransformOrigin="0.5,0.5"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="120" Height="120"
x:Name="LayoutCanvas">
<Ellipse Width="21.835" Height="21.862"
Canvas.Left="20.1696"
Canvas.Top="9.76358"
Stretch="Fill"
Opacity="1.0"
Fill="Green"/>
<Ellipse Width="21.835" Height="21.862"
Canvas.Left="2.86816"
Canvas.Top="29.9581" Stretch="Fill"
Opacity="0.9"
Fill="Green"/>
<Ellipse Width="21.835" Height="21.862"
Canvas.Left="5.03758e-006"
Canvas.Top="57.9341" Stretch="Fill"
Opacity="0.8"
Fill="Green"/>
<Ellipse Width="21.835" Height="21.862"
Canvas.Left="12.1203"
Canvas.Top="83.3163" Stretch="Fill"
Opacity="0.7"
Fill="Green"/>
<Ellipse Width="21.835" Height="21.862"
Canvas.Left="36.5459"
Canvas.Top="98.138" Stretch="Fill"
Opacity="0.6"
Fill="Green"/>
<Ellipse Width="21.835" Height="21.862"
Canvas.Left="64.6723"
Canvas.Top="96.8411" Stretch="Fill"
Opacity="0.5"
Fill="Green"/>
<Ellipse Width="21.835" Height="21.862"
Canvas.Left="87.6176"
Canvas.Top="81.2783" Stretch="Fill"
Opacity="0.4"
Fill="Green"/>
<Ellipse Width="21.835" Height="21.862"
Canvas.Left="98.165"
Canvas.Top="54.414" Stretch="Fill"
Opacity="0.3"
Fill="Green"/>
<Ellipse Width="21.835" Height="21.862"
Canvas.Left="92.9838"
Canvas.Top="26.9938" Stretch="Fill"
Opacity="0.2"
Fill="Green"/>
<Ellipse Width="21.835" Height="21.862"
Canvas.Left="47.2783"
Canvas.Top="0.5" Stretch="Fill"
Opacity="0.1"
Fill="Green"/>
<Canvas.RenderTransform>
<RotateTransform x:Name="SpinnerRotate"
Angle="0" />
</Canvas.RenderTransform>
<Canvas.Triggers>
<EventTrigger RoutedEvent="local:CircularProgressBar.StartSpinner">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="SpinnerRotate"
Storyboard.TargetProperty="(RotateTransform.Angle)"
From="0" To="360"
Duration="0:0:01"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="local:CircularProgressBar.StopSpinner">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="SpinnerRotate"
Storyboard.TargetProperty="(RotateTransform.Angle)"
To="360"
Duration="0:0:0" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
</Canvas>
</Grid>
</Viewbox>
namespace Spinner
{
/// <summary>
/// Interaction logic for CircularProgressBar.xaml
/// </summary>
public partial class CircularProgressBar : Viewbox
{
public static readonly RoutedEvent StartEvent =
EventManager.RegisterRoutedEvent("StartSpinner",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(CircularProgressBar));
public static readonly RoutedEvent StopEvent =
EventManager.RegisterRoutedEvent("StopSpinner",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(CircularProgressBar));
public CircularProgressBar()
{
InitializeComponent();
this.DataContext = this;
}
public event RoutedEventHandler StartSpinner
{
add { AddHandler(StartEvent, value); }
remove { RemoveHandler(StartEvent, value); }
}
public event RoutedEventHandler StopSpinner
{
add { AddHandler(StopEvent, value); }
remove { RemoveHandler(StopEvent, value); }
}
public void Show()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(StartEvent);
LayoutCanvas.RaiseEvent(newEventArgs);
this.Visibility = Visibility.Visible;
}
public void Hide()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(StopEvent);
LayoutCanvas.RaiseEvent(newEventArgs);
this.Visibility = Visibility.Hidden;
}
}
}
Enjoy!