[c#] WPF loading spinner

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.

This question is related to c# wpf mvvm spinner loading

The answer is


With Images

Visual summary of options for spinning icons. Recorded using Screen To Gif.


Font-Awesome-WPF

Documentation on GitHub.

Install via NuGet:

PM> Install-Package FontAwesome.WPF

Looks like this:

XAML:

<fa:ImageAwesome Icon="Spinner" Spin="True" SpinDuration="4" />

Icons pictured are Spinner, CircleOutlineNotch, Refresh and Cog. There are many others.


Method from @HAdes

XAML copy/paste.


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);
    }

In WPF, you can now simply do:

Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait; // set the cursor to loading spinner

Mouse.OverrideCursor = System.Windows.Input.Cursors.Arrow; // set the cursor back to arrow

To get this:

enter image description here

Stick this in a user control:

<UserControl.Resources>
    <Color x:Key="FilledColor" A="255" B="155" R="155" G="155"/>
    <Color x:Key="UnfilledColor" A="0" B="155" R="155" G="155"/>

    <Style x:Key="BusyAnimationStyle" TargetType="Control">
        <Setter Property="Background" Value="#7F000000"/>

        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Control">
                    <ControlTemplate.Resources>
                        <Storyboard x:Key="Animation0" BeginTime="00:00:00.0" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse0" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>

                        <Storyboard x:Key="Animation1" BeginTime="00:00:00.2" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse1" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>

                        <Storyboard x:Key="Animation2" BeginTime="00:00:00.4" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse2" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>

                        <Storyboard x:Key="Animation3" BeginTime="00:00:00.6" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse3" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>

                        <Storyboard x:Key="Animation4" BeginTime="00:00:00.8" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse4" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>

                        <Storyboard x:Key="Animation5" BeginTime="00:00:01.0" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse5" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>

                        <Storyboard x:Key="Animation6" BeginTime="00:00:01.2" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse6" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>

                        <Storyboard x:Key="Animation7" BeginTime="00:00:01.4" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse7" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                    </ControlTemplate.Resources>

                    <ControlTemplate.Triggers>
                        <Trigger Property="IsVisible" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard Storyboard="{StaticResource Animation0}" x:Name="Storyboard0" />
                                <BeginStoryboard Storyboard="{StaticResource Animation1}" x:Name="Storyboard1"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation2}" x:Name="Storyboard2"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation3}" x:Name="Storyboard3"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation4}" x:Name="Storyboard4"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation5}" x:Name="Storyboard5"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation6}" x:Name="Storyboard6"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation7}" x:Name="Storyboard7"/>
                            </Trigger.EnterActions>

                            <Trigger.ExitActions>
                                <StopStoryboard BeginStoryboardName="Storyboard0"/>
                                <StopStoryboard BeginStoryboardName="Storyboard1"/>
                                <StopStoryboard BeginStoryboardName="Storyboard2"/>
                                <StopStoryboard BeginStoryboardName="Storyboard3"/>
                                <StopStoryboard BeginStoryboardName="Storyboard4"/>
                                <StopStoryboard BeginStoryboardName="Storyboard5"/>
                                <StopStoryboard BeginStoryboardName="Storyboard6"/>
                                <StopStoryboard BeginStoryboardName="Storyboard7"/>
                            </Trigger.ExitActions>
                        </Trigger>
                    </ControlTemplate.Triggers>

                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                        <Grid>
                        <Canvas Height="60" Width="60">
                            <Canvas.Resources>
                                <Style TargetType="Ellipse">
                                    <Setter Property="Width" Value="15"/>
                                    <Setter Property="Height" Value="15" />
                                    <Setter Property="Fill" Value="#009B9B9B" />
                                </Style>
                            </Canvas.Resources>

                            <Ellipse x:Name="ellipse0" Canvas.Left="1.75" Canvas.Top="21"/>
                            <Ellipse x:Name="ellipse1" Canvas.Top="7" Canvas.Left="6.5"/>
                            <Ellipse x:Name="ellipse2" Canvas.Left="20.5" Canvas.Top="0.75"/>
                            <Ellipse x:Name="ellipse3" Canvas.Left="34.75" Canvas.Top="6.75"/>
                            <Ellipse x:Name="ellipse4" Canvas.Left="40.5" Canvas.Top="20.75" />
                            <Ellipse x:Name="ellipse5" Canvas.Left="34.75" Canvas.Top="34.5"/>
                            <Ellipse x:Name="ellipse6" Canvas.Left="20.75" Canvas.Top="39.75"/>
                            <Ellipse x:Name="ellipse7" Canvas.Top="34.25" Canvas.Left="7" />
                            <Ellipse Width="39.5" Height="39.5" Canvas.Left="8.75" Canvas.Top="8" Visibility="Hidden"/>
                        </Canvas>
                            <Label Content="{Binding Path=Text}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</UserControl.Resources>
