前言
动态效果和视觉元素在提升用户体验方面扮演着越来越重要的角色。在WPF应用程序中,创建一个圆形进度条不仅可以增加界面的美观性,还能有效地传递进度信息。
本文将详细介绍如何在WPF环境中实现这样一个圆形进度条,包括所需的基本概念、所需控件的配置以及动画效果的添加,帮助我们轻松地将这一功能集成到自己的应用程序中。
通过逐步解析每个步骤,我们将展示如何利用WPF的强大功能创造出既实用又美观的进度显示组件。
正文
先看一下效果吧
调用代码如下
<local:CycleProgressBar Width="100" Height="100" Background="#FFF68986" Foreground="#FFFA1F09"
Maximum="100" Minimum="0" Value="20" IsIndeterminate="False"/>
然后下面就来实现一下这个效果
第一步:先创建一个空的WPF项目
第二步:添加一个自定义控件,取名为CycleProgressBar
添加完以后,vs会自动生成一个类和一个Themes文件夹,下面有一个名为Generic的资源文件
Generic里面就是这个自定义控件的默认样式,里面只有一个border,我们就是通过改造这个默认的样式来实现圆形的进度条
到目前位置,都是vs自动生成的代码,不需要我们做任何操作
第三步:将父类设置成RangeBase,因为原生的progressbar就是继承的这个类,所以我们也继承这个类
第四步:添加依赖属性IsIndeterminate,这个属性用来控制进度条是不是一直转圈圈
public bool IsIndeterminate
{
get { return (bool)GetValue(IsIndeterminateProperty); }
set { SetValue(IsIndeterminateProperty, value); }
}
// Using a DependencyProperty as the backing store for IsIndeterminate. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsIndeterminateProperty =
DependencyProperty.Register("IsIndeterminate", typeof(bool), typeof(CycleProgressBar), new PropertyMetadata(false));
第五步:绘制控件的模板样式
在绘制之前,先添加一个nuget上面的引用,搜索expression.drawing,然后添加下面的引用
再在generic文件里面引入命名空间
xmlns:ed="schemas.microsoft.com/expression/…"
再写样式之前的准备工作就准备完了,后面就是开始写模板样式了,下面是样式的代码和注释
<local:ProgressBarValueToPercentage x:Key="ProgressBarValueToPercentage"/>
<Style TargetType="{x:Type local:CycleProgressBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CycleProgressBar}">
<Grid>
<!--这个椭圆就是进度条的背景部分,把高度和宽度都绑定到宽度上,当然绑定到高度上也一样,主要是为了保证高度和宽度一直,这样子才会显示成圆形
StrokeThickness根据自己的实际情况设置,这个表示背景部分的宽度-->
<Ellipse Stroke="{TemplateBinding Background}" StrokeThickness="10"
Height="{TemplateBinding Width}" Width="{TemplateBinding Width}"/>
<!--这个圆弧的作用就是显示进度条的进度-->
<ed:Arc x:Name="PART_Track" Width="{TemplateBinding Width}" Height="{TemplateBinding Width}"
StartAngle="0" EndAngle="0" Fill="{TemplateBinding Foreground}" Panel.ZIndex="1"
ArcThickness="10"
Stretch="None" StrokeEndLineCap="Round" StrokeStartLineCap="Round" ArcThicknessUnit="Pixel" RenderTransformOrigin="0.5,0.5">
<ed:Arc.RenderTransform>
<TransformGroup>
<RotateTransform/>
</TransformGroup>
</ed:Arc.RenderTransform>
</ed:Arc>
<!--这个进度条的作用就是当我们设置IsIndeterminate为true的时候,让这个进度条一直在那里转圈圈-->
<ed:Arc x:Name="PART_Track_Repeat" Width="{TemplateBinding Width}" Height="{TemplateBinding Width}"
StartAngle="0" EndAngle="90" Fill="{TemplateBinding Foreground}" Panel.ZIndex="1"
ArcThickness="10" Visibility="Collapsed"
Stretch="None" StrokeEndLineCap="Round" StrokeStartLineCap="Round"
ArcThicknessUnit="Pixel" RenderTransformOrigin="0.5,0.5">
<ed:Arc.RenderTransform>
<TransformGroup>
<RotateTransform/>
</TransformGroup>
</ed:Arc.RenderTransform>
</ed:Arc>
<!--这个textblock的作用就是显示进度条的百分比-->
<TextBlock x:Name="tbPercentage" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource ResourceKey=ProgressBarValueToPercentage}">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=local:CycleProgressBar}" Path="Maximum"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=local:CycleProgressBar}" Path="Value"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=local:CycleProgressBar}" Path="Minimum"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
<ControlTemplate.Triggers>
<!--设置IsIndeterminate为true的时候,就把百分比和进度条的进度的圆弧隐藏,只保留一直转圈圈的那个圆弧-->
<!--然后就是一个动画,让圆弧一直转圈圈-->
<Trigger Property="IsIndeterminate" Value="True">
<Setter Property="Visibility" TargetName="PART_Track" Value="Hidden"/>
<Setter Property="Visibility" TargetName="PART_Track_Repeat" Value="Visible"/>
<Setter Property="Visibility" TargetName="tbPercentage" Value="Hidden"/>
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard RepeatBehavior="Forever">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_Track_Repeat" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<EasingDoubleKeyFrame KeyTime="00:00:01" Value="360"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
<Trigger Property="IsIndeterminate" Value="False">
<Setter TargetName="tbPercentage" Property="Visibility" Value="Visible"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsIndeterminate" Value="false"/>
<Condition Property="Value" Value="0"/>
</MultiTrigger.Conditions>
<Setter Property="Visibility" Value="Hidden" TargetName="PART_Track"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
里面有一个名为ProgressBarValueToPercentage的转换,直接添加一个类,然后代码就在下面
public class ProgressBarValueToPercentage : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var maximum = System.Convert.ToDouble(values[0]);
var value = System.Convert.ToDouble(values[1]);
var minimum = System.Convert.ToDouble(values[2]);
if (maximum == 0)
{
return "0" + "%";
}
double progressValue = (value - minimum) / (maximum - minimum) * 100;
return (Math.Round(progressValue)).ToString() + "%";
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
到现在,样式部分就写完了,然后还要去后台代码里面实现具体的功能
第六步:实现后台代码功能
public class CycleProgressBar : RangeBase
{
public bool IsIndeterminate
{
get { return (bool)GetValue(IsIndeterminateProperty); }
set { SetValue(IsIndeterminateProperty, value); }
}
// Using a DependencyProperty as the backing store for IsIndeterminate. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsIndeterminateProperty =
DependencyProperty.Register("IsIndeterminate", typeof(bool), typeof(CycleProgressBar), new PropertyMetadata(false));
private FrameworkElement _track;
static CycleProgressBar()
{
//这段代码是创建控件的时候自带的,不用管,代码的意思就是去找generic里面名为CycleProgressBar的样式,
//如果把这段代码删了,或者generic没有CycleProgressBar的样式,程序就会报错
DefaultStyleKeyProperty.OverrideMetadata(typeof(CycleProgressBar), new FrameworkPropertyMetadata(typeof(CycleProgressBar)));
}
/// <summary>
/// 计算进度条的值
/// </summary>
private void SetPartTrackValue()
{
double minimum = this.Minimum;
double maximum = this.Maximum;
double value = this.Value;
double num = (maximum <= minimum) ? 1.0 : ((value - minimum) / (maximum - minimum));
var EndAngle = num * 360;
if (_track != null)
{
var arc = _track as Arc;
arc.EndAngle = EndAngle;
}
}
/// <summary>
/// 应用控件模板的时候调用的方法
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this._track = GetTemplateChild("PART_Track") as FrameworkElement;
SetPartTrackValue();
}
/// <summary>
/// 进度条的进度变化时触发
/// </summary>
/// <param name="oldValue"></param>
/// <param name="newValue"></param>
protected override void OnValueChanged(double oldValue, double newValue)
{
base.OnValueChanged(oldValue, newValue);
SetPartTrackValue();
}
/// <summary>
/// 最大值变化时触发
/// </summary>
/// <param name="oldMaximum"></param>
/// <param name="newMaximum"></param>
protected override void OnMaximumChanged(double oldMaximum, double newMaximum)
{
base.OnMaximumChanged(oldMaximum, newMaximum);
SetPartTrackValue();
}
/// <summary>
/// 最小值变化时触发
/// </summary>
/// <param name="oldMinimum"></param>
/// <param name="newMinimum"></param>
protected override void OnMinimumChanged(double oldMinimum, double newMinimum)
{
base.OnMinimumChanged(oldMinimum, newMinimum);
SetPartTrackValue();
}
/// <summary>
/// 控件大小变化时触发
/// </summary>
/// <param name="sizeInfo"></param>
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
SetPartTrackValue();
}
}
好了,控件的后台代码也写完了,就可以直接运行了
设置IsIndeterminate="True"以后,黄色部分就会一直转圈圈啦(脑补一下吧,没有gif,囧)
项目地址
GitHub:github.com/bearhanQ/WP…
最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。
也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!
优秀是一种习惯,欢迎大家留言学习!
转自:BearHan
链接:cnblogs.com/lvpp13/p/18356513
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!