根据网上的资源更新一个仪表盘。
去除依赖Arc,根据ArcSegment画圆弧。不多说,上效果。
还有些地方没有完善,有需要的小伙伴自行下载完善。
Dashboard.cs代码
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace AppWpfControls;
/// <summary>
/// 仪表盘=》Dashboard
/// </summary>
public class Dashboard : Control
{
//public const string ArcName = "arckd";
//public const string ValueTxtName = "valueTxt";
/// <summary>
///
/// </summary>
public const string CanvasName = "canvasPlate";
/// <summary>
///
/// </summary>
public const string ZZRotateName = "rtPointer";
//private static double StartAngle = -135.5;
static Dashboard()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Dashboard), new FrameworkPropertyMetadata(typeof(Dashboard)));
}
//Arc arckd;
//TextBlock valueTxt;
Canvas canvasPlate;
RotateTransform rtPointer;
/// <summary>
///
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//arckd ??= GetTemplateChild(ArcName) as Arc;
//valueTxt ??= GetTemplateChild(ValueTxtName) as TextBlock;
canvasPlate ??= GetTemplateChild(CanvasName) as Canvas;
rtPointer ??= GetTemplateChild(ZZRotateName) as RotateTransform;
DrawScale();
DrawAngle();
//arckd.EndAngle = StartAngle + (-StartAngle * 2d) * ((double)Value / Maximum);
//valueTxt.Text = Value.ToString();
}
#region 值、最大值、最小值
/// <summary>
/// 值
/// </summary>
public double Value
{
get { return (double)GetValue(ValueProperty); }
set
{
SetValue(ValueProperty, value);
}
}
/// <summary>
/// 值
/// </summary>
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(Dashboard),
new PropertyMetadata(default(double), new PropertyChangedCallback(OnValuePropertyChanged)));
/// <summary>
/// 值单位
/// </summary>
public string ValueUnit
{
get { return (string)GetValue(ValueUnitProperty); }
set
{
SetValue(ValueUnitProperty, value);
}
}
/// <summary>
/// 值单位
/// </summary>
public static readonly DependencyProperty ValueUnitProperty =
DependencyProperty.Register("ValueUnit", typeof(string), typeof(Dashboard),
new PropertyMetadata(default(string)));
/// <summary>
/// 最小值
/// </summary>
public double Minimum
{
get { return (double)GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
/// <summary>
/// 最小值
/// </summary>
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum", typeof(double), typeof(Dashboard),
new PropertyMetadata(double.NaN, new PropertyChangedCallback(OnPropertyChanged)));
/// <summary>
/// 最大值
/// </summary>
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
/// <summary>
/// 最大值
/// </summary>
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(Dashboard),
new PropertyMetadata(double.NaN, new PropertyChangedCallback(OnPropertyChanged)));
#endregion 值、最大值、最小值
/// <summary>
/// 框背景颜色
/// </summary>
public Brush PlateBackground
{
get { return (Brush)GetValue(PlateBackgroundProperty); }
set { SetValue(PlateBackgroundProperty, value); }
}
/// <summary>
/// 框背景颜色
/// </summary>
public static readonly DependencyProperty PlateBackgroundProperty =
DependencyProperty.Register("PlateBackground", typeof(Brush), typeof(Dashboard), null);
/// <summary>
/// 框边框颜色
/// </summary>
public Brush PlateBorderBrush
{
get { return (Brush)GetValue(PlateBorderBrushProperty); }
set { SetValue(PlateBorderBrushProperty, value); }
}
/// <summary>
/// 框边框颜色
/// </summary>
public static readonly DependencyProperty PlateBorderBrushProperty =
DependencyProperty.Register("PlateBorderBrush", typeof(Brush), typeof(Dashboard), null);
/// <summary>
/// 框边框大小
/// </summary>
public Thickness PlateBorderThickness
{
get { return (Thickness)GetValue(PlateBorderThicknessProperty); }
set { SetValue(PlateBorderThicknessProperty, value); }
}
/// <summary>
/// 框边框大小
/// </summary>
public static readonly DependencyProperty PlateBorderThicknessProperty =
DependencyProperty.Register("PlateBorderThickness", typeof(Thickness), typeof(Dashboard), null);
/// <summary>
///
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
public static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as Dashboard).DrawScale();
}
/// <summary>
///
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
public static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Dashboard dashboard = d as Dashboard;
dashboard.DrawAngle();
//if (dashboard.arckd == null)
// return;
//dashboard.arckd.EndAngle = StartAngle + (-StartAngle * 2d) * ((double)dashboard.Value / dashboard.Maximum);
//dashboard.valueTxt.Text = dashboard.Value.ToString();
}
/// <summary>
/// 画表盘的刻度
/// </summary>
private void DrawScale()
{
if (this.canvasPlate == null) return;
this.canvasPlate.Children.Clear();
for (double i = 0; i <= this.Maximum - this.Minimum; i++)
{
//添加刻度线
Line lineScale = new();
//画笔颜色
Color color = (Color)ColorConverter.ConvertFromString("#6c6974");
if (i % 10 == 0)
{
//注意Math.Cos和Math.Sin的参数是弧度,记得将角度转为弧度制
lineScale.X1 = 200 - 170 * Math.Cos(i * (270 / (this.Maximum - this.Minimum)) * Math.PI / 180);
lineScale.Y1 = 200 - 170 * Math.Sin(i * (270 / (this.Maximum - this.Minimum)) * Math.PI / 180);
//lineScale.Stroke = new SolidColorBrush(Colors.White);
lineScale.Stroke = new SolidColorBrush(color);
lineScale.StrokeThickness = 2;
//添加刻度值
TextBlock txtScale = new()
{
Text = (i + this.Minimum).ToString(),
Width = 34,
TextAlignment = TextAlignment.Center,
//Foreground = new SolidColorBrush(Colors.White);
//Color color = (Color)ColorConverter.ConvertFromString("#6c6974");
Foreground = new SolidColorBrush(color),
RenderTransform = new RotateTransform() { Angle = 45, CenterX = 17, CenterY = 8 },
FontSize = 18
};
Canvas.SetLeft(txtScale, 200 - 155 * Math.Cos(i * (270 / (this.Maximum - this.Minimum)) * Math.PI / 180) - 17);
Canvas.SetTop(txtScale, 200 - 155 * Math.Sin(i * (270 / (this.Maximum - this.Minimum)) * Math.PI / 180) - 10);
this.canvasPlate.Children.Add(txtScale);
}
else
{
lineScale.X1 = 200 - 180 * Math.Cos(i * (270 / (this.Maximum - this.Minimum)) * Math.PI / 180);
lineScale.Y1 = 200 - 180 * Math.Sin(i * (270 / (this.Maximum - this.Minimum)) * Math.PI / 180);
//lineScale.Stroke = new SolidColorBrush(Colors.White);
lineScale.Stroke = new SolidColorBrush(color);
lineScale.StrokeThickness = 1;
lineScale.Opacity = 0.5;
}
lineScale.X2 = 200 - 190 * Math.Cos(i * (270 / (this.Maximum - this.Minimum)) * Math.PI / 180);
lineScale.Y2 = 200 - 190 * Math.Sin(i * (270 / (this.Maximum - this.Minimum)) * Math.PI / 180);
this.canvasPlate.Children.Add(lineScale);
}
}
private void DrawAngle()
{
if (rtPointer == null)
return;
double step = 270.0 / (this.Maximum - this.Minimum);
double reject = 0;
if (this.Minimum <0)
{
reject = step * (-this.Minimum);
}
else if(this.Minimum >= 0)
{
reject = step * (-this.Minimum);
}
DoubleAnimation da = new(this.Value * step+ reject - 45, new Duration(TimeSpan.FromMilliseconds(200)));
this.rtPointer.BeginAnimation(RotateTransform.AngleProperty, da);
}
}
Xaml样式
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:control="clr-namespace:AppWpfControls">
<Style TargetType="{x:Type control:Dashboard}">
<Setter Property="Value" Value="0"/>
<Setter Property="Maximum" Value="120"/>
<Setter Property="Minimum" Value="0"/>
<Setter Property="PlateBackground" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type control:Dashboard}">
<Grid >
<Viewbox>
<Border CornerRadius="220" ClipToBounds="True"
BorderBrush="{TemplateBinding PlateBorderBrush}"
BorderThickness="{TemplateBinding PlateBorderThickness}"
Background="{TemplateBinding PlateBackground}">
<Grid>
<!-- Start 刻度盘 -->
<Canvas Name="canvasPlate" Width="400" Height="400" Background="Transparent" Margin="14">
<Canvas.RenderTransform>
<RotateTransform Angle="-45" CenterX="200" CenterY="200"/>
</Canvas.RenderTransform>
</Canvas>
<!-- End 刻度盘 -->
<!-- Start 刻度盘当前值对应的圆弧 -->
<Path Stroke="#45246B" Opacity="0.5"
StrokeThickness="20">
<!--StrokeStartLineCap="Round"
StrokeEndLineCap="Round"-->
<Path.Data>
<PathGeometry>
<PathFigure IsClosed="False" StartPoint="73,358">
<ArcSegment IsLargeArc="True"
Point="355,358"
Size="201,201"
SweepDirection="Clockwise" />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<!-- End 刻度盘当前值对应的圆弧 -->
<!-- Start 指针 -->
<!--<Path Name="pointer" Data="M0,195 0,205 15,200 0,195" RenderTransformOrigin="0.5,0.5" Margin="14">-->
<Path Name="pointer" Data="M200 205,40 200,200 195,200 205" RenderTransformOrigin="0.5,0.5" Margin="14">
<Path.Fill>
<RadialGradientBrush RadiusX="1" RadiusY="1">
<GradientStop Color="#00C9FF" Offset="0.3"/>
<GradientStop Color="#CE97EF" Offset="0.4"/>
<GradientStop Color="#091851" Offset="1"/>
</RadialGradientBrush>
</Path.Fill>
<Path.RenderTransform>
<RotateTransform x:Name="rtPointer" Angle="-45"/>
</Path.RenderTransform>
</Path>
<!-- End 指针 -->
<!-- Start 圆心 -->
<Border Width="50" Height="50" CornerRadius="40" Margin="14">
<Border.Background>
<RadialGradientBrush RadiusX="1" RadiusY="1">
<GradientStop Color="#45246B" Offset="0.2"/>
<GradientStop Color="#2E1C5C" Offset="0.3"/>
<GradientStop Color="#253192" Offset="0.5"/>
</RadialGradientBrush>
</Border.Background>
<!--<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock x:Name="valueTxt" FontSize="30" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Text="km/h" FontSize="12" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</StackPanel>-->
</Border>
<!-- End 圆心 -->
<Grid Width="150" Height="60" Margin="0,0,0,100" VerticalAlignment="Bottom" HorizontalAlignment="Center">
<StackPanel HorizontalAlignment="Center" Orientation="Vertical">
<!--<TextBlock Margin="0,4" Text="DOWNLOAD" />-->
<TextBlock Margin="0,4" HorizontalAlignment="Center" FontSize="26" Opacity="0.8"
Text="{Binding Value,RelativeSource={RelativeSource AncestorType=control:Dashboard}}" Foreground="White" />
<TextBlock Margin="0,4" HorizontalAlignment="Center"
Foreground="#6c6974"
Text="{Binding ValueUnit,RelativeSource={RelativeSource AncestorType=control:Dashboard}}" />
<!--<Border Margin="0,10,0,0" BorderBrush="#929093" BorderThickness="1" Height="25" CornerRadius="12" Padding="0,3">
<TextBlock HorizontalAlignment="Center" Text="RETRY" VerticalAlignment="Center" />
</Border>-->
</StackPanel>
</Grid>
</Grid>
</Border>
</Viewbox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="DashboardArc" TargetType="{x:Type control:Dashboard}">
<Setter Property="Value" Value="0"/>
<Setter Property="Maximum" Value="120"/>
<Setter Property="Minimum" Value="0"/>
<Setter Property="PlateBackground" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type control:Dashboard}">
<Grid >
<Viewbox>
<Border CornerRadius="220" ClipToBounds="True"
BorderBrush="{TemplateBinding PlateBorderBrush}"
BorderThickness="{TemplateBinding PlateBorderThickness}"
Background="{TemplateBinding PlateBackground}">
<Grid>
<!-- Start 刻度盘 -->
<Canvas Name="canvasPlate" Width="400" Height="400" Background="Transparent" Margin="14">
<Canvas.RenderTransform>
<RotateTransform Angle="-45" CenterX="200" CenterY="200"/>
</Canvas.RenderTransform>
</Canvas>
<!-- End 刻度盘 -->
<!-- Start 刻度盘当前值对应的圆弧 -->
<Path Stroke="#45246B" Opacity="0.5"
StrokeThickness="20">
<!--StrokeStartLineCap="Round"
StrokeEndLineCap="Round"-->
<Path.Data>
<PathGeometry>
<PathFigure IsClosed="False" StartPoint="73,358">
<ArcSegment IsLargeArc="True"
Point="355,358"
Size="201,201"
SweepDirection="Clockwise" />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<!-- End 刻度盘当前值对应的圆弧 -->
<!-- Start 指针 -->
<!--<Path Name="pointer" Data="M200 205,40 200,200 195,200 205" RenderTransformOrigin="0.5,0.5" Margin="14">-->
<Path Name="pointer" Data="M-7,194 -8,197 -9,200 -8,203 -7,206 15,200 -7,194" RenderTransformOrigin="0.5,0.5" Margin="14">
<Path.Fill>
<RadialGradientBrush RadiusX="1" RadiusY="1">
<GradientStop Color="#00C9FF" Offset="0.3"/>
<GradientStop Color="#CE97EF" Offset="0.4"/>
<GradientStop Color="#091851" Offset="1"/>
</RadialGradientBrush>
</Path.Fill>
<Path.RenderTransform>
<RotateTransform x:Name="rtPointer" Angle="-45"/>
</Path.RenderTransform>
</Path>
<!-- End 指针 -->
<Grid Width="150" Height="130" VerticalAlignment="Center" HorizontalAlignment="Center">
<StackPanel HorizontalAlignment="Center" Orientation="Vertical">
<TextBlock Margin="0,4" Text="DOWNLOAD" FontSize="16" Foreground="#6c6974" />
<TextBlock Margin="0,4" HorizontalAlignment="Center" FontSize="26" Opacity="0.6"
Text="{Binding Value,RelativeSource={RelativeSource AncestorType=control:Dashboard}}" Foreground="White" />
<TextBlock Margin="0,4" HorizontalAlignment="Center"
Foreground="#6c6974"
Text="{Binding ValueUnit,RelativeSource={RelativeSource AncestorType=control:Dashboard}}" />
<Border Margin="0,10,0,0" BorderBrush="#6c6974" BorderThickness="1" Height="25" CornerRadius="12" Padding="0,3">
<TextBlock HorizontalAlignment="Center" Foreground="#6c6974" Text="RETRY" VerticalAlignment="Center" />
</Border>
</StackPanel>
</Grid>
</Grid>
</Border>
</Viewbox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>