<Control Style="{StaticResource BusyAnimationStyle}"/>

To get a cool disappearing effect on each ellipse add the following after each ColorAnimationUsingKeyFrames element. Be sure to point it to the correct ellipse..

<ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse0" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
    <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
    <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ellipse0" Storyboard.TargetProperty="Width" >
    <DoubleAnimationUsingKeyFrames.KeyFrames>
        <SplineDoubleKeyFrame  KeyTime="00:00:00.0" Value="15" />
        <SplineDoubleKeyFrame  KeyTime="00:00:01.0" Value="12" />
        <SplineDoubleKeyFrame  KeyTime="00:00:01.6" Value="0" />
    </DoubleAnimationUsingKeyFrames.KeyFrames>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ellipse0" Storyboard.TargetProperty="Height" >
    <DoubleAnimationUsingKeyFrames.KeyFrames>
        <SplineDoubleKeyFrame  KeyTime="00:00:00.0" Value="15" />
        <SplineDoubleKeyFrame  KeyTime="00:00:01.0" Value="12" />
        <SplineDoubleKeyFrame  KeyTime="00:00:01.6" Value="0" />
    </DoubleAnimationUsingKeyFrames.KeyFrames>
</DoubleAnimationUsingKeyFrames>

This repo on github seems to do the job quite well:

https://github.com/blackspikeltd/Xaml-Spinners-WPF

The spinners are all light weight and can easily be placed wherever needed. There is a sample project included in the repo that shows how to use them.

No nasty code-behinds with a bunch of logic either. If MVVM support is needed, one can just take these and throw them in a Grid with a Visibility binding.


use an enum type to indicate your ViewModel's State

public enum ViewModeType
{
    Default, 
    Busy
    //etc.
}

then in your ViewModels Base class use a property

public ViewModeType ViewMode
{
    get { return this.viewMode; }
    set
    {
        if (this.viewMode != value)
        {
            this.viewMode = value;
                            //You should notify property changed here
        }
    }
}

and in view trigger the ViewMode and if it is busy show busyindicator:

<Trigger Property="ViewMode" Value="Busy">
    <!-- Show BusyIndicator -->
</Trigger>

Here's an example of an all-xaml solution. It binds to an "IsWorking" boolean in the viewmodel to show the control and start the animation.

<UserControl x:Class="MainApp.Views.SpinnerView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">

    <UserControl.Resources>
        <BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
    </UserControl.Resources>

    <StackPanel Orientation="Horizontal" Margin="5"
                     Visibility="{Binding IsWorking, Converter={StaticResource BoolToVisConverter}}">
        <Label>Wait...</Label>
        <Ellipse x:Name="spinnerEllipse" 
                     Width="20" Height="20">
            <Ellipse.Fill>
                <LinearGradientBrush StartPoint="1,1" EndPoint="0,0" >
                    <GradientStop Color="White" Offset="0"/>
                    <GradientStop Color="CornflowerBlue" Offset="1"/>
                </LinearGradientBrush>
            </Ellipse.Fill>
            <Ellipse.RenderTransform>
                <RotateTransform x:Name="SpinnerRotate" CenterX="10" CenterY="10"/>
            </Ellipse.RenderTransform>
            <Ellipse.Style>
                <Style TargetType="Ellipse">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding IsWorking}" Value="True">
                            <DataTrigger.EnterActions>
                                <BeginStoryboard x:Name="SpinStoryboard">
                                    <Storyboard TargetProperty="RenderTransform.Angle" >
                                        <DoubleAnimation
                                            From="0" To="360" Duration="0:0:01"
                                            RepeatBehavior="Forever" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.EnterActions>
                            <DataTrigger.ExitActions>
                                <StopStoryboard BeginStoryboardName="SpinStoryboard"></StopStoryboard>
                            </DataTrigger.ExitActions>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Ellipse.Style>
        </Ellipse>
    </StackPanel>
</UserControl>  

This is an update to the code given by @HAdes to parameterize width, height, and ellipse size.

