The goal is to display the information that the application is working. So I'm looking for an intelligent implementation sample of a loading spinner using WPF / MVVM.
The goal is to display the information that the application is working. So I'm looking for an intelligent implementation sample of a loading spinner using WPF / MVVM.
You can do it without any additional controls and libraries, using only Image control and transform:
<Image
Source="/images/spinner.png"
Width="100"
Height="100"
RenderTransformOrigin="0.5, 0.5" Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}">
<Image.RenderTransform>
<RotateTransform x:Name="noFreeze" />
</Image.RenderTransform>
<Image.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
To="360" Duration="0:0:1" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
Replace /images/spinner.png with your image. Change To="360" to To="-360" if you want to rotate it counterclockwise. Duration="0:0:1" equals to 1 second per rotation.
The customized spinner posted by @Menol had a small issue where the spinner would be shifted down and to the right by the size of one dot. I have updated the code so that it compensates for this offset by subtracting by half the dot.
Here is the updated code:
private void initialSetup()
{
float horizontalCenter = (float)(SpinnerWidth / 2);
float verticalCenter = (float)(SpinnerHeight / 2);
float distance = (float)Math.Min(SpinnerHeight, SpinnerWidth) / 2;
float dotComp = (float)(EllipseSize / 2);
double angleInRadians = 44.8;
float cosine = (float)Math.Cos(angleInRadians);
float sine = (float)Math.Sin(angleInRadians);
EllipseN = newPos(left: horizontalCenter - dotComp, top: verticalCenter - distance - dotComp);
EllipseNE = newPos(left: horizontalCenter + (distance * cosine) - dotComp, top: verticalCenter - (distance * sine) - dotComp);
EllipseE = newPos(left: horizontalCenter + distance - dotComp, top: verticalCenter - dotComp);
EllipseSE = newPos(left: horizontalCenter + (distance * cosine) - dotComp, top: verticalCenter + (distance * sine) - dotComp);
EllipseS = newPos(left: horizontalCenter - dotComp, top: verticalCenter + distance - dotComp);
EllipseSW = newPos(left: horizontalCenter - (distance * cosine) - dotComp, top: verticalCenter + (distance * sine) - dotComp);
EllipseW = newPos(left: horizontalCenter - distance - dotComp, top: verticalCenter - dotComp);
EllipseNW = newPos(left: horizontalCenter - (distance * cosine) - dotComp, top: verticalCenter - (distance * sine) - dotComp);
}
CircularProgressBarBlue.xaml
<UserControl
x:Class="CircularProgressBarBlue"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="Transparent"
Name="progressBar">
<UserControl.Resources>
<Storyboard x:Key="spinning" >
<DoubleAnimation
Storyboard.TargetName="SpinnerRotate"
Storyboard.TargetProperty="(RotateTransform.Angle)"
From="0"
To="360"
RepeatBehavior="Forever"/>
</Storyboard>
</UserControl.Resources>
<Grid
x:Name="LayoutRoot"
Background="Transparent"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image Source="C:\SpinnerImage\BlueSpinner.png" RenderTransformOrigin="0.5,0.5" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Image.RenderTransform>
<RotateTransform
x:Name="SpinnerRotate"
Angle="0"/>
</Image.RenderTransform>
</Image>
</Grid>
CircularProgressBarBlue.xaml.cs
using System;
using System.Windows;
using System.Windows.Media.Animation;
/// <summary>
/// Interaction logic for CircularProgressBarBlue.xaml
/// </summary>
public partial class CircularProgressBarBlue
{
private Storyboard _sb;
public CircularProgressBarBlue()
{
InitializeComponent();
StartStoryBoard();
IsVisibleChanged += CircularProgressBarBlueIsVisibleChanged;
}
void CircularProgressBarBlueIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (_sb == null) return;
if (e != null && e.NewValue != null && (((bool)e.NewValue)))
{
_sb.Begin();
_sb.Resume();
}
else
{
_sb.Stop();
}
}
void StartStoryBoard()
{
try
{
_sb = (Storyboard)TryFindResource("spinning");
if (_sb != null)
_sb.Begin();
}
catch
{ }
}
}
The approach is to use geometry with animations applied. Add the required geometry to the Path and animate its RotateTransform from 0-360°.
My spinner support two types of spinners:


And the central logic looks like:
if(spinner.SpinnerType == SpinnerType.Ring)
{
double innerRad = spinner.Radius - spinner.ItemRadius;
Point center = new Point(0, 0);
grp.Children.Add(new EllipseGeometry( center, spinner.Radius, spinner.Radius));
grp.Children.Add(new EllipseGeometry(center, innerRad, innerRad));
return;
}
var points = GetPointsOnCircle( spinner.Diameter/ 2);
double r = spinner.ItemRadius;
foreach (var point in points)
{
grp.Children.Add(new EllipseGeometry(point, r, r));
r -= spinner.ContinuousSizeReduction;
}
Usage is as simple as follows:
<local:SpinnerControl Diameter="60" Fill="#FFE8B311"/>
Here is the source code!