一.前言
申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接。
本文主要有三种实现方式:
- 简单忙碌状态控件BusyBox;
- Win8/win10效果忙碌状态控件ProgressRing;
- 弹出异步等待框WaitingBox;
二.简单忙碌状态控件BusyBox
效果图:
通过属性"IsActive"控制控件是否启用,后台C#代码:
/// <summary>
/// BusyBox.xaml 的交互逻辑
/// </summary> public partial class BusyBox : UserControl { public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register("IsActive", typeof(bool), typeof(BusyBox), new PropertyMetadata(false)); /// <summary> /// 是否启用 /// </summary> public bool IsActive { get { return (bool)GetValue(IsActiveProperty); } set { SetValue(IsActiveProperty, value); } } static BusyBox() { DefaultStyleKeyProperty.OverrideMetadata(typeof(BusyBox), new FrameworkPropertyMetadata(typeof(BusyBox))); } }
使用了一个字体图标,触发器中实现动画显示的控制,样式代码:
<Style TargetType="{x:Type local:BusyBox}"> <Setter Property="Foreground" Value="{StaticResource TextForeground}"></Setter> <Setter Property="Width" Value="32"></Setter> <Setter Property="Height" Value="32"></Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:BusyBox}"> <Grid VerticalAlignment="Center" HorizontalAlignment="Center" > <Viewbox Stretch="Uniform" VerticalAlignment="Center" HorizontalAlignment="Center"> <TextBlock Text="" x:Name="FIcon" FontSize="36" Style="{StaticResource FIcon}" RenderTransformOrigin="0.5,0.5" Foreground="{TemplateBinding Foreground}"> <TextBlock.RenderTransform> <RotateTransform x:Name="TransFIcon" Angle="0"/> </TextBlock.RenderTransform> </TextBlock> </Viewbox> </Grid> <ControlTemplate.Triggers> <!--激活状态--> <Trigger Property="IsActive" Value="true"> <Setter Property="Visibility" Value="Visible" TargetName="FIcon"/> <Trigger.EnterActions> <BeginStoryboard > <Storyboard > <DoubleAnimation RepeatBehavior="Forever" Storyboard.TargetName="TransFIcon" Storyboard.TargetProperty="Angle" To="360" Duration="0:0:2.5"/> </Storyboard> </BeginStoryboard> </Trigger.EnterActions> <Trigger.ExitActions> <BeginStoryboard > <Storyboard > <DoubleAnimation RepeatBehavior="Forever" Storyboard.TargetName="TransFIcon" Storyboard.TargetProperty="Angle" To="0" Duration="0"/> </Storyboard> </BeginStoryboard> </Trigger.ExitActions> </Trigger> <!--非激活状态--> <Trigger Property="IsActive" Value="false"> <Setter Property="Visibility" Value="Collapsed" TargetName="FIcon"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
使用示例:
<CheckBox VerticalAlignment="Center" x:Name="cbActive2" IsChecked="True" Margin="5">IsActive</CheckBox> <core:BusyBox Width="80" Height="80" Foreground="White" Background="Red" Margin="5" IsActive="{Binding IsChecked ,ElementName=cbActive2}" /> <core:BusyBox Width="30" Height="30" Foreground="White" Background="Red" Margin="5" IsActive="{Binding IsChecked ,ElementName=cbActive2}" />
三.Win8/win10效果忙碌状态控件ProgressRing
这是网上一个开源项目里的控件,项目地址:http://mahapps.com/。不做多介绍了,效果图:
后台C#代码:
[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 FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 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))); 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) { var ring = (ProgressRing)ringObject; //auto set IsActive to false if we're hiding it. if ((Visibility)e.NewValue != Visibility.Visible) { //sets the value without overriding it's binding (if any). ring.SetCurrentValue(ProgressRing.IsActiveProperty, false); } else { // #1105 don't forget to re-activate ring.IsActive = true; } } }))); } 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 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) { var ring = dependencyObject as ProgressRing; if (ring == null) return; var 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) { MaxSideLength = width <= 20 ? 20 : width; } private void SetEllipseDiameter(double width) { EllipseDiameter = width / 8; } private void SetEllipseOffset(double width) { EllipseOffset = new Thickness(0, width / 2, 0,