WPF loading spinner

Viewed 174017

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.

13 Answers

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:

  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!

Related