一.前言
申明: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(