使用环形进度条显示用量百分比
控件效果如下
控件的关键属性如下:
Background:控制背景圆环的原色。
Stroke:控制进度圆环颜色、以及中间文本颜色。
Value:进度百分比,double类型,进度0~1.
控件前端xaml模板
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFCustomControl.Controls">
<!--圆弧绑定转换,根据控件属性转换圆弧-->
<local:RingProgressArcConverter x:Key="ringProgressArcConverter" />
<!--文本显示转换-->
<local:RingProgressValueConverter x:Key="ringProgressValueConverter" />
<ControlTemplate x:Key="RingProgress_Template" TargetType="local:RingProgress">
<Grid>
<Path StrokeThickness="{TemplateBinding StrokeThickness}" Stroke="{TemplateBinding Background}" StrokeEndLineCap="Round" StrokeStartLineCap="Round"
HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="rootRing" >
<Path.Data>
<MultiBinding Converter="{StaticResource ringProgressArcConverter}">
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType=local:RingProgress}" />
<Binding Path="ActualHeight" RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType=local:RingProgress}" />
<Binding Path="StrokeThickness" RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType=local:RingProgress}" />
</MultiBinding>
</Path.Data>
</Path>
<Path StrokeThickness="{TemplateBinding StrokeThickness}" Stroke="{TemplateBinding Stroke}" StrokeEndLineCap="Round" StrokeStartLineCap="Round"
HorizontalAlignment="Center" VerticalAlignment="Center"
Width="{Binding ElementName=rootRing, Path=ActualWidth}"
Height="{Binding ElementName=rootRing, Path=ActualHeight}">
<Path.Data>
<MultiBinding Converter="{StaticResource ringProgressArcConverter}">
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType=local:RingProgress}" />
<Binding Path="ActualHeight" RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType=local:RingProgress}" />
<Binding Path="StrokeThickness" RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType=local:RingProgress}" />
<Binding Path="Value" RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType=local:RingProgress}" />
</MultiBinding>
</Path.Data>
</Path>
<TextBlock Foreground="{TemplateBinding Stroke}" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"
Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Value, Converter={StaticResource ringProgressValueConverter}}"/>
</Grid>
</ControlTemplate>
<!--设置 RingProgress 的默认样式-->
<Style TargetType="local:RingProgress">
<Setter Property="Template" Value="{StaticResource RingProgress_Template}" />
<Setter Property="Background" Value="#D2D2D2" />
</Style>
</ResourceDictionary>
控件后台代码:
public partial class RingProgress : RangeBase
{
static RingProgress()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(RingProgress), new FrameworkPropertyMetadata(typeof(RingProgress)));
}
#region StrokeThickness 圆环描边宽度
public static readonly DependencyProperty StrokeThicknessProperty =
DependencyProperty.Register("StrokeThickness", typeof(double), typeof(RingProgress), new PropertyMetadata(10d));
public double StrokeThickness
{
get { return (double)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
#endregion
#region Stroke 圆环描边颜色
public static readonly DependencyProperty StrokeProperty =
DependencyProperty.Register("Stroke", typeof(Brush), typeof(RingProgress), new PropertyMetadata(Brushes.Red));
public Brush Stroke
{
get { return (Brush)GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); }
}
#endregion
}
internal class RingProgressArcConverter : IMultiValueConverter
{
// 注意,因为这里使用Path绘制圆环, 所以要把描边宽度大小考虑进去. 所有点的x、y偏移 半个描边宽度
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double value;
if (values[0] is double width
&& values[1] is double height
&& width > 0 && height>0
&& values[2] is double storkeWidth)
{
width -= storkeWidth;
height -= storkeWidth;
value = values.Length == 4 ? System.Convert.ToDouble(values[3]) : 1d;
if (value == 0) return "";
var startAngle = -90d;
var endAngle = Math.Min(value * 360 -90 , 269);
var radius = Math.Min(width, height) * 0.5;
var start = startAngle.AngleToPoint(radius, storkeWidth * 0.5);
var end = endAngle.AngleToPoint(radius, storkeWidth * 0.5);
var dataStr = $"M {start.X},{start.Y} A {radius},{radius} 0 {(endAngle - startAngle >= 180 ? 1 : 0)} 1 {end.X},{end.Y}";
var converter = TypeDescriptor.GetConverter(typeof(Geometry));
return (Geometry)converter.ConvertFrom(dataStr);
}
else
{
return "";
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
internal class RingProgressValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double v)
{
return $"{v * 100}%";
}
else
{
return 0;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
internal static class RingProgressExtension
{
/// <summary>
/// 角度转为弧度
/// </summary>
/// <param name="a"></param>
/// <returns></returns>
public static double AngleToArc(this double a)
{
return Math.PI * a / 180;
}
/// <summary>
/// 角度及半径计算坐标点位置
/// </summary>
/// <param name="a"></param>
/// <param name="radius"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static Point AngleToPoint(this double a, double radius, double offset = 0)
{
return new Point(Math.Cos(a.AngleToArc()) * radius + radius + offset, Math.Sin(a.AngleToArc()) * radius + radius + offset);
}
}
使用示例
<UniformGrid Rows="2" Columns="2">
<c:RingProgress Value="0.1" Margin="5" Stroke="#393D49" Background="#31BDEC" />
<c:RingProgress Value="0.3" Margin="5" Stroke="Red" Background="#C2C2C2"/>
<c:RingProgress Value="0.5" Margin="5" Stroke="Red" Background="#C2C2C2"/>
<c:RingProgress Value="0.7" Margin="5" Stroke="Red" Background="#C2C2C2"/>
</UniformGrid>
有问题的请评论去留言。