This implementation automatically calculates required angles, widths, and heights on the fly.

The user control is bound to itself (code-behind) which takes care of all calculations.

XAML

<UserControl x:Class="WpfApplication2.Spinner"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApplication2"
             mc:Ignorable="d" 
             DataContext="{Binding RelativeSource={RelativeSource Self}}"
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <Color x:Key="FilledColor" A="255" B="155" R="155" G="155"/>
        <Color x:Key="UnfilledColor" A="0" B="155" R="155" G="155"/>

        <Style x:Key="BusyAnimationStyle" TargetType="Control">
            <Setter Property="Background" Value="Transparent"/>

            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Control">
                        <ControlTemplate.Resources>
                            <Storyboard x:Key="Animation0" BeginTime="00:00:00.0" RepeatBehavior="Forever">
                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipseN" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                    <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                    <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                                </ColorAnimationUsingKeyFrames>
                            </Storyboard>

                            <Storyboard x:Key="Animation1" BeginTime="00:00:00.2" RepeatBehavior="Forever">
                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipseNE" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                    <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                    <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                                </ColorAnimationUsingKeyFrames>
                            </Storyboard>

                            <Storyboard x:Key="Animation2" BeginTime="00:00:00.4" RepeatBehavior="Forever">
                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipseE" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                    <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                    <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                                </ColorAnimationUsingKeyFrames>
                            </Storyboard>

                            <Storyboard x:Key="Animation3" BeginTime="00:00:00.6" RepeatBehavior="Forever">
                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipseSE" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                    <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                    <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                                </ColorAnimationUsingKeyFrames>
                            </Storyboard>

                            <Storyboard x:Key="Animation4" BeginTime="00:00:00.8" RepeatBehavior="Forever">
                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipseS" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                    <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                    <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                                </ColorAnimationUsingKeyFrames>
                            </Storyboard>

                            <Storyboard x:Key="Animation5" BeginTime="00:00:01.0" RepeatBehavior="Forever">
                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipseSW" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                    <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                    <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                                </ColorAnimationUsingKeyFrames>
                            </Storyboard>

                            <Storyboard x:Key="Animation6" BeginTime="00:00:01.2" RepeatBehavior="Forever">
                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipseW" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                    <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                    <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                                </ColorAnimationUsingKeyFrames>
                            </Storyboard>

                            <Storyboard x:Key="Animation7" BeginTime="00:00:01.4" RepeatBehavior="Forever">
                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipseNW" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                    <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                    <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                                </ColorAnimationUsingKeyFrames>
                            </Storyboard>
                        </ControlTemplate.Resources>

                        <ControlTemplate.Triggers>
                            <Trigger Property="IsVisible" Value="True">
                                <Trigger.EnterActions>
                                    <BeginStoryboard Storyboard="{StaticResource Animation0}" x:Name="Storyboard0" />
                                    <BeginStoryboard Storyboard="{StaticResource Animation1}" x:Name="Storyboard1"/>
                                    <BeginStoryboard Storyboard="{StaticResource Animation2}" x:Name="Storyboard2"/>
                                    <BeginStoryboard Storyboard="{StaticResource Animation3}" x:Name="Storyboard3"/>
                                    <BeginStoryboard Storyboard="{StaticResource Animation4}" x:Name="Storyboard4"/>
                                    <BeginStoryboard Storyboard="{StaticResource Animation5}" x:Name="Storyboard5"/>
                                    <BeginStoryboard Storyboard="{StaticResource Animation6}" x:Name="Storyboard6"/>
                                    <BeginStoryboard Storyboard="{StaticResource Animation7}" x:Name="Storyboard7"/>
                                </Trigger.EnterActions>

                                <Trigger.ExitActions>
                                    <StopStoryboard BeginStoryboardName="Storyboard0"/>
                                    <StopStoryboard BeginStoryboardName="Storyboard1"/>
                                    <StopStoryboard BeginStoryboardName="Storyboard2"/>
                                    <StopStoryboard BeginStoryboardName="Storyboard3"/>
                                    <StopStoryboard BeginStoryboardName="Storyboard4"/>
                                    <StopStoryboard BeginStoryboardName="Storyboard5"/>
                                    <StopStoryboard BeginStoryboardName="Storyboard6"/>
                                    <StopStoryboard BeginStoryboardName="Storyboard7"/>
                                </Trigger.ExitActions>
                            </Trigger>
                        </ControlTemplate.Triggers>

                        <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                            <Grid>
                                <Canvas>
                                    <Canvas.Resources>
                                        <Style TargetType="Ellipse">
                                            <Setter Property="Width" Value="{Binding Path=EllipseSize}"/>
                                            <Setter Property="Height" Value="{Binding Path=EllipseSize}" />
                                            <Setter Property="Fill" Value="Transparent" />
                                        </Style>
                                    </Canvas.Resources>

                                    <Ellipse x:Name="ellipseN" Canvas.Left="{Binding Path=EllipseN.Left}" Canvas.Top="{Binding Path=EllipseN.Top}"/>
                                    <Ellipse x:Name="ellipseNE" Canvas.Left="{Binding Path=EllipseNE.Left}" Canvas.Top="{Binding Path=EllipseNE.Top}"/>
                                    <Ellipse x:Name="ellipseE" Canvas.Left="{Binding Path=EllipseE.Left}" Canvas.Top="{Binding Path=EllipseE.Top}"/>
                                    <Ellipse x:Name="ellipseSE" Canvas.Left="{Binding Path=EllipseSE.Left}" Canvas.Top="{Binding Path=EllipseSE.Top}"/>
                                    <Ellipse x:Name="ellipseS" Canvas.Left="{Binding Path=EllipseS.Left}" Canvas.Top="{Binding Path=EllipseS.Top}"/>
                                    <Ellipse x:Name="ellipseSW" Canvas.Left="{Binding Path=EllipseSW.Left}" Canvas.Top="{Binding Path=EllipseSW.Top}"/>
                                    <Ellipse x:Name="ellipseW" Canvas.Left="{Binding Path=EllipseW.Left}" Canvas.Top="{Binding Path=EllipseW.Top}"/>
                                    <Ellipse x:Name="ellipseNW" Canvas.Left="{Binding Path=EllipseNW.Left}" Canvas.Top="{Binding Path=EllipseNW.Top}"/>

                                </Canvas>
                                <Label Content="{Binding Path=Text}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </UserControl.Resources>
    <Border>
        <Control Style="{StaticResource BusyAnimationStyle}"/>
    </Border>
