WPF实现圆形进度条的示例代码
驚鏵 人气:0WPF 实现圆形进度条
- 框架使用
.NET40
; Visual Studio 2019
;CircularProgressBar
继承ProgressBar
,在XAML
中创建两个Path
的Data
设置ArcSegment
修改第二个控件的Point
,设置StartPoint = new Point(Size.Width, 0)
设置起点。- 创建依赖属性
Angle
作为修改ArcSegment
的Point
作为进度条的圆的闭合。 - 当进度条
ValueChanged
时创建DoubleAnimation
动画,修改Angle
。
示例代码
1) CircularProgressBar.xaml
代码如下:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:WPFDevelopers.Controls" xmlns:convert="clr-namespace:WPFDevelopers.Converts"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Basic/ControlBasic.xaml"/> </ResourceDictionary.MergedDictionaries> <convert:AngleToPointConverter x:Key="prConverter"/> <convert:AngleToIsLargeConverter x:Key="isLargeConverter"/> <Style TargetType="{x:Type controls:CircularProgressBar}" BasedOn="{StaticResource ControlBasicStyle}"> <Setter Property="Maximum" Value="100"/> <Setter Property="StrokeThickness" Value="10"/> <Setter Property="Foreground" Value="{DynamicResource InfoSolidColorBrush}"/> <Setter Property="Background" Value="{DynamicResource PrimaryNormalSolidColorBrush}"/> <Setter Property="HorizontalAlignment" Value="Center"/> <Setter Property="VerticalAlignment" Value="Center"/> <!--<Setter Property="Width" Value="100"/> <Setter Property="Height" Value="100"/>--> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type controls:CircularProgressBar}"> <controls:SmallPanel Width="{Binding ElementName=PART_Path,Path=ActualWidth}" Height="{Binding ElementName=PART_Path,Path=ActualHeight}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}"> <Canvas> <Path Stroke="{TemplateBinding BorderBrush}" StrokeThickness="{TemplateBinding BrushStrokeThickness}" x:Name="PART_Path"> <Path.Data> <PathGeometry> <PathFigure x:Name="PART_PathFigure"> <ArcSegment SweepDirection="Clockwise" IsLargeArc="True" x:Name="PART_ArcSegment"> </ArcSegment> </PathFigure> </PathGeometry> </Path.Data> </Path> <Path Stroke="{TemplateBinding Background}" StrokeThickness="{TemplateBinding StrokeThickness}"> <Path.Data> <PathGeometry> <PathFigure x:Name="PART_PathFigureAngle"> <ArcSegment SweepDirection="Clockwise" IsLargeArc="{Binding Path=Angle, Converter={StaticResource isLargeConverter}, RelativeSource={RelativeSource FindAncestor, AncestorType=ProgressBar}}" x:Name="PART_ArcSegmentAngle"> <ArcSegment.Point> <MultiBinding Converter="{StaticResource prConverter}"> <Binding Path="Angle" RelativeSource="{RelativeSource FindAncestor, AncestorType=ProgressBar}"/> <Binding Path="Size" RelativeSource="{RelativeSource FindAncestor, AncestorType=ProgressBar}"/> </MultiBinding> </ArcSegment.Point> </ArcSegment> </PathFigure> </PathGeometry> </Path.Data> </Path> </Canvas> <TextBlock Foreground="{TemplateBinding Foreground}" Text="{Binding Path=Value, StringFormat={}{0}%, RelativeSource={RelativeSource TemplatedParent}}" FontSize="{TemplateBinding FontSize}" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="PART_TextBlock"/> </controls:SmallPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
2) CircularProgressBar.xaml.cs
代码如下:
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; namespace WPFDevelopers.Controls { [TemplatePart(Name = ArcSegmentTemplateName, Type = typeof(ArcSegment))] [TemplatePart(Name = ArcSegmentAngleTemplateName, Type = typeof(ArcSegment))] [TemplatePart(Name = PathFigureTemplateName, Type = typeof(PathFigure))] [TemplatePart(Name = PathFigureAngleTemplateName, Type = typeof(PathFigure))] [TemplatePart(Name = TextBlockTemplateName, Type = typeof(TextBlock))] public class CircularProgressBar : ProgressBar { private const string ArcSegmentTemplateName = "PART_ArcSegment"; private const string ArcSegmentAngleTemplateName = "PART_ArcSegmentAngle"; private const string PathFigureTemplateName = "PART_PathFigure"; private const string PathFigureAngleTemplateName = "PART_PathFigureAngle"; private const string TextBlockTemplateName = "PART_TextBlock"; private ArcSegment _arcSegment, _arcSegmentAngle; private PathFigure _pathFigure, _pathFigureAngle; private TextBlock _textBlock; public static readonly DependencyProperty SizeProperty = DependencyProperty.Register("Size", typeof(Size), typeof(CircularProgressBar), new PropertyMetadata(new Size(50,50))); public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(double), typeof(CircularProgressBar), new PropertyMetadata(0.0)); public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register("StrokeThickness", typeof(double), typeof(CircularProgressBar), new PropertyMetadata(10.0)); public static readonly DependencyProperty BrushStrokeThicknessProperty = DependencyProperty.Register("BrushStrokeThickness", typeof(double), typeof(CircularProgressBar), new PropertyMetadata(1.0)); public CircularProgressBar() { ValueChanged += CircularProgressBar_ValueChanged; } public override void OnApplyTemplate() { base.OnApplyTemplate(); if (Size.Width != Size.Height) { var max = Math.Max(Size.Width, Size.Height); Size = new Size(max, max); } _pathFigure = GetTemplateChild(PathFigureTemplateName) as PathFigure; _pathFigureAngle = GetTemplateChild(PathFigureAngleTemplateName) as PathFigure; _pathFigure.StartPoint = new Point(Size.Width, 0); _pathFigureAngle.StartPoint = new Point(Size.Width, 0); _arcSegment = GetTemplateChild(ArcSegmentTemplateName) as ArcSegment; _arcSegment.Size = Size; _arcSegment.Point = new Point(Size.Width - 0.000872664626, 7.61543361704753E-09); _arcSegmentAngle = GetTemplateChild(ArcSegmentAngleTemplateName) as ArcSegment; _arcSegmentAngle.Size = Size; _textBlock = GetTemplateChild(TextBlockTemplateName) as TextBlock; if (Size.Width < 15) { FontSize = 8; } } public Size Size { get => (Size)GetValue(SizeProperty); set => SetValue(SizeProperty, value); } public double Angle { get => (double)GetValue(AngleProperty); set => SetValue(AngleProperty, value); } public double StrokeThickness { get => (double)GetValue(StrokeThicknessProperty); set => SetValue(StrokeThicknessProperty, value); } public double BrushStrokeThickness { get => (double)GetValue(BrushStrokeThicknessProperty); set => SetValue(BrushStrokeThicknessProperty, value); } private void CircularProgressBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) { var bar = sender as CircularProgressBar; var currentAngle = bar.Angle; var targetAngle = e.NewValue / bar.Maximum * 359.999; var anim = new DoubleAnimation(currentAngle, targetAngle, TimeSpan.FromMilliseconds(500)); bar.BeginAnimation(AngleProperty, anim, HandoffBehavior.SnapshotAndReplace); } } }
3) AngleToPointConverter.cs
代码如下:
using System; using System.Globalization; using System.Windows; using System.Windows.Data; namespace WPFDevelopers.Converts { internal class AngleToPointConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { var angle = (double)values[0]; var size = (Size)values[1]; var radius = (double)size.Height; var piang = angle * Math.PI / 180; var px = Math.Sin(piang) * radius + radius; var py = -Math.Cos(piang) * radius + radius; return new Point(px, py); } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } }
4) AngleToIsLargeConverter.cs
代码如下:
using System; using System.Globalization; using System.Windows.Data; namespace WPFDevelopers.Converts { internal class AngleToIsLargeConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var angle = (double)value; return angle > 180; } public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } }
5) CircularMenuExample.xaml
代码如下:
<UserControl x:Class="WPFDevelopers.Samples.ExampleViews.CircularMenuExample" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews" xmlns:wpfdev="https://github.com/WPFDevelopersOrg/WPFDevelopers" xmlns:controls="clr-namespace:WPFDevelopers.Samples.Controls" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <controls:CodeViewer> <StackPanel Background="Black"> <TextBlock Text="微信公众号:WPFDevelopers" FontSize="40" Foreground="#A9CC32" FontWeight="Bold" Margin="50,10,0,20"/> <wpfdev:CircularMenu ItemsSource="{Binding MenuArray,RelativeSource={RelativeSource AncestorType=local:CircularMenuExample}}" SelectionChanged="CircularMenu_SelectionChanged"/> </StackPanel> <controls:CodeViewer.SourceCodes> <controls:SourceCodeModel CodeSource="/WPFDevelopers.SamplesCode;component/ExampleViews/CircularMenuExample.xaml" CodeType="Xaml"/> <controls:SourceCodeModel CodeSource="/WPFDevelopers.SamplesCode;component/ExampleViews/CircularMenuExample.xaml.cs" CodeType="CSharp"/> </controls:CodeViewer.SourceCodes> </controls:CodeViewer> </UserControl>
效果图
加载全部内容