弧形油表 SimpleGauge

参考[WPF] 使用三种方式实现弧形进度条

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


posted @ 2021-12-13 18:32  wesson2019  阅读(66)  评论(0编辑  收藏  举报