using AIK.Models.GuageModels; using AIK.Service.IService; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.Metrics; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Threading; namespace AIK.ViewModels.CommonViewModel { public partial class SemiCircleGaugeViewModel : ObservableObject { private readonly DispatcherTimer _timer; private readonly Random _random = new Random(); private GaugeModel _circleGauge; public GaugeModel CircleGauge { get => _circleGauge; set => SetProperty(ref _circleGauge, value); } // 指针相关属性 [ObservableProperty] private double needleAngle = 180; // 默认指向左侧0位置 [ObservableProperty] private PointCollection needlePoints; // 弧线几何数据 [ObservableProperty] private Geometry outerArcGeometry; [ObservableProperty] private Geometry progressArcGeometry; // 仪表盘参数 public double CenterX { get; } = 200; public double CenterY { get; } = 200; public double Radius { get; } = 150; private const double StartAngle = 180; // 左侧0位置 private const double EndAngle = 0; // 右侧1000位置 private const double MinAngle = -180; // 最小角度(右侧) private const double MaxAngle = 0; // 最大角度(左侧) // 刻度点集合 public ObservableCollection MajorScalePoints { get; } = new ObservableCollection(); public ObservableCollection MinorScalePoints { get; } = new ObservableCollection(); public ObservableCollection ScaleLabels { get; } = new ObservableCollection(); public SemiCircleGaugeViewModel() { CircleGauge = new GaugeModel { Value = 500, MinValue = 0, MaxValue = 1000, Unit = "Hz", Title = "频率" }; CreateNeedleShape(); CalculateArcGeometry(); DrawScale(); _timer = new DispatcherTimer(); _timer.Interval = TimeSpan.FromSeconds(1); _timer.Tick += OnTimerTick; _timer.Start(); // 监听 CircleGauge.Value 的变化 CircleGauge.PropertyChanged += (s, e) => { if (e.PropertyName == nameof(CircleGauge.Value)) { UpdateNeedle(); //UpdateProgressArc(); } }; } private void OnTimerTick(object? sender, EventArgs e) { UpdateValue(); } [RelayCommand] private void UpdateValue() { var change = (_random.NextDouble() - 0.5) * 100; var newValue = CircleGauge.Value + change + 0.02; // 限制数值在有效范围内 newValue = Math.Max(CircleGauge.MinValue, Math.Min(newValue, CircleGauge.MaxValue)); if (CircleGauge.Value - newValue > 0.1) { CircleGauge.Value = Math.Round(newValue, 2); } } private void UpdateNeedle() { if (CircleGauge == null) return; double normalizedValue = (CircleGauge.Value - CircleGauge.MinValue) / (CircleGauge.MaxValue - CircleGauge.MinValue); // 计算角度:从180°(左侧0)到0°(右侧1000) // 现实生活中 90° 是向上,其实在 WPF 渲染中,90° 是向下!坑!!! double newAngle = -180 + (normalizedValue * 180); // 严格限制角度在0-180度范围内 newAngle = Math.Max(MinAngle, Math.Min(newAngle, MaxAngle)); // 只有当角度真正变化时才更新 if (Math.Abs(NeedleAngle - newAngle) > 0.1) { NeedleAngle = newAngle; OnPropertyChanged(nameof(NeedleAngle)); // 调试输出 //System.Diagnostics.Debug.WriteLine($"数值: {CircleGauge.Value}, 标准化: {normalizedValue:F2}, 角度: {NeedleAngle:F1}°"); } } private void UpdateProgressArc() { if (CircleGauge == null) return; double normalizedValue = (CircleGauge.Value - CircleGauge.MinValue) / (CircleGauge.MaxValue - CircleGauge.MinValue); // 使用与指针相同的角度计算 double progressAngle = StartAngle - (normalizedValue * 180); progressAngle = Math.Max(MinAngle, Math.Min(progressAngle, MaxAngle)); double progressRadians = progressAngle * Math.PI / 180; double progressX = CenterX + Radius * Math.Cos(progressRadians); double progressY = CenterY - Radius * Math.Sin(progressRadians); var progressGeometry = new PathGeometry(); var progressFigure = new PathFigure { StartPoint = new Point(CenterX - Radius, CenterY) // 起点:左侧0位置 }; progressFigure.Segments.Add(new ArcSegment { Point = new Point(progressX, progressY), Size = new Size(Radius, Radius), SweepDirection = SweepDirection.Clockwise, IsLargeArc = normalizedValue > 0.5 }); progressGeometry.Figures.Add(progressFigure); ProgressArcGeometry = progressGeometry; } private void CalculateArcGeometry() { var outerGeometry = new PathGeometry(); var outerFigure = new PathFigure { StartPoint = new Point(CenterX - Radius, CenterY) // 起点:左侧0位置 }; outerFigure.Segments.Add(new ArcSegment { Point = new Point(CenterX + Radius, CenterY), // 终点:右侧1000位置 Size = new Size(Radius, Radius), SweepDirection = SweepDirection.Clockwise, IsLargeArc = false }); outerGeometry.Figures.Add(outerFigure); OuterArcGeometry = outerGeometry; UpdateProgressArc(); } private void CreateNeedleShape() { // 钟表指针形状 - 确保从圆心出发 // 让指针默认朝右(WPF 0° 方向) var points = new PointCollection { new Point(CenterX, CenterY), // 圆心 new Point(CenterX - 8, CenterY - 3), // 尾部上 new Point(CenterX + 110, CenterY), // 尖端向右! new Point(CenterX - 8, CenterY + 3), // 尾部下 }; NeedlePoints = points; } private void DrawScale() { MajorScalePoints.Clear(); MinorScalePoints.Clear(); ScaleLabels.Clear(); // 大刻度:0, 200, 400, 600, 800, 1000 int majorScaleCount = 6; for (int i = 0; i < majorScaleCount; i++) { double value = i * 200; double normalizedValue = value / 1000.0; double angle = StartAngle - (normalizedValue * 180); double radians = angle * Math.PI / 180; // 大刻度线 double innerRadius = 130; double outerRadius = 150; double x1 = CenterX + innerRadius * Math.Cos(radians); double y1 = CenterY - innerRadius * Math.Sin(radians); double x2 = CenterX + outerRadius * Math.Cos(radians); double y2 = CenterY - outerRadius * Math.Sin(radians); MajorScalePoints.Add(new ScalePoint { X1 = x1, Y1 = y1, X2 = x2, Y2 = y2 }); // 大刻度标签 double labelRadius = 120; double labelX = CenterX + labelRadius * Math.Cos(radians); double labelY = CenterY - labelRadius * Math.Sin(radians); string labelText = value.ToString("F0"); double offsetX; switch (labelText) { case "0": offsetX = -8; break; case "1000": offsetX = -22; break; default: offsetX = -15; break; } double offsetY = -7; ScaleLabels.Add(new ScaleLabel { Text = labelText, X = labelX + offsetX, Y = labelY + offsetY, Angle = 0 }); } // 小刻度:在每个200区间内添加6个小刻度 int minorScalesPerSegment = 6; for (int i = 0; i < majorScaleCount - 1; i++) { for (int j = 1; j < minorScalesPerSegment; j++) { double minorValue = i * 200 + (j * (200.0 / minorScalesPerSegment)); double normalizedValue = minorValue / 1000.0; double angle = StartAngle - (normalizedValue * 180); double radians = angle * Math.PI / 180; double innerRadius = 140; double outerRadius = 150; double x1 = CenterX + innerRadius * Math.Cos(radians); double y1 = CenterY - innerRadius * Math.Sin(radians); double x2 = CenterX + outerRadius * Math.Cos(radians); double y2 = CenterY - outerRadius * Math.Sin(radians); MinorScalePoints.Add(new ScalePoint { X1 = x1, Y1 = y1, X2 = x2, Y2 = y2 }); } } } // 测试方法:手动设置指针位置 [RelayCommand] private void SetToMin() { CircleGauge.Value = 0; // 指向左侧180° } [RelayCommand] private void SetToMax() { CircleGauge.Value = 1000; // 指向右侧0° } [RelayCommand] private void SetToMiddle() { CircleGauge.Value = 500; // 指向顶部90° } } public class ScalePoint { public double X1 { get; set; } public double Y1 { get; set; } public double X2 { get; set; } public double Y2 { get; set; } } public partial class ScaleLabel : ObservableObject { [ObservableProperty] private string text; [ObservableProperty] private double x; [ObservableProperty] private double y; [ObservableProperty] private double angle; } }