WPF如何实现在控件上显示Loading等待动画
WPF 如何在控件上显示 Loading 等待动画
框架使用
.NET40;Visual Studio 2022;使用方式需引入命名空间后设置控件的附加属性
wd:Loading.IsShow="true",即可显示默认等待动画效果如下:

如需自定义
Loading一定要 先设置wd:Loading.Child在设置IsShow="true"。显示不同
Loading内容需wd:Loading.Child ={x:Static wd:NormalLoading.Default}进行复赋值显示NormalLoading效果如下:
Github[2]
Github xaml[3]
Gitee[4]
Gitee xaml[5]

实现代码
也可以自定义 Loading 动画如下:
1、自定义控件 CustomLoading 。
public class CustomLoading : Control
{
public static CustomLoading Default = new CustomLoading();
static CustomLoading()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomLoading),
new FrameworkPropertyMetadata(typeof(CustomLoading)));
}
}2、编写 CustomLoading.xaml 代码如下。
1)创建装饰 AdornerContainer 代码如下:
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
namespace WPFDevelopers.Utilities
{
public class AdornerContainer : Adorner
{
private UIElement _child;
public AdornerContainer(UIElement adornedElement) : base(adornedElement)
{
}
public UIElement Child
{
get => _child;
set
{
if (value == null)
{
RemoveVisualChild(_child);
_child = value;
return;
}
AddVisualChild(value);
_child = value;
}
}
protected override int VisualChildrenCount
{
get
{
return _child != null ? 1 : 0;
}
}
protected override Size ArrangeOverride(Size finalSize)
{
_child?.Arrange(new Rect(finalSize));
return finalSize;
}
protected override Visual GetVisualChild(int index)
{
if (index == 0 && _child != null) return _child;
return base.GetVisualChild(index);
}
}
}2)创建蒙板控件 MaskControl 代码如下:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WPFDevelopers.Controls
{
public class MaskControl : ContentControl
{
private readonly Visual visual;
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(MaskControl),
new PropertyMetadata(new CornerRadius(0)));
public MaskControl(Visual _visual)
{
visual = _visual;
}
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
}
}3)创建 Loading 继承 BaseControl 增加附加属性 IsShow 代码如下:
True则动态添加装饰器AdornerContainer并将MaskControl添加到AdornerContainer.Child中。False则移除装饰器。
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Media;
using WPFDevelopers.Helpers;
using WPFDevelopers.Utilities;
namespace WPFDevelopers.Controls
{
public class Loading : BaseControl
{
public static readonly DependencyProperty IsShowProperty =
DependencyProperty.RegisterAttached("IsShow", typeof(bool), typeof(Loading),
new PropertyMetadata(false, OnIsLoadingChanged));
private const short SIZE = 25;
private const double MINSIZE = 40;
private static FrameworkElement oldFrameworkElement;
private static void OnIsLoadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool isMask && d is FrameworkElement parent)
{
if (isMask)
{
if (!parent.IsLoaded)
parent.Loaded += Parent_Loaded;
else
CreateMask(parent);
}
else
{
parent.Loaded -= Parent_Loaded;
CreateMask(parent, true);
}
}
}
private static void Parent_Loaded(object sender, RoutedEventArgs e)
{
if (sender is UIElement element)
CreateMask(element);
}
static void CreateMask(UIElement uIElement, bool isRemove = false)
{
var layer = AdornerLayer.GetAdornerLayer(uIElement);
if (layer == null) return;
if (isRemove && uIElement != null)
{
var adorners = layer.GetAdorners(uIElement);
if (adorners != null)
{
foreach (var item in adorners)
{
if (item is AdornerContainer container)
{
var isAddChild = (bool)Loading.GetIsAddChild(uIElement);
if (!isAddChild)
Loading.SetChild(uIElement, null);
container.Child = null;
layer.Remove(container);
}
}
}
return;
}
var adornerContainer = new AdornerContainer(uIElement);
var value = Loading.GetChild(uIElement);
if (value == null)
{
var isLoading = GetIsShow(uIElement);
if (isLoading)
{
var w = (double)uIElement.GetValue(ActualWidthProperty);
var h = (double)uIElement.GetValue(ActualHeightProperty);
var defaultLoading = new DefaultLoading();
if (w < MINSIZE || h < MINSIZE)
{
defaultLoading.Width = SIZE;
defaultLoading.Height = SIZE;
defaultLoading.StrokeArray = new DoubleCollection { 10, 100 };
}
SetChild(uIElement, defaultLoading);
value = Loading.GetChild(uIElement);
}
if (value != null)
adornerContainer.Child = new MaskControl(uIElement) { Content = value, Background = ControlsHelper.Brush };
}
else
{
var normalLoading = (FrameworkElement)value;
var frameworkElement = (FrameworkElement)uIElement;
Loading.SetIsAddChild(uIElement, true);
if (oldFrameworkElement != null)
value = oldFrameworkElement;
else
{
string xaml = XamlWriter.Save(normalLoading);
oldFrameworkElement = (FrameworkElement) XamlReader.Parse(xaml);
}
var _size = frameworkElement.ActualHeight < frameworkElement.ActualWidth ? frameworkElement.ActualHeight : frameworkElement.ActualWidth;
if(_size < MINSIZE)
{
normalLoading.Width = SIZE;
normalLoading.Height = SIZE;
value = normalLoading;
}
adornerContainer.Child = new MaskControl(uIElement) { Content = value, Background = ControlsHelper.Brush };
}
layer.Add(adornerContainer);
}
public static bool GetIsShow(DependencyObject obj)
{
return (bool)obj.GetValue(IsShowProperty);
}
public static void SetIsShow(DependencyObject obj, bool value)
{
obj.SetValue(IsShowProperty, value);
}
}
}4)创建 DefaultLoading.xaml 代码如下:
5)创建 LoadingExample.xaml 实例代码如下:
效果图
