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="http://schemas.microsoft.com/expression/2010/drawing"
再写样式之前的准备工作就准备完了,后面就是开始写模板样式了,下面是样式的代码和注释
<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地址:bearhanQ/WPFFramework: Share some experience (github.com)
QQ技术交流群:332035933;