You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
339 lines
11 KiB
339 lines
11 KiB
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<ScalePoint> MajorScalePoints { get; } = new ObservableCollection<ScalePoint>();
|
|
public ObservableCollection<ScalePoint> MinorScalePoints { get; } = new ObservableCollection<ScalePoint>();
|
|
public ObservableCollection<ScaleLabel> ScaleLabels { get; } = new ObservableCollection<ScaleLabel>();
|
|
|
|
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;
|
|
}
|
|
}
|
|
|