兼容win7版应用
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

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;
}
}