diff --git a/DentistryHandpieces/MainWindow.xaml b/DentistryHandpieces/MainWindow.xaml
index 1850089..056e6f2 100644
--- a/DentistryHandpieces/MainWindow.xaml
+++ b/DentistryHandpieces/MainWindow.xaml
@@ -768,7 +768,8 @@
-
+
+
+
+
+
+
@@ -1120,7 +1158,7 @@
-
+
@@ -1171,7 +1209,7 @@
-
+
diff --git a/DentistryHandpieces/MainWindow.xaml.cs b/DentistryHandpieces/MainWindow.xaml.cs
index 740d74f..4c66884 100644
--- a/DentistryHandpieces/MainWindow.xaml.cs
+++ b/DentistryHandpieces/MainWindow.xaml.cs
@@ -239,4 +239,34 @@ public partial class MainWindow : Window
return button.Tag is string tag
&& Enum.TryParse(tag, ignoreCase: true, out target);
}
+
+ private void TorqueTrendZoomIn_Click(object sender, RoutedEventArgs e)
+ {
+ RealtimeTorqueTrend.ZoomIn();
+ }
+
+ private void TorqueTrendZoomOut_Click(object sender, RoutedEventArgs e)
+ {
+ RealtimeTorqueTrend.ZoomOut();
+ }
+
+ private void TorqueTrendReset_Click(object sender, RoutedEventArgs e)
+ {
+ RealtimeTorqueTrend.ResetView();
+ }
+
+ private void TorqueTrendFullscreen_Click(object sender, RoutedEventArgs e)
+ {
+ if (DataContext is not MainWindowViewModel viewModel)
+ {
+ return;
+ }
+
+ var window = new TorqueTrendWindow(viewModel.TorqueSamples, RealtimeTorqueTrend.GetViewState())
+ {
+ Owner = this
+ };
+ window.ShowDialog();
+ RealtimeTorqueTrend.ApplyViewState(window.ViewState);
+ }
}
diff --git a/DentistryHandpieces/MainWindowViewModel.cs b/DentistryHandpieces/MainWindowViewModel.cs
index b256f42..7f8e49c 100644
--- a/DentistryHandpieces/MainWindowViewModel.cs
+++ b/DentistryHandpieces/MainWindowViewModel.cs
@@ -1241,9 +1241,9 @@ public sealed class MainWindowViewModel : ObservableObject
private void UpdateParameterSummaries()
{
AxialConfigSummaryText =
- $"2号轴:位移极限 {FormatDisplacement(_parameterConfig.AxialDisplacementLimit)} mm;速度 {FormatSpeedSetting(_parameterConfig.AxialSpeed)} mm/min;手动位移 {FormatDisplacement(_parameterConfig.AxialManualDisplacement)} mm;{GetActiveAxialForceSetpointName()} {FormatForce(GetActiveAxialForceSetpoint())} N";
+ $"位移极限 {FormatDisplacement(_parameterConfig.AxialDisplacementLimit)} mm;速度 {FormatSpeedSetting(_parameterConfig.AxialSpeed)} mm/min;手动位移 {FormatDisplacement(_parameterConfig.AxialManualDisplacement)} mm;{GetActiveAxialForceSetpointName()} {FormatForce(GetActiveAxialForceSetpoint())} N";
SpeedTorqueConfigSummaryText =
- $"1号轴:位移极限 {FormatDisplacement(_parameterConfig.SpeedTorqueDisplacementLimit)} mm;速度 {FormatSpeedSetting(_parameterConfig.SpeedTorqueSpeed)} mm/min;手动位移 {FormatDisplacement(_parameterConfig.SpeedTorqueManualDisplacement)} mm;低速停止 {FormatSpeed(_parameterConfig.SpeedStopThreshold)} r/min;负载转速 {FormatSpeedSetting(_parameterConfig.LoadSpeedSetting)} r/min";
+ $"位移极限 {FormatDisplacement(_parameterConfig.SpeedTorqueDisplacementLimit)} mm;速度 {FormatSpeedSetting(_parameterConfig.SpeedTorqueSpeed)} mm/min;手动位移 {FormatDisplacement(_parameterConfig.SpeedTorqueManualDisplacement)} mm;低速停止 {FormatSpeed(_parameterConfig.SpeedStopThreshold)} r/min;负载转速 {FormatSpeedSetting(_parameterConfig.LoadSpeedSetting)} r/min";
}
private void Export()
diff --git a/DentistryHandpieces/TorqueTrendControl.cs b/DentistryHandpieces/TorqueTrendControl.cs
index 46e459e..ff8d183 100644
--- a/DentistryHandpieces/TorqueTrendControl.cs
+++ b/DentistryHandpieces/TorqueTrendControl.cs
@@ -2,14 +2,40 @@ using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.Windows;
+using System.Windows.Input;
using System.Windows.Media;
namespace DentistryHandpieces;
+public readonly record struct TorqueTrendViewState(
+ bool IsManual,
+ double MinTorque,
+ double MaxTorque,
+ double MinSpeed,
+ double MaxSpeed);
+
public sealed class TorqueTrendControl : FrameworkElement
{
private const string TorqueUnit = "mN.m";
private const string SpeedUnit = "r/min";
+ private const double SensorMinTorque = 0;
+ private const double SensorMaxTorque = 100;
+ private const double MinimumTorqueRange = 0.1;
+ private const double MinimumSpeedRange = 1;
+
+ private bool _isManualView;
+ private double _viewMinTorque = SensorMinTorque;
+ private double _viewMaxTorque = SensorMaxTorque;
+ private double _viewMinSpeed;
+ private double _viewMaxSpeed = 1;
+ private Point? _lastDragPoint;
+
+ public TorqueTrendControl()
+ {
+ Focusable = true;
+ IsManipulationEnabled = true;
+ Cursor = Cursors.Cross;
+ }
public static readonly DependencyProperty SamplesProperty =
DependencyProperty.Register(
@@ -24,6 +50,51 @@ public sealed class TorqueTrendControl : FrameworkElement
set => SetValue(SamplesProperty, value);
}
+ public TorqueTrendViewState GetViewState()
+ {
+ return new TorqueTrendViewState(
+ _isManualView,
+ _viewMinTorque,
+ _viewMaxTorque,
+ _viewMinSpeed,
+ _viewMaxSpeed);
+ }
+
+ public void ApplyViewState(TorqueTrendViewState state)
+ {
+ if (!state.IsManual)
+ {
+ ResetView();
+ return;
+ }
+
+ _isManualView = true;
+ _viewMinTorque = state.MinTorque;
+ _viewMaxTorque = state.MaxTorque;
+ _viewMinSpeed = state.MinSpeed;
+ _viewMaxSpeed = state.MaxSpeed;
+ ClampView();
+ InvalidateVisual();
+ }
+
+ public void ZoomIn()
+ {
+ Zoom(0.72, new Point(ActualWidth / 2, ActualHeight / 2));
+ }
+
+ public void ZoomOut()
+ {
+ Zoom(1.38, new Point(ActualWidth / 2, ActualHeight / 2));
+ }
+
+ public void ResetView()
+ {
+ _isManualView = false;
+ _viewMinTorque = SensorMinTorque;
+ _viewMaxTorque = SensorMaxTorque;
+ InvalidateVisual();
+ }
+
protected override Size MeasureOverride(Size availableSize)
{
double width = double.IsInfinity(availableSize.Width) ? 360 : availableSize.Width;
@@ -47,107 +118,107 @@ public sealed class TorqueTrendControl : FrameworkElement
return;
}
- Rect plot = new(46, 14, ActualWidth - 64, ActualHeight - 38);
- var gridPen = new Pen(new SolidColorBrush(Color.FromRgb(219, 229, 238)), 1);
- for (int i = 0; i <= 4; i++)
- {
- double y = plot.Top + plot.Height * i / 4;
- drawingContext.DrawLine(gridPen, new Point(plot.Left, y), new Point(plot.Right, y));
- }
-
- for (int i = 0; i <= 5; i++)
- {
- double x = plot.Left + plot.Width * i / 5;
- drawingContext.DrawLine(gridPen, new Point(x, plot.Top), new Point(x, plot.Bottom));
- }
+ Rect plot = GetPlotRect();
+ DrawGrid(drawingContext, plot);
List samples = ReadSamples();
if (samples.Count == 0)
{
DrawText(drawingContext, "等待转速/扭矩数据", 15, Color.FromRgb(82, 97, 111), new Point(plot.Left + 8, plot.Top + 8));
- DrawText(drawingContext, $"转速 ({SpeedUnit})", 12, Color.FromRgb(82, 97, 111), new Point(6, plot.Top));
- DrawText(drawingContext, $"扭矩 ({TorqueUnit})", 12, Color.FromRgb(82, 97, 111), new Point(plot.Right - 82, plot.Bottom + 5));
+ DrawAxisTitles(drawingContext, plot);
return;
}
- List torqueValues = samples.Select(sample => sample.TorqueMilliNewtonMeters).ToList();
- double minTorque = torqueValues.Min();
- double maxTorque = torqueValues.Max();
- double torqueRange = maxTorque - minTorque;
- if (torqueRange < 0.01)
- {
- torqueRange = 1;
- minTorque -= 0.5;
- maxTorque += 0.5;
- }
- else
- {
- double padding = torqueRange * 0.12;
- minTorque -= padding;
- maxTorque += padding;
- torqueRange = maxTorque - minTorque;
- }
+ (double minTorque, double maxTorque, double minSpeed, double maxSpeed) = GetVisibleRanges(samples);
+ DrawAxisLabels(drawingContext, plot, minTorque, maxTorque, minSpeed, maxSpeed);
- List speedValues = samples.Select(sample => sample.SpeedRpm).ToList();
- double minSpeed = speedValues.Min();
- double maxSpeed = speedValues.Max();
- double speedRange = maxSpeed - minSpeed;
- if (speedRange < 1)
- {
- double padding = Math.Max(Math.Abs(minSpeed) * 0.05, 1);
- minSpeed -= padding;
- maxSpeed += padding;
- speedRange = maxSpeed - minSpeed;
- }
- else
- {
- double padding = speedRange * 0.05;
- minSpeed -= padding;
- maxSpeed += padding;
- speedRange = maxSpeed - minSpeed;
- }
+ var points = samples
+ .Select(sample => ToPlotPoint(sample, plot, minTorque, maxTorque, minSpeed, maxSpeed))
+ .ToList();
- DrawText(drawingContext, maxSpeed.ToString("0", CultureInfo.InvariantCulture), 12, Color.FromRgb(82, 97, 111), new Point(8, plot.Top - 2));
- DrawText(drawingContext, minSpeed.ToString("0", CultureInfo.InvariantCulture), 12, Color.FromRgb(82, 97, 111), new Point(8, plot.Bottom - 14));
- DrawText(drawingContext, minTorque.ToString("0.##", CultureInfo.InvariantCulture), 12, Color.FromRgb(82, 97, 111), new Point(plot.Left, plot.Bottom + 5));
- DrawText(drawingContext, maxTorque.ToString("0.##", CultureInfo.InvariantCulture), 12, Color.FromRgb(82, 97, 111), new Point(plot.Right - 38, plot.Bottom + 5));
- DrawText(drawingContext, $"转速 ({SpeedUnit})", 11, Color.FromRgb(82, 97, 111), new Point(plot.Left + 4, plot.Top + 3));
- DrawText(drawingContext, $"扭矩 ({TorqueUnit})", 11, Color.FromRgb(82, 97, 111), new Point(plot.Right - 82, plot.Bottom - 18));
-
- var geometry = new StreamGeometry();
- using (StreamGeometryContext context = geometry.Open())
- {
- for (int i = 0; i < samples.Count; i++)
- {
- double x = plot.Left + plot.Width * (samples[i].TorqueMilliNewtonMeters - minTorque) / torqueRange;
- double y = plot.Bottom - (samples[i].SpeedRpm - minSpeed) / speedRange * plot.Height;
- Point point = new(x, y);
-
- if (i == 0)
- {
- context.BeginFigure(point, false, false);
- }
- else
- {
- context.LineTo(point, true, false);
- }
- }
- }
-
- geometry.Freeze();
- var linePen = new Pen(new SolidColorBrush(Color.FromRgb(29, 78, 216)), 2.4);
- drawingContext.DrawGeometry(null, linePen, geometry);
+ drawingContext.PushClip(new RectangleGeometry(plot));
+ DrawTrajectory(drawingContext, points);
+ DrawSamplePoints(drawingContext, points);
+ DrawCurrentPoint(drawingContext, points[^1]);
+ drawingContext.Pop();
TorqueSamplePayload current = samples[^1];
- double currentY = plot.Bottom - (current.SpeedRpm - minSpeed) / speedRange * plot.Height;
- var currentPen = new Pen(new SolidColorBrush(Color.FromRgb(15, 118, 110)), 1.2);
- drawingContext.DrawLine(currentPen, new Point(plot.Left, currentY), new Point(plot.Right, currentY));
DrawText(
drawingContext,
$"当前 {current.SpeedRpm:0} {SpeedUnit} / {current.TorqueMilliNewtonMeters:0.##} {TorqueUnit}",
12,
Color.FromRgb(15, 118, 110),
- new Point(plot.Left + 6, Math.Max(plot.Top + 18, currentY - 20)));
+ new Point(plot.Left + 6, plot.Top + 5));
+ }
+
+ protected override void OnMouseWheel(MouseWheelEventArgs e)
+ {
+ base.OnMouseWheel(e);
+ Zoom(e.Delta > 0 ? 0.82 : 1.22, e.GetPosition(this));
+ e.Handled = true;
+ }
+
+ protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
+ {
+ base.OnMouseLeftButtonDown(e);
+ Focus();
+ _lastDragPoint = e.GetPosition(this);
+ CaptureMouse();
+ Cursor = Cursors.Hand;
+ e.Handled = true;
+ }
+
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ base.OnMouseMove(e);
+ if (_lastDragPoint is not Point previous || e.LeftButton != MouseButtonState.Pressed)
+ {
+ return;
+ }
+
+ Point current = e.GetPosition(this);
+ Pan(current.X - previous.X, current.Y - previous.Y);
+ _lastDragPoint = current;
+ e.Handled = true;
+ }
+
+ protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
+ {
+ base.OnMouseLeftButtonUp(e);
+ EndMouseDrag();
+ e.Handled = true;
+ }
+
+ protected override void OnLostMouseCapture(MouseEventArgs e)
+ {
+ base.OnLostMouseCapture(e);
+ EndMouseDrag();
+ }
+
+ protected override void OnManipulationStarting(ManipulationStartingEventArgs e)
+ {
+ base.OnManipulationStarting(e);
+ e.ManipulationContainer = this;
+ e.Mode = ManipulationModes.Scale | ManipulationModes.Translate;
+ e.Handled = true;
+ }
+
+ protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
+ {
+ base.OnManipulationDelta(e);
+ Vector translation = e.DeltaManipulation.Translation;
+ if (Math.Abs(translation.X) > 0.01 || Math.Abs(translation.Y) > 0.01)
+ {
+ Pan(translation.X, translation.Y);
+ }
+
+ double scale = Math.Sqrt(e.DeltaManipulation.Scale.X * e.DeltaManipulation.Scale.Y);
+ if (double.IsFinite(scale) && Math.Abs(scale - 1) > 0.005)
+ {
+ Zoom(1 / scale, e.ManipulationOrigin);
+ }
+
+ e.Handled = true;
}
private static void OnSamplesChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
@@ -171,6 +242,210 @@ public sealed class TorqueTrendControl : FrameworkElement
InvalidateVisual();
}
+ private Rect GetPlotRect()
+ {
+ return new Rect(52, 14, Math.Max(1, ActualWidth - 70), Math.Max(1, ActualHeight - 42));
+ }
+
+ private static void DrawGrid(DrawingContext drawingContext, Rect plot)
+ {
+ var gridPen = new Pen(new SolidColorBrush(Color.FromRgb(219, 229, 238)), 1);
+ for (int i = 0; i <= 4; i++)
+ {
+ double y = plot.Top + plot.Height * i / 4;
+ drawingContext.DrawLine(gridPen, new Point(plot.Left, y), new Point(plot.Right, y));
+ }
+
+ for (int i = 0; i <= 5; i++)
+ {
+ double x = plot.Left + plot.Width * i / 5;
+ drawingContext.DrawLine(gridPen, new Point(x, plot.Top), new Point(x, plot.Bottom));
+ }
+ }
+
+ private (double MinTorque, double MaxTorque, double MinSpeed, double MaxSpeed) GetVisibleRanges(List samples)
+ {
+ if (!_isManualView)
+ {
+ (_viewMinSpeed, _viewMaxSpeed) = GetAutoSpeedRange(samples);
+ }
+
+ return (_viewMinTorque, _viewMaxTorque, _viewMinSpeed, _viewMaxSpeed);
+ }
+
+ private static (double MinSpeed, double MaxSpeed) GetAutoSpeedRange(List samples)
+ {
+ double minSpeed = samples.Min(sample => sample.SpeedRpm);
+ double maxSpeed = samples.Max(sample => sample.SpeedRpm);
+ double range = maxSpeed - minSpeed;
+ double padding = range < MinimumSpeedRange
+ ? Math.Max(Math.Abs(minSpeed) * 0.05, MinimumSpeedRange)
+ : range * 0.08;
+ return (minSpeed - padding, maxSpeed + padding);
+ }
+
+ private static Point ToPlotPoint(
+ TorqueSamplePayload sample,
+ Rect plot,
+ double minTorque,
+ double maxTorque,
+ double minSpeed,
+ double maxSpeed)
+ {
+ double x = plot.Left + plot.Width * (sample.TorqueMilliNewtonMeters - minTorque) / (maxTorque - minTorque);
+ double y = plot.Bottom - (sample.SpeedRpm - minSpeed) / (maxSpeed - minSpeed) * plot.Height;
+ return new Point(x, y);
+ }
+
+ private static void DrawTrajectory(DrawingContext drawingContext, IReadOnlyList points)
+ {
+ var geometry = new StreamGeometry();
+ using (StreamGeometryContext context = geometry.Open())
+ {
+ for (int i = 0; i < points.Count; i++)
+ {
+ if (i == 0)
+ {
+ context.BeginFigure(points[i], false, false);
+ }
+ else
+ {
+ context.LineTo(points[i], true, false);
+ }
+ }
+ }
+
+ geometry.Freeze();
+ var lineBrush = new SolidColorBrush(Color.FromArgb(85, 29, 78, 216));
+ drawingContext.DrawGeometry(null, new Pen(lineBrush, 1.2), geometry);
+ }
+
+ private static void DrawSamplePoints(DrawingContext drawingContext, IReadOnlyList points)
+ {
+ var pointBrush = new SolidColorBrush(Color.FromArgb(175, 29, 78, 216));
+ Point? lastDrawn = null;
+ foreach (Point point in points)
+ {
+ if (lastDrawn is Point previous
+ && Math.Abs(point.X - previous.X) < 1.5
+ && Math.Abs(point.Y - previous.Y) < 1.5)
+ {
+ continue;
+ }
+
+ drawingContext.DrawEllipse(pointBrush, null, point, 2.2, 2.2);
+ lastDrawn = point;
+ }
+ }
+
+ private static void DrawCurrentPoint(DrawingContext drawingContext, Point point)
+ {
+ var brush = new SolidColorBrush(Color.FromRgb(15, 118, 110));
+ drawingContext.DrawEllipse(brush, new Pen(Brushes.White, 1.5), point, 5, 5);
+ }
+
+ private void DrawAxisLabels(
+ DrawingContext drawingContext,
+ Rect plot,
+ double minTorque,
+ double maxTorque,
+ double minSpeed,
+ double maxSpeed)
+ {
+ DrawText(drawingContext, maxSpeed.ToString("0", CultureInfo.InvariantCulture), 11, Color.FromRgb(82, 97, 111), new Point(5, plot.Top - 2));
+ DrawText(drawingContext, minSpeed.ToString("0", CultureInfo.InvariantCulture), 11, Color.FromRgb(82, 97, 111), new Point(5, plot.Bottom - 14));
+ DrawText(drawingContext, minTorque.ToString("0.##", CultureInfo.InvariantCulture), 11, Color.FromRgb(82, 97, 111), new Point(plot.Left, plot.Bottom + 5));
+ DrawText(drawingContext, maxTorque.ToString("0.##", CultureInfo.InvariantCulture), 11, Color.FromRgb(82, 97, 111), new Point(plot.Right - 34, plot.Bottom + 5));
+ DrawAxisTitles(drawingContext, plot);
+ }
+
+ private void DrawAxisTitles(DrawingContext drawingContext, Rect plot)
+ {
+ DrawText(drawingContext, $"转速 ({SpeedUnit})", 11, Color.FromRgb(82, 97, 111), new Point(plot.Left + 4, plot.Top + 22));
+ DrawText(drawingContext, $"扭矩 ({TorqueUnit})", 11, Color.FromRgb(82, 97, 111), new Point(plot.Right - 82, plot.Bottom - 18));
+ }
+
+ private void Zoom(double factor, Point origin)
+ {
+ Rect plot = GetPlotRect();
+ if (plot.Width <= 1 || plot.Height <= 1 || !double.IsFinite(factor) || factor <= 0)
+ {
+ return;
+ }
+
+ EnsureManualView();
+ double originX = Math.Clamp((origin.X - plot.Left) / plot.Width, 0, 1);
+ double originY = Math.Clamp((plot.Bottom - origin.Y) / plot.Height, 0, 1);
+ double torqueAtOrigin = _viewMinTorque + (_viewMaxTorque - _viewMinTorque) * originX;
+ double speedAtOrigin = _viewMinSpeed + (_viewMaxSpeed - _viewMinSpeed) * originY;
+ double torqueRange = Math.Max(MinimumTorqueRange, (_viewMaxTorque - _viewMinTorque) * factor);
+ double speedRange = Math.Max(MinimumSpeedRange, (_viewMaxSpeed - _viewMinSpeed) * factor);
+
+ _viewMinTorque = torqueAtOrigin - torqueRange * originX;
+ _viewMaxTorque = _viewMinTorque + torqueRange;
+ _viewMinSpeed = speedAtOrigin - speedRange * originY;
+ _viewMaxSpeed = _viewMinSpeed + speedRange;
+ ClampView();
+ InvalidateVisual();
+ }
+
+ private void Pan(double deltaX, double deltaY)
+ {
+ Rect plot = GetPlotRect();
+ if (plot.Width <= 1 || plot.Height <= 1)
+ {
+ return;
+ }
+
+ EnsureManualView();
+ double torqueShift = -deltaX / plot.Width * (_viewMaxTorque - _viewMinTorque);
+ double speedShift = deltaY / plot.Height * (_viewMaxSpeed - _viewMinSpeed);
+ _viewMinTorque += torqueShift;
+ _viewMaxTorque += torqueShift;
+ _viewMinSpeed += speedShift;
+ _viewMaxSpeed += speedShift;
+ ClampView();
+ InvalidateVisual();
+ }
+
+ private void EnsureManualView()
+ {
+ if (_isManualView)
+ {
+ return;
+ }
+
+ List samples = ReadSamples();
+ (_viewMinSpeed, _viewMaxSpeed) = samples.Count == 0 ? (0, 1) : GetAutoSpeedRange(samples);
+ _isManualView = true;
+ }
+
+ private void ClampView()
+ {
+ double torqueRange = Math.Clamp(_viewMaxTorque - _viewMinTorque, MinimumTorqueRange, SensorMaxTorque - SensorMinTorque);
+ _viewMinTorque = Math.Clamp(_viewMinTorque, SensorMinTorque, SensorMaxTorque - torqueRange);
+ _viewMaxTorque = _viewMinTorque + torqueRange;
+
+ if (!double.IsFinite(_viewMinSpeed)
+ || !double.IsFinite(_viewMaxSpeed)
+ || _viewMaxSpeed - _viewMinSpeed < MinimumSpeedRange)
+ {
+ _viewMinSpeed = 0;
+ _viewMaxSpeed = MinimumSpeedRange;
+ }
+ }
+
+ private void EndMouseDrag()
+ {
+ _lastDragPoint = null;
+ if (IsMouseCaptured)
+ {
+ ReleaseMouseCapture();
+ }
+
+ Cursor = Cursors.Cross;
+ }
+
private List ReadSamples()
{
if (Samples is null)
@@ -182,16 +457,13 @@ public sealed class TorqueTrendControl : FrameworkElement
foreach (object? sample in Samples)
{
if (sample is TorqueSamplePayload torqueSample
- && !double.IsNaN(torqueSample.ElapsedSeconds)
- && !double.IsInfinity(torqueSample.ElapsedSeconds)
- && !double.IsNaN(torqueSample.SpeedRpm)
- && !double.IsInfinity(torqueSample.SpeedRpm)
- && !double.IsNaN(torqueSample.TorqueMilliNewtonMeters)
- && !double.IsInfinity(torqueSample.TorqueMilliNewtonMeters))
+ && double.IsFinite(torqueSample.ElapsedSeconds)
+ && double.IsFinite(torqueSample.SpeedRpm)
+ && double.IsFinite(torqueSample.TorqueMilliNewtonMeters))
{
values.Add(torqueSample);
}
- else if (sample is double value && !double.IsNaN(value) && !double.IsInfinity(value))
+ else if (sample is double value && double.IsFinite(value))
{
values.Add(new TorqueSamplePayload { ElapsedSeconds = values.Count, SpeedRpm = values.Count, TorqueMilliNewtonMeters = value });
}
diff --git a/DentistryHandpieces/TorqueTrendWindow.cs b/DentistryHandpieces/TorqueTrendWindow.cs
new file mode 100644
index 0000000..68a5a17
--- /dev/null
+++ b/DentistryHandpieces/TorqueTrendWindow.cs
@@ -0,0 +1,76 @@
+using System.Collections;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace DentistryHandpieces;
+
+public sealed class TorqueTrendWindow : Window
+{
+ private readonly TorqueTrendControl _trendControl;
+
+ public TorqueTrendWindow(IEnumerable samples, TorqueTrendViewState initialViewState)
+ {
+ Title = "转速/扭矩实时曲线";
+ WindowState = WindowState.Maximized;
+ WindowStyle = WindowStyle.None;
+ Background = new SolidColorBrush(Color.FromRgb(241, 245, 249));
+ KeyDown += TorqueTrendWindow_KeyDown;
+
+ _trendControl = new TorqueTrendControl
+ {
+ Samples = samples,
+ Margin = new Thickness(18)
+ };
+ _trendControl.ApplyViewState(initialViewState);
+
+ var toolbar = new StackPanel
+ {
+ Orientation = Orientation.Horizontal,
+ HorizontalAlignment = HorizontalAlignment.Right,
+ Margin = new Thickness(18, 12, 18, 0)
+ };
+ toolbar.Children.Add(CreateButton("放大", (_, _) => _trendControl.ZoomIn()));
+ toolbar.Children.Add(CreateButton("缩小", (_, _) => _trendControl.ZoomOut()));
+ toolbar.Children.Add(CreateButton("复位视图", (_, _) => _trendControl.ResetView()));
+ toolbar.Children.Add(CreateButton("退出全屏", (_, _) => Close()));
+
+ var layout = new Grid();
+ layout.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
+ layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
+ Grid.SetRow(toolbar, 0);
+ Grid.SetRow(_trendControl, 1);
+ layout.Children.Add(toolbar);
+ layout.Children.Add(_trendControl);
+ Content = layout;
+ }
+
+ public TorqueTrendViewState ViewState => _trendControl.GetViewState();
+
+ private static Button CreateButton(string text, RoutedEventHandler click)
+ {
+ var button = new Button
+ {
+ Content = text,
+ MinWidth = 100,
+ MinHeight = 40,
+ FontSize = 15,
+ Margin = new Thickness(8, 0, 0, 0),
+ Padding = new Thickness(14, 5, 14, 5),
+ Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)),
+ Foreground = Brushes.White,
+ BorderBrush = new SolidColorBrush(Color.FromRgb(29, 78, 216))
+ };
+ button.Click += click;
+ return button;
+ }
+
+ private void TorqueTrendWindow_KeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Escape)
+ {
+ Close();
+ }
+ }
+}