1.ProgressRing
cs代码
[TemplateVisualState(GroupName = VisualStates.GroupActive, Name = VisualStates.StateActive)]
[TemplateVisualState(GroupName = VisualStates.GroupActive, Name = VisualStates.StateInactive)]
public partial class ProgressRing: Control
{
private bool hasAppliedTemplate = false;
public bool IsActive
{
get { return (bool)GetValue(IsActiveProperty); }
set { SetValue(IsActiveProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.Register("IsActive", typeof(bool), typeof(ProgressRing), new PropertyMetadata(false,new PropertyChangedCallback (OnActiveChanged)));
public TemplateSettingValues TemplateSettings
{
get { return (TemplateSettingValues)GetValue(TemplateSettingsProperty); }
set { SetValue(TemplateSettingsProperty, value); }
}
// Using a DependencyProperty as the backing store for TemplateSettings. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TemplateSettingsProperty =
DependencyProperty.Register("TemplateSettings", typeof(TemplateSettingValues), typeof(ProgressRing), new PropertyMetadata(null));
private static void OnActiveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var pr = (ProgressRing)d;
var isActive = (bool)e.NewValue;
pr.UpdateState(isActive);
}
public ProgressRing()
{
DefaultStyleKey = typeof(ProgressRing);
TemplateSettings = new TemplateSettingValues(150);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
hasAppliedTemplate = true;
UpdateState(IsActive);
}
private void UpdateState(bool isActive)
{
if (hasAppliedTemplate)
{
string state = isActive ? VisualStates.StateActive : VisualStates.StateInactive;
VisualStateManager.GoToState(this, state, true);
}
}
}
public class TemplateSettingValues : System.Windows.DependencyObject
{
// Using a DependencyProperty as the backing store for MaxSideLength. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MaxSideLengthProperty =
DependencyProperty.Register("MaxSideLength", typeof(double), typeof(TemplateSettingValues), new PropertyMetadata(0D));
// Using a DependencyProperty as the backing store for EllipseDiameter. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EllipseDiameterProperty =
DependencyProperty.Register("EllipseDiameter", typeof(double), typeof(TemplateSettingValues), new PropertyMetadata(0D));
// Using a DependencyProperty as the backing store for EllipseOffset. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EllipseOffsetProperty =
DependencyProperty.Register("EllipseOffset", typeof(Thickness), typeof(TemplateSettingValues), new PropertyMetadata(default(Thickness)));
public TemplateSettingValues(double width)
{
if (width <= 40)
{
EllipseDiameter = (width / 10) + 1;
}
else
{
EllipseDiameter = width / 10;
}
MaxSideLength = width - EllipseDiameter;
EllipseOffset = new System.Windows.Thickness(0, EllipseDiameter * 2.5, 0, 0);
}
public double MaxSideLength
{
get { return (double)GetValue(MaxSideLengthProperty); }
set { SetValue(MaxSideLengthProperty, value); }
}
public double EllipseDiameter
{
get { return (double)GetValue(EllipseDiameterProperty); }
set { SetValue(EllipseDiameterProperty, value); }
}
public Thickness EllipseOffset
{
get { return (Thickness)GetValue(EllipseOffsetProperty); }
set { SetValue(EllipseOffsetProperty, value); }
}
}
xaml代码:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfAppStyle.ProgressRing">
<Style TargetType="{x:Type local:ProgressRing}">
<Setter Property="Background"
Value="Transparent" />
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
<Setter Property="IsHitTestVisible"
Value="False" />
<Setter Property="HorizontalAlignment"
Value="Center" />
<Setter Property="VerticalAlignment"
Value="Center" />
<Setter Property="MinHeight"
Value="20" />
<Setter Property="MinWidth"
Value="20" />
<Setter Property="IsTabStop"
Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ProgressRing}">
<Border x:Name="ProgressRingRoot"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}">
<Border.Resources>
<Style x:Key="ProgressRingEllipseStyle"
TargetType="Ellipse">
<Setter Property="Opacity" Value="0" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
</Style>
</Border.Resources>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SizeStates">
<VisualState x:Name="Large">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="SixthCircle" Storyboard.TargetProperty="Visibility">
<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 Duration="0" Storyboard.TargetName="Ring" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="E1" Storyboard.TargetProperty="Opacity" BeginTime="0">
<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 Storyboard.TargetName="E2" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.167">
<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 Storyboard.TargetName="E3" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.334">
<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 Storyboard.TargetName="E4" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.501">
<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 Storyboard.TargetName="E5" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.668">
<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 Storyboard.TargetName="E6" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.835">
<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 Storyboard.TargetName="E1R" BeginTime="0" Storyboard.TargetProperty="Angle">
<SplineDoubleKeyFrame KeyTime="0" Value="-110" KeySpline="0.13,0.21,0.1,0.7" />
<SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="10" KeySpline="0.02,0.33,0.38,0.77" />
<SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="93" />
<SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="205" KeySpline="0.57,0.17,0.95,0.75" />
<SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="357" KeySpline="0,0.19,0.07,0.72" />
<SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="439" />
<SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="585" KeySpline="0,0,0.95,0.37" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="E2R" BeginTime="00:00:00.167" Storyboard.TargetProperty="Angle">
<SplineDoubleKeyFrame KeyTime="0" Value="-116" KeySpline="0.13,0.21,0.1,0.7" />
<SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="4" KeySpline="0.02,0.33,0.38,0.77" />
<SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="87" />
<SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="199" KeySpline="0.57,0.17,0.95,0.75" />
<SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="351" KeySpline="0,0.19,0.07,0.72" />
<SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="433" />
<SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="579" KeySpline="0,0,0.95,0.37" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="E3R" BeginTime="00:00:00.334" Storyboard.TargetProperty="Angle">
<SplineDoubleKeyFrame KeyTime="0" Value="-122" KeySpline="0.13,0.21,0.1,0.7" />
<SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-2" KeySpline="0.02,0.33,0.38,0.77" />
<SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="81" />
<SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="193" KeySpline="0.57,0.17,0.95,0.75" />
<SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="345" KeySpline="0,0.19,0.07,0.72" />
<SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="427" />
<SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="573" KeySpline="0,0,0.95,0.37" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="E4R" BeginTime="00:00:00.501" Storyboard.TargetProperty="Angle">
<SplineDoubleKeyFrame KeyTime="0" Value="-128" KeySpline="0.13,0.21,0.1,0.7" />
<SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-8" KeySpline="0.02,0.33,0.38,0.77" />
<SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="75" />
<SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="187" KeySpline="0.57,0.17,0.95,0.75" />
<SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="339" KeySpline="0,0.19,0.07,0.72" />
<SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="421" />
<SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="567" KeySpline="0,0,0.95,0.37" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="E5R" BeginTime="00:00:00.668" Storyboard.TargetProperty="Angle">
<SplineDoubleKeyFrame KeyTime="0" Value="-134" KeySpline="0.13,0.21,0.1,0.7" />
<SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-14" KeySpline="0.02,0.33,0.38,0.77" />
<SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="69" />
<SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="181" KeySpline="0.57,0.17,0.95,0.75" />
<SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="331" KeySpline="0,0.19,0.07,0.72" />
<SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="415" />
<SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="561" KeySpline="0,0,0.95,0.37" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="E6R" BeginTime="00:00:00.835" Storyboard.TargetProperty="Angle">
<SplineDoubleKeyFrame KeyTime="0" Value="-140" KeySpline="0.13,0.21,0.1,0.7" />
<SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-20" KeySpline="0.02,0.33,0.38,0.77" />
<SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="63" />
<SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="175" KeySpline="0.57,0.17,0.95,0.75" />
<SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="325" KeySpline="0,0.19,0.07,0.72" />
<SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="409" />
<SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="555" KeySpline="0,0,0.95,0.37" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="Ring"
Background="{TemplateBinding Background}"
MaxWidth="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.MaxSideLength}"
MaxHeight="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.MaxSideLength}"
Visibility="Collapsed"
RenderTransformOrigin=".5,.5"
FlowDirection="LeftToRight">
<Canvas RenderTransformOrigin=".5,.5">
<Canvas.RenderTransform>
<RotateTransform x:Name="E1R" />
</Canvas.RenderTransform>
<Ellipse x:Name="E1"
Style="{StaticResource ProgressRingEllipseStyle}"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseDiameter}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseDiameter}"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseOffset}"
Fill="{TemplateBinding Foreground}" />
</Canvas>
<Canvas RenderTransformOrigin=".5,.5">
<Canvas.RenderTransform>
<RotateTransform x:Name="E2R" />
</Canvas.RenderTransform>
<Ellipse x:Name="E2"
Style="{StaticResource ProgressRingEllipseStyle}"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseDiameter}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseDiameter}"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseOffset}"
Fill="{TemplateBinding Foreground}" />
</Canvas>
<Canvas RenderTransformOrigin=".5,.5">
<Canvas.RenderTransform>
<RotateTransform x:Name="E3R" />
</Canvas.RenderTransform>
<Ellipse x:Name="E3"
Style="{StaticResource ProgressRingEllipseStyle}"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseDiameter}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseDiameter}"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseOffset}"
Fill="{TemplateBinding Foreground}" />
</Canvas>
<Canvas RenderTransformOrigin=".5,.5">
<Canvas.RenderTransform>
<RotateTransform x:Name="E4R" />
</Canvas.RenderTransform>
<Ellipse x:Name="E4"
Style="{StaticResource ProgressRingEllipseStyle}"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseDiameter}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseDiameter}"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseOffset}"
Fill="{TemplateBinding Foreground}" />
</Canvas>
<Canvas RenderTransformOrigin=".5,.5">
<Canvas.RenderTransform>
<RotateTransform x:Name="E5R" />
</Canvas.RenderTransform>
<Ellipse x:Name="E5"
Style="{StaticResource ProgressRingEllipseStyle}"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseDiameter}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseDiameter}"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseOffset}"
Fill="{TemplateBinding Foreground}" />
</Canvas>
<Canvas RenderTransformOrigin=".5,.5" Visibility="Collapsed" x:Name="SixthCircle">
<Canvas.RenderTransform>
<RotateTransform x:Name="E6R" />
</Canvas.RenderTransform>
<Ellipse x:Name="E6"
Style="{StaticResource ProgressRingEllipseStyle}"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseDiameter}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseDiameter}"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.EllipseOffset}"
Fill="{TemplateBinding Foreground}" />
</Canvas>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
使用:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/WpfAppStyle;component/ProgressRing/ProgressRing.xaml" />
</ResourceDictionary>
</Window.Resources>
<progressRing:ProgressRing IsActive="False" Width="150" Height="150" x:Name="progressRing" />
效果:
2.busyIndicator
cs代码
/// <summary>
/// A control to provide a visual indicator when an application is busy.
/// </summary>
[TemplateVisualState(Name = VisualState4BusyIndicator.StateIdle, GroupName = VisualState4BusyIndicator.GroupBusyStatus)]
[TemplateVisualState(Name = VisualState4BusyIndicator.StateBusy, GroupName = VisualState4BusyIndicator.GroupBusyStatus)]
[TemplateVisualState(Name = VisualState4BusyIndicator.StateVisible, GroupName = VisualState4BusyIndicator.GroupVisibility)]
[TemplateVisualState(Name = VisualState4BusyIndicator.StateHidden, GroupName = VisualState4BusyIndicator.GroupVisibility)]
[StyleTypedProperty(Property = "OverlayStyle", StyleTargetType = typeof(Rectangle))]
[StyleTypedProperty(Property = "ProgressBarStyle", StyleTargetType = typeof(ProgressBar))]
public partial class BusyIndicator : ContentControl
{
#region Private Members
/// <summary>
/// Timer used to delay the initial display and avoid flickering.
/// </summary>
private DispatcherTimer _displayAfterTimer = new DispatcherTimer();
#endregion //Private Members
#region Constructors
static BusyIndicator()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BusyIndicator), new FrameworkPropertyMetadata(typeof(BusyIndicator)));
}
public BusyIndicator()
{
_displayAfterTimer.Tick += DisplayAfterTimerElapsed;
}
#endregion //Constructors
#region Base Class Overrides
/// <summary>
/// Overrides the OnApplyTemplate method.
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
ChangeVisualState(false);
}
#endregion //Base Class Overrides
#region Properties
/// <summary>
/// Gets or sets a value indicating whether the BusyContent is visible.
/// </summary>
protected bool IsContentVisible
{
get;
set;
}
#endregion //Properties
#region Dependency Properties
#region IsBusy
/// <summary>
/// Identifies the IsBusy dependency property.
/// </summary>
public static readonly DependencyProperty IsBusyProperty = DependencyProperty.Register(
"IsBusy",
typeof(bool),
typeof(BusyIndicator),
new PropertyMetadata(false, new PropertyChangedCallback(OnIsBusyChanged)));
/// <summary>
/// Gets or sets a value indicating whether the busy indicator should show.
/// </summary>
public bool IsBusy
{
get
{
return (bool)GetValue(IsBusyProperty);
}
set
{
SetValue(IsBusyProperty, value);
}
}
/// <summary>
/// IsBusyProperty property changed handler.
/// </summary>
/// <param name="d">BusyIndicator that changed its IsBusy.</param>
/// <param name="e">Event arguments.</param>
private static void OnIsBusyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((BusyIndicator)d).OnIsBusyChanged(e);
}
/// <summary>
/// IsBusyProperty property changed handler.
/// </summary>
/// <param name="e">Event arguments.</param>
protected virtual void OnIsBusyChanged(DependencyPropertyChangedEventArgs e)
{
if (IsBusy)
{
if (DisplayAfter.Equals(TimeSpan.Zero))
{
// Go visible now
IsContentVisible = true;
}
else
{
// Set a timer to go visible
_displayAfterTimer.Interval = DisplayAfter;
_displayAfterTimer.Start();
}
}
else
{
// No longer visible
_displayAfterTimer.Stop();
IsContentVisible = false;
if (this.FocusAfterBusy != null)
{
this.FocusAfterBusy.Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
{
this.FocusAfterBusy.Focus();
}
));
}
}
ChangeVisualState(true);
}
#endregion //IsBusy
#region Busy Content
/// <summary>
/// Identifies the BusyContent dependency property.
/// </summary>
public static readonly DependencyProperty BusyContentProperty = DependencyProperty.Register(
"BusyContent",
typeof(object),
typeof(BusyIndicator),
new PropertyMetadata(null));
/// <summary>
/// Gets or sets a value indicating the busy content to display to the user.
/// </summary>
public object BusyContent
{
get
{
return (object)GetValue(BusyContentProperty);
}
set
{
SetValue(BusyContentProperty, value);
}
}
#endregion //Busy Content
#region Busy Content Template
/// <summary>
/// Identifies the BusyTemplate dependency property.
/// </summary>
public static readonly DependencyProperty BusyContentTemplateProperty = DependencyProperty.Register(
"BusyContentTemplate",
typeof(DataTemplate),
typeof(BusyIndicator),
new PropertyMetadata(null));
/// <summary>
/// Gets or sets a value indicating the template to use for displaying the busy content to the user.
/// </summary>
public DataTemplate BusyContentTemplate
{
get
{
return (DataTemplate)GetValue(BusyContentTemplateProperty);
}
set
{
SetValue(BusyContentTemplateProperty, value);
}
}
#endregion //Busy Content Template
#region Display After
/// <summary>
/// Identifies the DisplayAfter dependency property.
/// </summary>
public static readonly DependencyProperty DisplayAfterProperty = DependencyProperty.Register(
"DisplayAfter",
typeof(TimeSpan),
typeof(BusyIndicator),
new PropertyMetadata(TimeSpan.FromSeconds(0.1)));
/// <summary>
/// Gets or sets a value indicating how long to delay before displaying the busy content.
/// </summary>
public TimeSpan DisplayAfter
{
get
{
return (TimeSpan)GetValue(DisplayAfterProperty);
}
set
{
SetValue(DisplayAfterProperty, value);
}
}
#endregion //Display After
#region FocusAfterBusy
/// <summary>
/// Identifies the FocusAfterBusy dependency property.
/// </summary>
public static readonly DependencyProperty FocusAfterBusyProperty = DependencyProperty.Register(
"FocusAfterBusy",
typeof(Control),
typeof(BusyIndicator),
new PropertyMetadata(null));
/// <summary>
/// Gets or sets a Control that should get focus when the busy indicator disapears.
/// </summary>
public Control FocusAfterBusy
{
get
{
return (Control)GetValue(FocusAfterBusyProperty);
}
set
{
SetValue(FocusAfterBusyProperty, value);
}
}
#endregion //FocusAfterBusy
#region Overlay Style
/// <summary>
/// Identifies the OverlayStyle dependency property.
/// </summary>
public static readonly DependencyProperty OverlayStyleProperty = DependencyProperty.Register(
"OverlayStyle",
typeof(Style),
typeof(BusyIndicator),
new PropertyMetadata(null));
/// <summary>
/// Gets or sets a value indicating the style to use for the overlay.
/// </summary>
public Style OverlayStyle
{
get
{
return (Style)GetValue(OverlayStyleProperty);
}
set
{
SetValue(OverlayStyleProperty, value);
}
}
#endregion //Overlay Style
#region ProgressBar Style
/// <summary>
/// Identifies the ProgressBarStyle dependency property.
/// </summary>
public static readonly DependencyProperty ProgressBarStyleProperty = DependencyProperty.Register(
"ProgressBarStyle",
typeof(Style),
typeof(BusyIndicator),
new PropertyMetadata(null));
/// <summary>
/// Gets or sets a value indicating the style to use for the progress bar.
/// </summary>
public Style ProgressBarStyle
{
get
{
return (Style)GetValue(ProgressBarStyleProperty);
}
set
{
SetValue(ProgressBarStyleProperty, value);
}
}
#endregion //ProgressBar Style
#endregion //Dependency Properties
#region Methods
/// <summary>
/// Handler for the DisplayAfterTimer.
/// </summary>
/// <param name="sender">Event sender.</param>
/// <param name="e">Event arguments.</param>
private void DisplayAfterTimerElapsed(object sender, EventArgs e)
{
_displayAfterTimer.Stop();
IsContentVisible = true;
ChangeVisualState(true);
}
/// <summary>
/// Changes the control's visual state(s).
/// </summary>
/// <param name="useTransitions">True if state transitions should be used.</param>
protected virtual void ChangeVisualState(bool useTransitions)
{
VisualStateManager.GoToState(this, IsBusy ? VisualState4BusyIndicator.StateBusy : VisualState4BusyIndicator.StateIdle, useTransitions);
VisualStateManager.GoToState(this, IsContentVisible ? VisualState4BusyIndicator.StateVisible : VisualState4BusyIndicator.StateHidden, useTransitions);
}
#endregion //Methods
}
public class ProgressBarWidthConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var contentWidth = (double)values[0];
var parentMinWidth = (double)values[1];
return Math.Max(contentWidth, parentMinWidth);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class VisualState4BusyIndicator
{
/// <summary>
/// Busyness group name.
/// </summary>
public const string GroupBusyStatus = "BusyStatusStates";
/// <summary>
/// Busy state for BusyIndicator.
/// </summary>
public const string StateBusy = "Busy";
/// <summary>
/// Idle state for BusyIndicator.
/// </summary>
public const string StateIdle = "Idle";
/// <summary>
/// BusyDisplay group.
/// </summary>
public const string GroupVisibility = "VisibilityStates";
/// <summary>
/// Visible state name for BusyIndicator.
/// </summary>
public const string StateVisible = "Visible";
/// <summary>
/// Hidden state name for BusyIndicator.
/// </summary>
public const string StateHidden = "Hidden";
}
xmal代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:converter="clr-namespace:WpfAppStyle.Busy.Converter"
xmlns:local="clr-namespace:WpfAppStyle.Busy">
<converter:ProgressBarWidthConverter x:Key="ProgressBarWidthConverter" />
<Style TargetType="{x:Type local:BusyIndicator}">
<Setter Property="BusyContent" Value="Please wait..." />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Focusable" Value="False" />
<Setter Property="OverlayStyle">
<Setter.Value>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="White" />
<Setter Property="Opacity" Value="0.5" />
</Style>
</Setter.Value>
</Setter>
<Setter Property="ProgressBarStyle">
<Setter.Value>
<Style TargetType="ProgressBar">
<Setter Property="IsIndeterminate" Value="True" />
<Setter Property="Height" Value="15" />
<Setter Property="Foreground" Value="BurlyWood"></Setter>
<Setter Property="Margin" Value="8,0,8,8" />
</Style>
</Setter.Value>
</Setter>
<Setter Property="DisplayAfter" Value="00:00:00.1" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BusyIndicator}">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisibilityStates">
<VisualState x:Name="Hidden">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="busycontent" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="overlay" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Visible">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="busycontent" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="overlay" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="BusyStatusStates">
<VisualState x:Name="Idle">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="content" Storyboard.TargetProperty="(Control.IsEnabled)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean>True</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Busy">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="content" Storyboard.TargetProperty="(Control.IsEnabled)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean>False</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentControl x:Name="content" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" IsTabStop="False" Focusable="False" />
<Rectangle x:Name="overlay" Style="{TemplateBinding OverlayStyle}" />
<ContentPresenter x:Name="busycontent">
<ContentPresenter.Content>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Border Background="White" BorderThickness="1" CornerRadius="2">
<Border.BorderBrush>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<GradientStop Color="#FFA3AEB9" Offset="0" />
<GradientStop Color="#FF8399A9" Offset="0.375" />
<GradientStop Color="#FF718597" Offset="0.375" />
<GradientStop Color="#FF617584" Offset="1" />
</LinearGradientBrush>
</Border.BorderBrush>
<Border CornerRadius="1.5" Margin="1">
<Border.Background>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<GradientStop Color="#FFF6F8F9" Offset="0.02" />
<GradientStop Color="#FFB8B8B8" Offset="0.996" />
</LinearGradientBrush>
</Border.Background>
<Grid x:Name="_grid"
MinWidth="150">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentPresenter x:Name="busyContent"
Content="{TemplateBinding BusyContent}"
ContentTemplate="{TemplateBinding BusyContentTemplate}"
HorizontalAlignment="Center"
Margin="8" />
<ProgressBar Grid.Row="1" Style="{TemplateBinding ProgressBarStyle}" >
<ProgressBar.Width>
<MultiBinding Converter="{StaticResource ProgressBarWidthConverter}">
<Binding Path="ActualWidth"
ElementName="busyContent" />
<Binding Path="MinWidth"
ElementName="_grid" />
</MultiBinding>
</ProgressBar.Width>
</ProgressBar>
</Grid>
</Border>
</Border>
</Grid>
</ContentPresenter.Content>
</ContentPresenter>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
效果
3.progress
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:WpfAppStyle.Loading">
<System:Double x:Key="ProgressBarMinHeight">6</System:Double>
<Style TargetType="{x:Type local:Loading}">
<Setter Property="Background" Value="#1FFFFFFF" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Foreground" Value="#FF086F9E" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Maximum" Value="100" />
<Setter Property="MinHeight" Value="{StaticResource ProgressBarMinHeight}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Loading}">
<ControlTemplate.Resources>
<Storyboard x:Key="IndeterminateStoryboard" RepeatBehavior="Forever">
<DoubleAnimation x:Name="MainDoubleAnim"
Storyboard.TargetName="EllipseGrid"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
Duration="0:0:3.917" />
<DoubleAnimationUsingKeyFrames x:Name="E1Anim"
Storyboard.TargetName="E1"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<SplineDoubleKeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:1" />
<EasingDoubleKeyFrame KeyTime="0:0:2" />
<SplineDoubleKeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:3" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames x:Name="E2Anim"
Storyboard.TargetName="E2"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.167" Value="0" />
<SplineDoubleKeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:1.167" />
<EasingDoubleKeyFrame KeyTime="0:0:2.167" />
<SplineDoubleKeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:3.167" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames x:Name="E3Anim"
Storyboard.TargetName="E3"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.333" Value="0" />
<SplineDoubleKeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:1.333" />
<EasingDoubleKeyFrame KeyTime="0:0:2.333" />
<SplineDoubleKeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:3.333" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames x:Name="E4Anim"
Storyboard.TargetName="E4"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0" />
<SplineDoubleKeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:1.5" />
<EasingDoubleKeyFrame KeyTime="0:0:2.5" />
<SplineDoubleKeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:3.5" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames x:Name="E5Anim"
Storyboard.TargetName="E5"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.667" Value="0" />
<SplineDoubleKeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:1.667" />
<EasingDoubleKeyFrame KeyTime="0:0:2.667" />
<SplineDoubleKeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:3.667" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="B1" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0" Value="-50" />
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:2" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:3" Value="100" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="B2" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0" Value="-50" />
<EasingDoubleKeyFrame KeyTime="0:0:0.667" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:2.167" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:3.167" Value="100" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="B3" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0" Value="-50" />
<EasingDoubleKeyFrame KeyTime="0:0:0.833" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:2.333" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:3.333" Value="100" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="B4" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0" Value="-50" />
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:2.5" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:3.5" Value="100" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="B5" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0" Value="-50" />
<EasingDoubleKeyFrame KeyTime="0:0:1.167" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:2.667" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:3.667" Value="100" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetName="EllipseGrid"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0" />
<DoubleAnimation Storyboard.TargetName="DeterminateRoot"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0" />
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="E1" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:3" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:3" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="E2" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.167" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.167" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:3.167" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:3.167" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="E3" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.333" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.333" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:3.333" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:3.333" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="E4" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:3.5" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:3.5" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="E5" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.667" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.667" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:3.667" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:3.667" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<Grid x:Name="ContainingGrid">
<Grid x:Name="EllipseClip" ClipToBounds="True">
<Grid x:Name="EllipseGrid" Opacity="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RenderTransform>
<TranslateTransform />
</Grid.RenderTransform>
<Border x:Name="B1"
Grid.Column="8"
RenderTransformOrigin="0.5,0.5">
<Border.RenderTransform>
<TranslateTransform />
</Border.RenderTransform>
<Ellipse x:Name="E1"
Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Fill="{TemplateBinding Foreground}"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TranslateTransform />
</Ellipse.RenderTransform>
</Ellipse>
</Border>
<Rectangle Grid.Column="7" Width="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<Border x:Name="B2"
Grid.Column="6"
RenderTransformOrigin="0.5,0.5">
<Border.RenderTransform>
<TranslateTransform />
</Border.RenderTransform>
<Ellipse x:Name="E2"
Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Fill="{TemplateBinding Foreground}"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TranslateTransform />
</Ellipse.RenderTransform>
</Ellipse>
</Border>
<Rectangle Grid.Column="5" Width="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<Border x:Name="B3"
Grid.Column="4"
RenderTransformOrigin="0.5,0.5">
<Border.RenderTransform>
<TranslateTransform />
</Border.RenderTransform>
<Ellipse x:Name="E3"
Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Fill="{TemplateBinding Foreground}"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TranslateTransform />
</Ellipse.RenderTransform>
</Ellipse>
</Border>
<Rectangle Grid.Column="3" Width="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<Border x:Name="B4"
Grid.Column="2"
RenderTransformOrigin="0.5,0.5">
<Border.RenderTransform>
<TranslateTransform />
</Border.RenderTransform>
<Ellipse x:Name="E4"
Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Fill="{TemplateBinding Foreground}"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TranslateTransform />
</Ellipse.RenderTransform>
</Ellipse>
</Border>
<Rectangle Grid.Column="1" Width="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<Border x:Name="B5"
Grid.Column="0"
RenderTransformOrigin="0.5,0.5">
<Border.RenderTransform>
<TranslateTransform />
</Border.RenderTransform>
<Ellipse x:Name="E5"
Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Fill="{TemplateBinding Foreground}"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TranslateTransform />
</Ellipse.RenderTransform>
</Ellipse>
</Border>
</Grid>
</Grid>
<Grid x:Name="DeterminateRoot"
Margin="{TemplateBinding Padding}"
Opacity="0">
<Border x:Name="PART_Track"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" />
<Rectangle x:Name="PART_Indicator"
HorizontalAlignment="Left"
Fill="{TemplateBinding Foreground}" />
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Determinate" />
<VisualState x:Name="Indeterminate" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsIndeterminate" Value="False">
<Setter TargetName="DeterminateRoot" Property="Opacity" Value="1" />
</Trigger>
<Trigger Property="Orientation" Value="Vertical">
<Setter Property="MinHeight" Value="0" />
<Setter Property="MinWidth" Value="{StaticResource ProgressBarMinHeight}" />
<Setter Property="UseLayoutRounding" Value="True" />
<Setter TargetName="ContainingGrid" Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="-90" />
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
public class Loading : ProgressBar
{
public static readonly DependencyProperty EllipseDiameterProperty
= DependencyProperty.Register(nameof(EllipseDiameter),
typeof(double),
typeof(Loading),
new PropertyMetadata(default(double)));
public static readonly DependencyProperty EllipseOffsetProperty =
DependencyProperty.Register(nameof(EllipseOffset),
typeof(double),
typeof(Loading),
new PropertyMetadata(default(double)));
private readonly object lockme = new object();
private Storyboard indeterminateStoryboard;
static Loading()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Loading), new FrameworkPropertyMetadata(typeof(Loading)));
IsIndeterminateProperty.OverrideMetadata(typeof(Loading), new FrameworkPropertyMetadata(OnIsIndeterminateChanged));
}
public Loading()
{
this.IsVisibleChanged += this.VisibleChangedHandler;
}
private void VisibleChangedHandler(object sender, DependencyPropertyChangedEventArgs e)
{
// reset Storyboard if Visibility is set to Visible #1300
if (this.IsIndeterminate)
{
ToggleIndeterminate(this, (bool)e.OldValue, (bool)e.NewValue);
}
}
private static void OnIsIndeterminateChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var bar = (Loading)dependencyObject;
if (bar.IsLoaded && bar.IsVisible)
{
ToggleIndeterminate(bar, (bool)e.OldValue, (bool)e.NewValue);
}
}
private static void ToggleIndeterminate(Loading bar, bool oldValue, bool newValue)
{
if (newValue == oldValue)
{
return;
}
var indeterminateState = bar.GetIndeterminate();
var containingObject = bar.GetTemplateChild("ContainingGrid") as FrameworkElement;
if (indeterminateState != null && containingObject != null)
{
var resetAction = new Action(() =>
{
if (oldValue && indeterminateState.Storyboard != null)
{
// remove the previous storyboard from the Grid #1855
indeterminateState.Storyboard.Stop(containingObject);
indeterminateState.Storyboard.Remove(containingObject);
}
if (newValue)
{
bar.ResetStoryboard(bar.ActualSize(true), false);
}
});
resetAction?.Invoke();
}
}
/// <summary>
/// Gets/sets the diameter of the ellipses used in the indeterminate animation.
/// </summary>
public double EllipseDiameter
{
get { return (double)this.GetValue(EllipseDiameterProperty); }
set { this.SetValue(EllipseDiameterProperty, value); }
}
/// <summary>
/// Gets/sets the offset of the ellipses used in the indeterminate animation.
/// </summary>
public double EllipseOffset
{
get { return (double)this.GetValue(EllipseOffsetProperty); }
set { this.SetValue(EllipseOffsetProperty, value); }
}
private void SizeChangedHandler(object sender, SizeChangedEventArgs e)
{
var size = this.ActualSize(false);
var bar = this;
if (this.Visibility == Visibility.Visible && this.IsIndeterminate)
{
bar.ResetStoryboard(size, true);
}
}
private double ActualSize(bool invalidateMeasureArrange)
{
if (invalidateMeasureArrange)
{
this.UpdateLayout();
this.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
this.InvalidateArrange();
}
return this.Orientation == Orientation.Horizontal ? this.ActualWidth : this.ActualHeight;
}
private void ResetStoryboard(double width, bool removeOldStoryboard)
{
if (!this.IsIndeterminate)
{
return;
}
lock (this.lockme)
{
//perform calculations
var containerAnimStart = this.CalcContainerAnimStart(width);
var containerAnimEnd = this.CalcContainerAnimEnd(width);
var ellipseAnimWell = this.CalcEllipseAnimWell(width);
var ellipseAnimEnd = this.CalcEllipseAnimEnd(width);
//reset the main double animation
try
{
var indeterminate = this.GetIndeterminate();
if (indeterminate != null && this.indeterminateStoryboard != null)
{
var newStoryboard = this.indeterminateStoryboard.Clone();
var doubleAnim = newStoryboard.Children.First(t => t.Name == "MainDoubleAnim");
doubleAnim.SetValue(DoubleAnimation.FromProperty, containerAnimStart);
doubleAnim.SetValue(DoubleAnimation.ToProperty, containerAnimEnd);
var namesOfElements = new[] { "E1", "E2", "E3", "E4", "E5" };
foreach (var elemName in namesOfElements)
{
var doubleAnimParent = (DoubleAnimationUsingKeyFrames)newStoryboard.Children.First(t => t.Name == elemName + "Anim");
DoubleKeyFrame first,
second,
third;
if (elemName == "E1")
{
first = doubleAnimParent.KeyFrames[1];
second = doubleAnimParent.KeyFrames[2];
third = doubleAnimParent.KeyFrames[3];
}
else
{
first = doubleAnimParent.KeyFrames[2];
second = doubleAnimParent.KeyFrames[3];
third = doubleAnimParent.KeyFrames[4];
}
first.Value = ellipseAnimWell;
second.Value = ellipseAnimWell;
third.Value = ellipseAnimEnd;
first.InvalidateProperty(DoubleKeyFrame.ValueProperty);
second.InvalidateProperty(DoubleKeyFrame.ValueProperty);
third.InvalidateProperty(DoubleKeyFrame.ValueProperty);
doubleAnimParent.InvalidateProperty(Storyboard.TargetPropertyProperty);
doubleAnimParent.InvalidateProperty(Storyboard.TargetNameProperty);
}
var containingGrid = (FrameworkElement)this.GetTemplateChild("ContainingGrid");
if (removeOldStoryboard && indeterminate.Storyboard != null)
{
// remove the previous storyboard from the Grid #1855
indeterminate.Storyboard.Stop(containingGrid);
indeterminate.Storyboard.Remove(containingGrid);
}
indeterminate.Storyboard = newStoryboard;
indeterminate.Storyboard?.Begin(containingGrid, true);
}
}
catch (Exception)
{
//we just ignore
}
}
}
private VisualState GetIndeterminate()
{
var templateGrid = this.GetTemplateChild("ContainingGrid") as FrameworkElement;
if (templateGrid == null)
{
this.ApplyTemplate();
templateGrid = this.GetTemplateChild("ContainingGrid") as FrameworkElement;
if (templateGrid == null) return null;
}
var groups = VisualStateManager.GetVisualStateGroups(templateGrid);
return groups?.OfType<VisualStateGroup>()
.SelectMany(group => group.States.OfType<VisualState>())
.FirstOrDefault(state => state.Name == "Indeterminate");
}
private void SetEllipseDiameter(double width)
{
this.SetCurrentValue(EllipseDiameterProperty, width <= 180 ? 4d : (width <= 280 ? 5d : 6d));
}
private void SetEllipseOffset(double width)
{
this.SetCurrentValue(EllipseOffsetProperty, width <= 180 ? 4d : (width <= 280 ? 7d : 9d));
}
private double CalcContainerAnimStart(double width)
{
return width <= 180 ? -34 : (width <= 280 ? -50.5 : -63);
}
private double CalcContainerAnimEnd(double width)
{
var firstPart = 0.4352 * width;
return width <= 180 ? firstPart - 25.731 : (width <= 280 ? firstPart + 27.84 : firstPart + 58.862);
}
private double CalcEllipseAnimWell(double width)
{
return width * 1.0 / 3.0;
}
private double CalcEllipseAnimEnd(double width)
{
return width * 2.0 / 3.0;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
lock (this.lockme)
{
this.indeterminateStoryboard = this.TryFindResource("IndeterminateStoryboard") as Storyboard;
}
this.Loaded -= this.LoadedHandler;
this.Loaded += this.LoadedHandler;
}
private void LoadedHandler(object sender, RoutedEventArgs routedEventArgs)
{
this.Loaded -= this.LoadedHandler;
this.SizeChangedHandler(null, null);
this.SizeChanged += this.SizeChangedHandler;
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
this.UpdateEllipseProperties();
}
private void UpdateEllipseProperties()
{
// Update the Ellipse properties to their default values
// only if they haven't been user-set.
var actualSize = this.ActualSize(true);
if (actualSize > 0)
{
if (this.EllipseDiameter.Equals(0))
{
this.SetEllipseDiameter(actualSize);
}
if (this.EllipseOffset.Equals(0))
{
this.SetEllipseOffset(actualSize);
}
}
}
}
效果(使用时IsIndeterminate="True")