WPF как сделать анимацию загрузки для долгих операций
Подскажите, как сделать анимацию загрузки? Раньше делал так: добавлял ProgressBar, в VM добавлял свойство Loading и видимость ProgressBar биндил на это свойство. Сейчас хочу что-то другое сделать, что-то типо по центру окна приложения будет появляться кружок , который будет крутиться а под ним надпись "Загрузка того-то..." и в это время будет выполняться асинхронно какая-то длительная операция.
Как это можно реализовать? Надо делать какой-то UserControl или что-то другое, где оно должно храниться также на окне и управлять его видимостью или вообще где-то в другом месте?
Ответы (1 шт):
Вот например бегающие по кругу точки 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}}" />