</UserControl>

Code Behind (C#)

using System;
using System.Windows;
using System.Windows.Controls;

namespace WpfApplication2
{
    /// <summary>
    /// Interaction logic for Spinner.xaml
    /// </summary>
    public partial class Spinner : UserControl
    {
        public int EllipseSize { get; set; } = 8;
        public int SpinnerHeight { get; set; } = 0;
        public int SpinnerWidth { get; set; } = 0;


        // start positions
        public EllipseStartPosition EllipseN { get; private set; }
        public EllipseStartPosition EllipseNE { get; private set; }
        public EllipseStartPosition EllipseE { get; private set; }
        public EllipseStartPosition EllipseSE { get; private set; }
        public EllipseStartPosition EllipseS { get; private set; }
        public EllipseStartPosition EllipseSW { get; private set; }
        public EllipseStartPosition EllipseW { get; private set; }
        public EllipseStartPosition EllipseNW { get; private set; }

        public Spinner()
        {
            InitializeComponent();
        }

        private void initialSetup()
        {
            float horizontalCenter = (float)(SpinnerWidth / 2);
            float verticalCenter = (float)(SpinnerHeight / 2);
            float distance = (float)Math.Min(SpinnerHeight, SpinnerWidth) /2;

            double angleInRadians = 44.8;
            float cosine = (float)Math.Cos(angleInRadians);
            float sine = (float)Math.Sin(angleInRadians);

            EllipseN = newPos(left: horizontalCenter, top: verticalCenter - distance);
            EllipseNE = newPos(left: horizontalCenter + (distance * cosine), top: verticalCenter - (distance * sine));
            EllipseE = newPos(left: horizontalCenter + distance, top: verticalCenter);
            EllipseSE = newPos(left: horizontalCenter + (distance * cosine), top: verticalCenter + (distance * sine));
            EllipseS = newPos(left: horizontalCenter, top: verticalCenter + distance);
            EllipseSW = newPos(left: horizontalCenter - (distance * cosine), top: verticalCenter + (distance * sine));
            EllipseW = newPos(left: horizontalCenter - distance, top: verticalCenter);
            EllipseNW = newPos(left: horizontalCenter - (distance * cosine), top: verticalCenter - (distance * sine));
        }

        private EllipseStartPosition newPos(float left, float top)
        {
            return new EllipseStartPosition() { Left = left, Top = top };
        }

        
        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            if(e.Property.Name == "Height")
            {
                SpinnerHeight = Convert.ToInt32(e.NewValue);
            }

            if (e.Property.Name == "Width")
            {
                SpinnerWidth = Convert.ToInt32(e.NewValue);
            }

            if(SpinnerHeight > 0 && SpinnerWidth > 0)
            {
                initialSetup();
            }

            base.OnPropertyChanged(e);
        }
    }

    public struct EllipseStartPosition
    {
        public float Left { get; set; }
        public float Top { get; set; }
    }
}

