WPF как сделать анимацию загрузки для долгих операций

Подскажите, как сделать анимацию загрузки? Раньше делал так: добавлял ProgressBar, в VM добавлял свойство Loading и видимость ProgressBar биндил на это свойство. Сейчас хочу что-то другое сделать, что-то типо по центру окна приложения будет появляться кружок , который будет крутиться а под ним надпись "Загрузка того-то..." и в это время будет выполняться асинхронно какая-то длительная операция.

Как это можно реализовать? Надо делать какой-то UserControl или что-то другое, где оно должно храниться также на окне и управлять его видимостью или вообще где-то в другом месте?


Ответы (1 шт):

Автор решения: aepot

Вот например бегающие по кругу точки ProgressRing в стиле Windows 10, не помню, где взял.

ProgressRing.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WPFApp1">
    <Style TargetType="local:ProgressRing">
        <Setter Property="Foreground" Value="{Binding Foreground,RelativeSource={RelativeSource AncestorType=Window}}"/>
        <Setter Property="Height" Value="60" />
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="IsTabStop" Value="False" />
        <Setter Property="MinHeight" Value="20" />
        <Setter Property="MinWidth" Value="20" />
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="Width" Value="60" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="c:ProgressRing">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Border.Resources>
                            <Style x:Key="ProgressRingEllipseStyle" TargetType="Ellipse">
                                <Setter Property="HorizontalAlignment" Value="Left" />
                                <Setter Property="Opacity" Value="0" />
                                <Setter Property="VerticalAlignment" Value="Top" />
                            </Style>
                        </Border.Resources>
                        <Grid x:Name="Ring"
                              MaxWidth="{Binding MaxSideLength, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                              MaxHeight="{Binding MaxSideLength, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                              Margin="{TemplateBinding Padding}"
                              Background="Transparent"
                              FlowDirection="LeftToRight"
                              RenderTransformOrigin=".5,.5"
                              Visibility="Collapsed">
                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E1R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E1" Fill="{TemplateBinding Foreground}" Style="{StaticResource ProgressRingEllipseStyle}"
                                         Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                                         Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                                         Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
                            </Canvas>
                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E2R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E2" Fill="{TemplateBinding Foreground}" Style="{StaticResource ProgressRingEllipseStyle}"
                                         Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                                         Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                                         Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
                            </Canvas>
                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E3R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E3" Fill="{TemplateBinding Foreground}" Style="{StaticResource ProgressRingEllipseStyle}"
                                         Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                                         Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                                         Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
                            </Canvas>
                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E4R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E4" Fill="{TemplateBinding Foreground}" Style="{StaticResource ProgressRingEllipseStyle}"
                                         Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                                         Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                                         Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
                            </Canvas>
                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E5R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E5" Fill="{TemplateBinding Foreground}" Style="{StaticResource ProgressRingEllipseStyle}"
                                         Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                                         Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                                         Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
                            </Canvas>
                            <Canvas x:Name="SixthCircle" RenderTransformOrigin=".5,.5" Visibility="Collapsed">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E6R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E6" Fill="{TemplateBinding Foreground}" Style="{StaticResource ProgressRingEllipseStyle}"
                                         Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                                         Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                                         Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
                            </Canvas>
                        </Grid>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="SizeStates">
                                <VisualState x:Name="Large">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SixthCircle" Storyboard.TargetProperty="Visibility" Duration="0">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Small" />
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="ActiveStates">
                                <VisualState x:Name="Inactive" />
                                <VisualState x:Name="Active">
                                    <Storyboard RepeatBehavior="Forever">
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Ring" Storyboard.TargetProperty="Visibility" Duration="0">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="0" Storyboard.TargetName="E1" Storyboard.TargetProperty="Opacity">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.167" Storyboard.TargetName="E2" Storyboard.TargetProperty="Opacity">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.334" Storyboard.TargetName="E3" Storyboard.TargetProperty="Opacity">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.501" Storyboard.TargetName="E4" Storyboard.TargetProperty="Opacity">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.668" Storyboard.TargetName="E5" Storyboard.TargetProperty="Opacity">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.835" Storyboard.TargetName="E6" Storyboard.TargetProperty="Opacity">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="0" Storyboard.TargetName="E1R" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeySpline="0.13,0.21,0.1,0.7" KeyTime="0" Value="-110" />
                                            <SplineDoubleKeyFrame KeySpline="0.02,0.33,0.38,0.77" KeyTime="0:0:0.433" Value="10" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="93" />
                                            <SplineDoubleKeyFrame KeySpline="0.57,0.17,0.95,0.75" KeyTime="0:0:1.617" Value="205" />
                                            <SplineDoubleKeyFrame KeySpline="0,0.19,0.07,0.72" KeyTime="0:0:2.017" Value="357" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="439" />
                                            <SplineDoubleKeyFrame KeySpline="0,0,0.95,0.37" KeyTime="0:0:3.217" Value="585" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.167" Storyboard.TargetName="E2R" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeySpline="0.13,0.21,0.1,0.7" KeyTime="0" Value="-116" />
                                            <SplineDoubleKeyFrame KeySpline="0.02,0.33,0.38,0.77" KeyTime="0:0:0.433" Value="4" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="87" />
                                            <SplineDoubleKeyFrame KeySpline="0.57,0.17,0.95,0.75" KeyTime="0:0:1.617" Value="199" />
                                            <SplineDoubleKeyFrame KeySpline="0,0.19,0.07,0.72" KeyTime="0:0:2.017" Value="351" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="433" />
                                            <SplineDoubleKeyFrame KeySpline="0,0,0.95,0.37" KeyTime="0:0:3.217" Value="579" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.334" Storyboard.TargetName="E3R" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeySpline="0.13,0.21,0.1,0.7" KeyTime="0" Value="-122" />
                                            <SplineDoubleKeyFrame KeySpline="0.02,0.33,0.38,0.77" KeyTime="0:0:0.433" Value="-2" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="81" />
                                            <SplineDoubleKeyFrame KeySpline="0.57,0.17,0.95,0.75" KeyTime="0:0:1.617" Value="193" />
                                            <SplineDoubleKeyFrame KeySpline="0,0.19,0.07,0.72" KeyTime="0:0:2.017" Value="345" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="427" />
                                            <SplineDoubleKeyFrame KeySpline="0,0,0.95,0.37" KeyTime="0:0:3.217" Value="573" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.501" Storyboard.TargetName="E4R" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeySpline="0.13,0.21,0.1,0.7" KeyTime="0" Value="-128" />
                                            <SplineDoubleKeyFrame KeySpline="0.02,0.33,0.38,0.77" KeyTime="0:0:0.433" Value="-8" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="75" />
                                            <SplineDoubleKeyFrame KeySpline="0.57,0.17,0.95,0.75" KeyTime="0:0:1.617" Value="187" />
                                            <SplineDoubleKeyFrame KeySpline="0,0.19,0.07,0.72" KeyTime="0:0:2.017" Value="339" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="421" />
                                            <SplineDoubleKeyFrame KeySpline="0,0,0.95,0.37" KeyTime="0:0:3.217" Value="567" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.668" Storyboard.TargetName="E5R" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeySpline="0.13,0.21,0.1,0.7" KeyTime="0" Value="-134" />
                                            <SplineDoubleKeyFrame KeySpline="0.02,0.33,0.38,0.77" KeyTime="0:0:0.433" Value="-14" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="69" />
                                            <SplineDoubleKeyFrame KeySpline="0.57,0.17,0.95,0.75" KeyTime="0:0:1.617" Value="181" />
                                            <SplineDoubleKeyFrame KeySpline="0,0.19,0.07,0.72" KeyTime="0:0:2.017" Value="331" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="415" />
                                            <SplineDoubleKeyFrame KeySpline="0,0,0.95,0.37" KeyTime="0:0:3.217" Value="561" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.835" Storyboard.TargetName="E6R" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeySpline="0.13,0.21,0.1,0.7" KeyTime="0" Value="-140" />
                                            <SplineDoubleKeyFrame KeySpline="0.02,0.33,0.38,0.77" KeyTime="0:0:0.433" Value="-20" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="63" />
                                            <SplineDoubleKeyFrame KeySpline="0.57,0.17,0.95,0.75" KeyTime="0:0:1.617" Value="175" />
                                            <SplineDoubleKeyFrame KeySpline="0,0.19,0.07,0.72" KeyTime="0:0:2.017" Value="325" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="409" />
                                            <SplineDoubleKeyFrame KeySpline="0,0,0.95,0.37" KeyTime="0:0:3.217" Value="555" />
                                        </DoubleAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

ProgressRing.xaml.cs

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;

namespace WPFApp1
{
    [TemplateVisualState(Name = "Large", GroupName = "SizeStates")]
    [TemplateVisualState(Name = "Small", GroupName = "SizeStates")]
    [TemplateVisualState(Name = "Inactive", GroupName = "ActiveStates")]
    [TemplateVisualState(Name = "Active", GroupName = "ActiveStates")]
    public class ProgressRing : Control
    {
        public static readonly DependencyProperty BindableWidthProperty = DependencyProperty.Register("BindableWidth", typeof(double), typeof(ProgressRing), new PropertyMetadata(default(double), BindableWidthCallback));
        public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register("IsActive", typeof(bool), typeof(ProgressRing), new PropertyMetadata(true, IsActiveChanged));
        public static readonly DependencyProperty IsLargeProperty = DependencyProperty.Register("IsLarge", typeof(bool), typeof(ProgressRing), new PropertyMetadata(true, IsLargeChangedCallback));
        public static readonly DependencyProperty MaxSideLengthProperty = DependencyProperty.Register("MaxSideLength", typeof(double), typeof(ProgressRing), new PropertyMetadata(default(double)));
        public static readonly DependencyProperty EllipseDiameterProperty = DependencyProperty.Register("EllipseDiameter", typeof(double), typeof(ProgressRing), new PropertyMetadata(default(double)));
        public static readonly DependencyProperty EllipseOffsetProperty = DependencyProperty.Register("EllipseOffset", typeof(Thickness), typeof(ProgressRing), new PropertyMetadata(default(Thickness)));
        public static readonly DependencyProperty EllipseDiameterScaleProperty = DependencyProperty.Register("EllipseDiameterScale", typeof(double), typeof(ProgressRing), new PropertyMetadata(1D));

        private List<Action> _deferredActions = new List<Action>();

        static ProgressRing()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ProgressRing), new FrameworkPropertyMetadata(typeof(ProgressRing)));
            VisibilityProperty.OverrideMetadata(typeof(ProgressRing), new FrameworkPropertyMetadata(new PropertyChangedCallback((ringObject, e) =>
            {
                if (e.NewValue != e.OldValue && ringObject is ProgressRing ring)
                    ring.SetCurrentValue(IsActiveProperty, (Visibility)e.NewValue == Visibility.Visible);
            })));
        }

        public ProgressRing()
        {
            SizeChanged += OnSizeChanged;
        }

        public double MaxSideLength
        {
            get { return (double)GetValue(MaxSideLengthProperty); }
            private set { SetValue(MaxSideLengthProperty, value); }
        }

        public double EllipseDiameter
        {
            get { return (double)GetValue(EllipseDiameterProperty); }
            private set { SetValue(EllipseDiameterProperty, value); }
        }

        public double EllipseDiameterScale
        {
            get { return (double)GetValue(EllipseDiameterScaleProperty); }
            set { SetValue(EllipseDiameterScaleProperty, value); }
        }

        public Thickness EllipseOffset
        {
            get { return (Thickness)GetValue(EllipseOffsetProperty); }
            private set { SetValue(EllipseOffsetProperty, value); }
        }

        public double BindableWidth
        {
            get { return (double)GetValue(BindableWidthProperty); }
            private set { SetValue(BindableWidthProperty, value); }
        }

        public bool IsActive
        {
            get { return (bool)GetValue(IsActiveProperty); }
            set { SetValue(IsActiveProperty, value); }
        }

        public bool IsLarge
        {
            get { return (bool)GetValue(IsLargeProperty); }
            set { SetValue(IsLargeProperty, value); }
        }

        private static void BindableWidthCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            if (dependencyObject is ProgressRing ring)
            {
                Action action = new Action(() =>
                {
                    ring.SetEllipseDiameter((double)dependencyPropertyChangedEventArgs.NewValue);
                    ring.SetEllipseOffset((double)dependencyPropertyChangedEventArgs.NewValue);
                    ring.SetMaxSideLength((double)dependencyPropertyChangedEventArgs.NewValue);
                });

                if (ring._deferredActions != null)
                    ring._deferredActions.Add(action);
                else
                    action();
            }
        }

        private void SetMaxSideLength(double width)
        {
            SetCurrentValue(MaxSideLengthProperty, width <= 20 ? 20 : width);
        }

        private void SetEllipseDiameter(double width)
        {
            SetCurrentValue(EllipseDiameterProperty, (width / 8) * EllipseDiameterScale);
        }

        private void SetEllipseOffset(double width)
        {
            SetCurrentValue(EllipseOffsetProperty, new Thickness(0, width / 2, 0, 0));
        }

        private static void IsLargeChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            if (dependencyObject is ProgressRing ring)
                ring.UpdateLargeState();
        }

        private void UpdateLargeState()
        {
            Action action;

            if (IsLarge)
                action = () => VisualStateManager.GoToState(this, "Large", true);
            else
                action = () => VisualStateManager.GoToState(this, "Small", true);

            if (_deferredActions != null)
                _deferredActions.Add(action);

            else
                action();
        }

        private void OnSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
        {
            SetCurrentValue(BindableWidthProperty, ActualWidth);
        }

        private static void IsActiveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            if (dependencyObject is ProgressRing ring)
                ring.UpdateActiveState();
        }

        private void UpdateActiveState()
        {
            Action action;

            if (IsActive)
                action = () => VisualStateManager.GoToState(this, "Active", true);
            else
                action = () => VisualStateManager.GoToState(this, "Inactive", true);

            if (_deferredActions != null)
                _deferredActions.Add(action);

            else
                action();
        }

        public override void OnApplyTemplate()
        {
            UpdateLargeState();
            UpdateActiveState();
            base.OnApplyTemplate();
            if (_deferredActions != null)
                foreach (Action action in _deferredActions)
                    action();
            _deferredActions = null;
        }
    }
}

Подключить этот файл можно например в файле окна

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="ProgressRing.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>

Использовать так

<local:ProgressRing Visibility="Visible"
                    IsActive="True"
                    Height="22"
                    Width="{Binding Height,RelativeSource={RelativeSource Self}}" />
→ Ссылка