弧形油表 SimpleGauge
Nuget引入 Microsoft.Xaml.Behaviors.Wpf
xaml
xmlns:interactivity="http://schemas.microsoft.com/xaml/behaviors"
<Grid Background="{Binding BackgroundStroke, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}">
<!--描边-->
<Ellipse StrokeDashCap="Round" Margin="0"
Stroke="{Binding RimStroke, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
StrokeThickness="4">
<interactivity:Interaction.Behaviors>
<local:EllipseProgressBehavior
EndAngle="{Binding EndAngle, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
Progress="{Binding Maximum, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
StartAngle="{Binding StartAngle, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</interactivity:Interaction.Behaviors>
</Ellipse>
<Ellipse StrokeDashCap="Round" Margin="1"
Stroke="{Binding BackgroundStroke, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
StrokeThickness="2">
<interactivity:Interaction.Behaviors>
<local:EllipseProgressBehavior
EndAngle="{Binding EndAngle, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
Progress="{Binding Maximum, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
StartAngle="{Binding StartAngle, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</interactivity:Interaction.Behaviors>
</Ellipse>
<!--范围-->
<Ellipse Margin="0" StrokeDashCap="Round"
Stroke="{Binding RangeStroke, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
StrokeThickness="4">
<interactivity:Interaction.Behaviors>
<local:EllipseProgressBehavior
EndAngle="{Binding EndAngle, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
Progress="{Binding ProgressValue, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
StartAngle="{Binding StartAngle, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</interactivity:Interaction.Behaviors>
</Ellipse>
</Grid>
cs
using Microsoft.Xaml.Behaviors;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
/// <summary>
/// SimpleGauge.xaml 的交互逻辑
/// </summary>
public partial class SimpleGauge : UserControl
{
public SimpleGauge()
{
InitializeComponent();
}
/// <summary>
/// 起始角度
/// </summary>
public double StartAngle
{
get => (double)GetValue(StartAngleProperty);
set => SetValue(StartAngleProperty, value);
}
public static readonly DependencyProperty StartAngleProperty =
DependencyProperty.Register("StartAngle", typeof(double), typeof(SimpleGauge), new PropertyMetadata(-130d));
/// <summary>
/// 结束角度
/// </summary>
public double EndAngle
{
get => (double)GetValue(EndAngleProperty);
set => SetValue(EndAngleProperty, value);
}
public static readonly DependencyProperty EndAngleProperty =
DependencyProperty.Register("EndAngle", typeof(double), typeof(SimpleGauge), new PropertyMetadata(130d));
/// <summary>
/// 描边颜色
/// </summary>
public Brush RimStroke
{
get => (Brush)GetValue(RimStrokeProperty);
set => SetValue(RimStrokeProperty, value);
}
public static readonly DependencyProperty RimStrokeProperty =
DependencyProperty.Register("RimStroke", typeof(Brush), typeof(SimpleGauge), new PropertyMetadata(default(Brush)));
/// <summary>
/// 背景颜色
/// </summary>
public Brush BackgroundStroke
{
get => (Brush)GetValue(BackgroundStrokeProperty);
set => SetValue(BackgroundStrokeProperty, value);
}
public static readonly DependencyProperty BackgroundStrokeProperty =
DependencyProperty.Register("BackgroundStroke", typeof(Brush), typeof(SimpleGauge), new PropertyMetadata(default(Brush)));
/// <summary>
/// 范围值颜色
/// </summary>
public Brush RangeStroke
{
get => (Brush)GetValue(RangeStrokeProperty);
set => SetValue(RangeStrokeProperty, value);
}
public static readonly DependencyProperty RangeStrokeProperty =
DependencyProperty.Register("RangeStroke", typeof(Brush), typeof(SimpleGauge), new PropertyMetadata(default(Brush)));
/// <summary>
/// 范围最大值
/// </summary>
public double Maximum
{
get => (double)GetValue(MaximumProperty);
set => SetValue(MaximumProperty, value);
}
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(SimpleGauge), new PropertyMetadata(100d));
/// <summary>
/// 范围真值
/// </summary>
public double ProgressValue
{
get => (double)GetValue(ProgressValueProperty);
set => SetValue(ProgressValueProperty, value);
}
public static readonly DependencyProperty ProgressValueProperty =
DependencyProperty.Register("ProgressValue", typeof(double), typeof(SimpleGauge), new PropertyMetadata(50d));
}
public class EllipseProgressBehavior : Behavior<Ellipse>
{
/// <summary>
/// 标识 EndAngle 依赖属性。
/// </summary>
public static readonly DependencyProperty EndAngleProperty =
DependencyProperty.Register(nameof(EndAngle), typeof(double), typeof(EllipseProgressBehavior), new PropertyMetadata(default(double), OnEndAngleChanged));
/// <summary>
/// 标识 Progress 依赖属性。
/// </summary>
public static readonly DependencyProperty ProgressProperty =
DependencyProperty.Register("Progress", typeof(double), typeof(EllipseProgressBehavior), new PropertyMetadata(0d, OnProgressChanged));
/// <summary>
/// 标识 StartAngle 依赖属性。
/// </summary>
public static readonly DependencyProperty StartAngleProperty =
DependencyProperty.Register(nameof(StartAngle), typeof(double), typeof(EllipseProgressBehavior), new PropertyMetadata(default(double), OnStartAngleChanged));
private double _normalizedMinAngle;
private double _normalizedMaxAngle;
/// <summary>
/// 获取或设置EndAngle的值
/// </summary>
public double EndAngle
{
get => (double)GetValue(EndAngleProperty);
set => SetValue(EndAngleProperty, value);
}
/// <summary>
/// 获取或设置Progress的值
/// </summary>
public double Progress
{
get => (double)GetValue(ProgressProperty);
set => SetValue(ProgressProperty, value);
}
/// <summary>
/// 获取或设置StartAngle的值
/// </summary>
public double StartAngle
{
get => (double)GetValue(StartAngleProperty);
set => SetValue(StartAngleProperty, value);
}
protected virtual double GetTotalLength()
{
return AssociatedObject == null || AssociatedObject.ActualHeight == 0
? 0
: (AssociatedObject.ActualHeight - AssociatedObject.StrokeThickness) * Math.PI * (_normalizedMaxAngle - _normalizedMinAngle) / 360;
}
protected override void OnAttached()
{
base.OnAttached();
UpdateAngle();
UpdateStrokeDashArray();
AssociatedObject.SizeChanged += (s, e) => { UpdateStrokeDashArray(); };
}
/// <summary>
/// EndAngle 属性更改时调用此方法。
/// </summary>
/// <param name="oldValue">EndAngle 属性的旧值。</param>
/// <param name="newValue">EndAngle 属性的新值。</param>
protected virtual void OnEndAngleChanged(double oldValue, double newValue)
{
UpdateAngle();
UpdateStrokeDashArray();
}
protected virtual void OnProgressChanged(double oldValue, double newValue)
{
UpdateStrokeDashArray();
}
/// <summary>
/// StartAngle 属性更改时调用此方法。
/// </summary>
/// <param name="oldValue">StartAngle 属性的旧值。</param>
/// <param name="newValue">StartAngle 属性的新值。</param>
protected virtual void OnStartAngleChanged(double oldValue, double newValue)
{
UpdateAngle();
UpdateStrokeDashArray();
}
private static void OnEndAngleChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var oldValue = (double)args.OldValue;
var newValue = (double)args.NewValue;
if (oldValue == newValue)
{
return;
}
var target = obj as EllipseProgressBehavior;
target?.OnEndAngleChanged(oldValue, newValue);
}
private static void OnProgressChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var target = obj as EllipseProgressBehavior;
double oldValue = (double)args.OldValue;
double newValue = (double)args.NewValue;
if (oldValue != newValue)
{
target.OnProgressChanged(oldValue, newValue);
}
}
private static void OnStartAngleChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var oldValue = (double)args.OldValue;
var newValue = (double)args.NewValue;
if (oldValue == newValue)
{
return;
}
var target = obj as EllipseProgressBehavior;
target?.OnStartAngleChanged(oldValue, newValue);
}
private void UpdateStrokeDashArray()
{
if (AssociatedObject == null || AssociatedObject.StrokeThickness == 0)
{
return;
}
var totalLength = GetTotalLength();
if (totalLength == 0)
{
return;
}
totalLength /= AssociatedObject.StrokeThickness;
var progressLenth = Progress * totalLength / 100;
var result = new DoubleCollection { progressLenth, double.MaxValue };
AssociatedObject.StrokeDashArray = result;
}
private void UpdateAngle()
{
UpdateNormalizedAngles();
if (AssociatedObject == null)
{
return;
}
AssociatedObject.RenderTransformOrigin = new Point(0.5, 0.5);
if (AssociatedObject.RenderTransform is RotateTransform transform)
{
transform.Angle = _normalizedMinAngle - 90;
}
else
{
AssociatedObject.RenderTransform = new RotateTransform { Angle = _normalizedMinAngle - 90 };
}
}
private double Mod(double number, double divider)
{
var result = number % divider;
result = result < 0 ? result + divider : result;
return result;
}
private void UpdateNormalizedAngles()
{
var result = Mod(StartAngle, 360);
if (result >= 180)
{
result -= 360;
}
_normalizedMinAngle = result;
result = Mod(EndAngle, 360);
if (result < 180)
{
result += 360;
}
if (result > _normalizedMinAngle + 360)
{
result -= 360;
}
_normalizedMaxAngle = result;
}
}
demo
<wesson:SimpleGauge Width="132" Height="132" Margin="1"
BackgroundStroke="White"
RimStroke="Gray"
RangeStroke="Red"
ProgressValue="50"/>
prism