Sample Use

<Window x:Class="WpfApplication2.MainWindow"
        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:local="clr-namespace:WpfApplication2"
        xmlns:animated="WpfApplication2.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <StackPanel Background="DarkGoldenrod" Width="200" Height="200" VerticalAlignment="Top" HorizontalAlignment="Left" >
        <Button Height="35">
            <Button.Content >
                <DockPanel LastChildFill="True" Height="NaN" Width="NaN" HorizontalAlignment="Left">
                    <local:Spinner EllipseSize="4" DockPanel.Dock="Left" HorizontalAlignment="Left" Margin="0,0,10,5" Height="16" Width="16"/>
                    <TextBlock Text="Cancel" VerticalAlignment="Center"/>
                </DockPanel>
            </Button.Content>
        </Button>

    </StackPanel>

</Window>

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.


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:

  1. Circles :
  2. Rings :

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!



A very simple "plug and play" spinner could be one of the spinning icons from the Font Awesome Wpf Package (Spinning icons).

The usage is quite simple, just install the nuget package:

PM> Install-Package FontAwesome.WPF

Then add the reference to the namespace

xmlns:fa="http://schemas.fontawesome.io/icons/"

and use the ImageAwesome control. Set the Spin="True" property and select one of the "Spinner", "Refresh", "Cog" and "CircleOutlinedNotched" Icon. It's scalable and can be resized by setting width and height.

<Window x:Class="Example.FontAwesome.WPF.Single"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:fa="http://schemas.fontawesome.io/icons/"
    Title="Single" Height="300" Width="300">
    <Grid  Margin="20">
        <fa:ImageAwesome Icon="Refresh" Spin="True" Height="48" Width="48" />
    </Grid>
</Window>

Examples related to c#

How can I convert this one line of ActionScript to C#? Microsoft Advertising SDK doesn't deliverer ads How to use a global array in C#? How to correctly write async method? C# - insert values from file into two arrays Uploading into folder in FTP? Are these methods thread safe? dotnet ef not found in .NET Core 3 HTTP Error 500.30 - ANCM In-Process Start Failure Best way to "push" into C# array

Examples related to wpf

Error: the entity type requires a primary key Reportviewer tool missing in visual studio 2017 RC Pass command parameter to method in ViewModel in WPF? Calling async method on button click Setting DataContext in XAML in WPF How to resolve this System.IO.FileNotFoundException System.Windows.Markup.XamlParseException' occurred in PresentationFramework.dll? Binding an Image in WPF MVVM How to bind DataTable to Datagrid Setting cursor at the end of any text of a textbox

Examples related to mvvm

Vue.js—Difference between v-model and v-bind Pass command parameter to method in ViewModel in WPF? [Vue warn]: Cannot find element Binding an Image in WPF MVVM Binding ComboBox SelectedItem using MVVM What is difference between MVC, MVP & MVVM design pattern in terms of coding c# How to make all controls resize accordingly proportionally when window is maximized? Add directives from directive in AngularJS Wpf DataGrid Add new row Close Window from ViewModel

Examples related to spinner

How to add a Hint in spinner in XML Hide Spinner in Input Number - Firefox 29 How can I use onItemSelected in Android? Show default value in Spinner in android Set selected item of spinner programmatically How to set Spinner Default by its Value instead of Position? How to change spinner text size and text color? WPF loading spinner How do I create an Android Spinner as a popup? How to add items to a spinner in Android?

Examples related to loading

JavaScript Loading Screen while page loads How to add a spinner icon to button when it's in the Loading state? Display a loading bar before the entire page is loaded WPF loading spinner Using the "animated circle" in an ImageView while loading stuff Display loading image while post with ajax Running sites on "localhost" is extremely slow open resource with relative path in Java Dynamic loading of images in WPF How to create a JavaScript callback for knowing when an image is loaded?