MeasureOverride 和 ArrangeOverride

本文深入解析了FrameworkElement中的MeasureOverride和ArrangeOverride方法,详细讲解了如何在派生类中重写这些方法以计算子元素在布局中所需的大小,并为父元素定位子元素并确定大小。通过实例代码演示了具体实现步骤。

FrameworkElement.MeasureOverride方法实现当在派生类中重写时,测量子元素在布局中所需的大小,然后确定 FrameworkElement 派生类的大小。

FrameworkElement.MeasureOverride Method

函数原型:

protected virtual Size MeasureOverride (Size availableSize)

假设A为父element,B为子elements集合中的一个元素。通过在FrameworkElement的子类中override这个方法来计算B所需要的空间。

参数:A提供的可用空间。

返回值:通过计算后确定的A所需的空间。

在override的MeasureOverride方法中要做以下3步:

  1. 对每一个B调用Measure方法。
  2. Measure方法调用完后,B的期望大小就保存在DesiredSize中了。
  3. 计算所有的DesiredSize的和,作为总的期望空间返回。

FrameworkElement.ArrangeOverride 方法实现在派生类中重写时,为 FrameworkElement 派生类定位子元素并确定大小。

FrameworkElement.ArrangeOverride Method

函数原型:

protected virtual Size ArrangeOverride (

               Size finalSize

)

参数: A所占的空间

返回值:A实际所用到的空间

通过在子类中override这个方法,实现为A中的所有子元素定位的目的。

例如:
public class CustomControl1 : Panel {
/// <summary>
/// 先测量需要多大尺寸,做个申报准备
/// </summary>
/// <param name="constraint">限定的尺寸,比如,规定了width和height</param>
/// <returns></returns>
protected override Size MeasureOverride(Size constraint){
//定义预期的宽度和高度
double height = 0, width = 0;
UIElement element;
//遍历每个元素,计算所需的总尺寸
for (int i = 0; i < Children.Count; i++){
element = Children[i];
//按照限定的尺寸测量一下自己,拿镜子找着自己
element.Measure(constraint);
if (height < element.DesiredSize.Height)
height = element.DesiredSize.Height;
width += element.DesiredSize.Width;
}
//申报,需要这个尺寸
return new Size(width, height);
}

/// <summary>
/// 排列每个元素
/// </summary>
/// <param name="arrangeBounds">测量的尺寸</param>
/// <returns></returns>
protected override Size ArrangeOverride(Size arrangeBounds){
double currentX = 100;
UIElement element;
for (int i = 0; i < Children.Count; i++){
element = Children[i];
//排列每个元素
Children[i].Arrange(new Rect(currentX, 0, element.DesiredSize.Width, element.DesiredSize.Height));
currentX += element.DesiredSize.Width;
}
return arrangeBounds;
}
}


public class ColorBorder : Control { // 定义四个方向的边框画刷依赖属性 public static readonly StyledProperty<IBrush> LeftBorderBrushProperty = AvaloniaProperty.Register<ColorBorder, IBrush>(nameof(LeftBorderBrush)); public IBrush LeftBorderBrush { get => GetValue(LeftBorderBrushProperty); set => SetValue(LeftBorderBrushProperty, value); } // 同样定义TopBorderBrush, RightBorderBrush, BottomBorderBrush public static readonly StyledProperty<IBrush> TopBorderBrushProperty = AvaloniaProperty.Register<ColorBorder, IBrush>(nameof(TopBorderBrush)); public IBrush TopBorderBrush { get => GetValue(TopBorderBrushProperty); set => SetValue(TopBorderBrushProperty, value); } public static readonly StyledProperty<IBrush> RightBorderBrushProperty = AvaloniaProperty.Register<ColorBorder, IBrush>(nameof(RightBorderBrush)); public IBrush RightBorderBrush { get => GetValue(RightBorderBrushProperty); set => SetValue(RightBorderBrushProperty, value); } public static readonly StyledProperty<IBrush> BottomBorderBrushProperty = AvaloniaProperty.Register<ColorBorder, IBrush>(nameof(BottomBorderBrush)); public IBrush BottomBorderBrush { get => GetValue(BottomBorderBrushProperty); set => SetValue(BottomBorderBrushProperty, value); } // 定义边框厚度 public static readonly StyledProperty<Thickness> BorderThicknessProperty = AvaloniaProperty.Register<ColorBorder, Thickness>(nameof(BorderThickness)); public Thickness BorderThickness { get => GetValue(BorderThicknessProperty); set => SetValue(BorderThicknessProperty, value); } // 定义内容(子控件) public static readonly StyledProperty<Control?> ChildProperty = AvaloniaProperty.Register<ColorBorder, Control?>(nameof(Child)); public Control? Child { get => GetValue(ChildProperty); set => SetValue(ChildProperty, value); } // 定义内边距 public static readonly StyledProperty<Thickness> PaddingProperty = AvaloniaProperty.Register<ColorBorder, Thickness>(nameof(Padding)); public Thickness Padding { get => GetValue(PaddingProperty); set => SetValue(PaddingProperty, value); } static ColorBorder() { // 当这些属性改变时,触发重新绘制 AffectsRender<ColorBorder>( LeftBorderBrushProperty, TopBorderBrushProperty, RightBorderBrushProperty, BottomBorderBrushProperty, BorderThicknessProperty ); // 注意:Child的改变需要重新布局,所以需要重写MeasureOverrideArrangeOverride AffectsMeasure<ColorBorder>(ChildProperty); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); // 如果边框厚度改变,也需要重新测量(因为边框厚度可能影响控件的内部空间) if (change.Property == BorderThicknessProperty || change.Property == PaddingProperty) { InvalidateMeasure(); } } // 重写MeasureOverride以测量子控件(如果有) protected override Size MeasureOverride(Size availableSize) { // 计算内部可用空间:总空间减去边框内边距 Thickness border = BorderThickness; Thickness padding = Padding; Size innerSize = new Size( Math.Max(0, availableSize.Width - border.Left - border.Right - padding.Left - padding.Right), Math.Max(0, availableSize.Height - border.Top - border.Bottom - padding.Top - padding.Bottom) ); // 如果有子控件,则测量子控件 if (Child != null) { Child.Measure(innerSize); return new Size( Child.DesiredSize.Width + border.Left + border.Right + padding.Left + padding.Right, Child.DesiredSize.Height + border.Top + border.Bottom + padding.Top + padding.Bottom ); } // 没有子控件,则返回最小尺寸(边框内边距) return new Size( border.Left + border.Right + padding.Left + padding.Right, border.Top + border.Bottom + padding.Top + padding.Bottom ); } // 重写ArrangeOverride以安排子控件的位置 protected override Size ArrangeOverride(Size finalSize) { Thickness border = BorderThickness; Thickness padding = Padding; // 计算子控件的布局矩形(在边框内边距之内) Rect contentRect = new Rect( border.Left + padding.Left, border.Top + padding.Top, Math.Max(0, finalSize.Width - border.Left - border.Right - padding.Left - padding.Right), Math.Max(0, finalSize.Height - border.Top - border.Bottom - padding.Top - padding.Bottom) ); // 如果有子控件,则安排子控件 if (Child != null) { Child.Arrange(contentRect); } return finalSize; } // 重写Render方法,绘制四个边框 public override void Render(DrawingContext context) { // 获取控件的边界矩形 Rect bounds = new Rect(0, 0, Bounds.Width, Bounds.Height); // 绘制左边框 if (BorderThickness.Left > 0 && LeftBorderBrush != null) { context.FillRectangle(LeftBorderBrush, new Rect(0, 0, BorderThickness.Left, bounds.Height)); } // 绘制上边框 if (BorderThickness.Top > 0 && TopBorderBrush != null) { context.FillRectangle(TopBorderBrush, new Rect(0, 0, bounds.Width, BorderThickness.Top)); } // 绘制右边框 if (BorderThickness.Right > 0 && RightBorderBrush != null) { context.FillRectangle( RightBorderBrush, new Rect(bounds.Width - BorderThickness.Right, 0, BorderThickness.Right, bounds.Height) ); } // 绘制下边框 if (BorderThickness.Bottom > 0 && BottomBorderBrush != null) { context.FillRectangle( BottomBorderBrush, new Rect(0, bounds.Height - BorderThickness.Bottom, bounds.Width, BorderThickness.Bottom) ); } } }在这个基础上添加背景圆角属性
09-26
using System; using System.Collections.ObjectModel; using System.IO; using System.Text.Json; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; using System.Windows.Threading; using ActUtlType64Lib; using Microsoft.Win32; using System.Text.Json.Serialization; using System.Windows.Media.Animation; using System.Text; using Path = System.IO.Path; using System.Globalization; using System.Linq; using System.Collections.Generic; using System.Windows.Input; using System.Collections.Concurrent; using System.Diagnostics; namespace _9696999 { public partial class MainWindow : Window { //设备节拍时间负载率保存 private string _saveDirectory = @"D:\PLC_Monitor_Data"; private StreamWriter? _rhythmDataWriter; private StreamWriter? _loadDataWriter; private DateTime _lastFileCreationDate; private PlcMonitorViewModel _viewModel; private ActUtlType64 _plc; private DispatcherTimer _readTimer; private DispatcherTimer _logClearTimer; private DispatcherTimer _heartbeatTimer; // 心跳定时器 private bool _isConnected = false; private int _stationNumber = 1; // 保存站号用于重连 private int _connectionRetryCount = 0; // 重试次数计数 private const int MAX_RETRY_COUNT = 5; // 最大重试次数 // 存储上一次的值用于比较变化 private ObservableCollection<float> _lastMotorValues = new ObservableCollection<float>(); // 添加状态跟踪字典 private Dictionary<string, bool> _rhythmAlarmStatus = new Dictionary<string, bool>(); private Dictionary<string, bool> _loadAlarmStatus = new Dictionary<string, bool>(); private bool _rhythmNeedsRedraw = false; private bool _loadNeedsRedraw = false; private DispatcherTimer _renderTimer = new DispatcherTimer(); public MainWindow() { InitializeComponent(); _viewModel = new PlcMonitorViewModel(); DataContext = _viewModel; _plc = new ActUtlType64(); _readTimer = new DispatcherTimer(); _logClearTimer = new DispatcherTimer(); _heartbeatTimer = new DispatcherTimer(); // 初始化心跳定时器 InitializePlc(); InitializeLogClearTimer(); InitializeHeartbeatTimer(); // 初始化心跳定时器 InitializeDataSaving(); //数据保存 InitializeDefaultDates(); // 初始化默认日期时间 InitializeRenderTimer(); // 新增渲染定时器初始化 } private void InitializeRenderTimer() { // 使用已创建的实例进行配置 _renderTimer.Interval = TimeSpan.FromMilliseconds(300); _renderTimer.Tick += RenderTimer_Tick; _renderTimer.Start(); AddLog("图表渲染定时器已启动"); } // 修改渲染处理方法 private void RenderTimer_Tick(object? sender, EventArgs e) { // 添加空值检查确保安全 if (_renderTimer == null) return; // 按需渲染图表 if (_rhythmNeedsRedraw) { DrawBarChart(canvasRhythmChart, _viewModel.RhythmDataList, true, "rhythm"); _rhythmNeedsRedraw = false; } if (_loadNeedsRedraw) { DrawBarChart(canvasLoadChart, _viewModel.LoadDataList, true, "load"); _loadNeedsRedraw = false; } } private void InitializeDefaultDates() { dpStartDate.SelectedDate = DateTime.Today; dpEndDate.SelectedDate = DateTime.Today; txtStartTime.Text = "00:00:00"; txtEndTime.Text = "23:59:59"; } private void InitializeDataSaving() { // 创建保存目录 if (!Directory.Exists(_saveDirectory)) { Directory.CreateDirectory(_saveDirectory); } // 初始化文件写入器 CreateDataFiles(); AddLog($"数据将实时保存到: {_saveDirectory}"); } private void CreateDataFiles() { try { // 关闭现有文件流 _rhythmDataWriter?.Close(); _loadDataWriter?.Close(); string dateStr = DateTime.Today.ToString("yyyyMMdd"); string rhythmFilePath = $"{_saveDirectory}\\rhythm_{dateStr}.csv"; string loadFilePath = $"{_saveDirectory}\\load_{dateStr}.csv"; // 确保目录存在 if (!Directory.Exists(_saveDirectory)) { Directory.CreateDirectory(_saveDirectory); } // 使用FileMode.OpenOrCreateFileShare.ReadWrite模式 if (!File.Exists(rhythmFilePath)) { using (var fs = new FileStream(rhythmFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) { using (var writer = new StreamWriter(fs, Encoding.UTF8)) { writer.WriteLine("时间,名称,地址,数值"); } } } if (!File.Exists(loadFilePath)) { using (var fs = new FileStream(loadFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) { using (var writer = new StreamWriter(fs, Encoding.UTF8)) { writer.WriteLine("时间,名称,地址,数值"); } } } // 以共享模式打开写入器(允许其他进程读取) var rhythmFs = new FileStream(rhythmFilePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); _rhythmDataWriter = new StreamWriter(rhythmFs, Encoding.UTF8); _rhythmDataWriter.AutoFlush = true; var loadFs = new FileStream(loadFilePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); _loadDataWriter = new StreamWriter(loadFs, Encoding.UTF8); _loadDataWriter.AutoFlush = true; _lastFileCreationDate = DateTime.Today; } catch (Exception ex) { AddLog($"创建数据文件失败: {ex.Message}"); _rhythmDataWriter = null; _loadDataWriter = null; } } // 异步保存节拍数据 private void SaveRhythmData(ChartData data, float value) { try { // 每天创建新文件 if (DateTime.Today != _lastFileCreationDate) { CreateDataFiles(); } if (_rhythmDataWriter == null || _rhythmDataWriter.BaseStream == null) { return; } // 写入CSV数据 string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); string csvLine = $"{timestamp},{data.Name},{data.Address},{value}"; // 使用线程安全的方式写入 lock (_rhythmDataWriter) { _rhythmDataWriter.WriteLine(csvLine); } } catch (Exception ex) { AddLog($"保存节拍数据失败: {ex.Message}"); } } // 异步保存负载数据 private void SaveLoadData(ChartData data, float value) { try { // 每天创建新文件 if (DateTime.Today != _lastFileCreationDate) { CreateDataFiles(); } if (_loadDataWriter == null || _loadDataWriter.BaseStream == null) { return; } // 写入CSV数据 string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); string csvLine = $"{timestamp},{data.Name},{data.Address},{value}"; // 使用线程安全的方式写入 lock (_loadDataWriter) { _loadDataWriter.WriteLine(csvLine); } } catch (Exception ex) { AddLog($"保存负载数据失败: {ex.Message}"); } } private void InitializeHeartbeatTimer() { _heartbeatTimer.Interval = TimeSpan.FromSeconds(5); // 5秒检测一次 _heartbeatTimer.Tick += HeartbeatTimer_Tick; _heartbeatTimer.Start(); AddLog("心跳检测功能已启动"); } private void HeartbeatTimer_Tick(object? sender, EventArgs e) { // 只有在已连接状态下才检测心跳 if (!_isConnected) return; try { // 尝试读取一个测试点来检测连接状态 short testValue; int result = _plc.ReadDeviceBlock2("D0", 1, out testValue); if (result != 0) { // 读取失败,连接可能已断开 _isConnected = false; _readTimer.Stop(); Application.Current.Dispatcher.Invoke(() => { tbConnectionStatus.Text = "连接断开 (尝试重连中...)"; tbHeartbeatStatus.Text = "连接异常"; tbHeartbeatStatus.Foreground = Brushes.Red; }); AddLog($"心跳检测失败,错误代码: {result}"); TryReconnect(); } else { // 连接正常 Application.Current.Dispatcher.Invoke(() => { tbHeartbeatStatus.Text = "连接正常"; tbHeartbeatStatus.Foreground = Brushes.Green; }); } } catch (Exception ex) { AddLog($"心跳检测异常: {ex.Message}"); _isConnected = false; _readTimer.Stop(); TryReconnect(); } } private void TryReconnect() { if (_connectionRetryCount >= MAX_RETRY_COUNT) { AddLog($"已达到最大重试次数({MAX_RETRY_COUNT}),停止自动重连"); _heartbeatTimer.Stop(); return; } _connectionRetryCount++; AddLog($"尝试第 {_connectionRetryCount} 次重新连接..."); try { _plc.Close(); // 确保先关闭连接 int result = _plc.Open(); if (result == 0) { _isConnected = true; _connectionRetryCount = 0; // 重置重试计数 Application.Current.Dispatcher.Invoke(() => { tbConnectionStatus.Text = $"已重新连接 (站号: {_stationNumber})"; tbHeartbeatStatus.Text = "连接正常"; tbHeartbeatStatus.Foreground = Brushes.Green; btnConnect.IsEnabled = false; btnDisconnect.IsEnabled = true; }); _readTimer.Start(); AddLog($"PLC重新连接成功 (站号: {_stationNumber})"); } else { AddLog($"重新连接失败,错误代码: {result}"); } } catch (Exception ex) { AddLog($"重新连接异常: {ex.Message}"); } } private void InitializeLogClearTimer() { _logClearTimer = new DispatcherTimer(); // 设置间隔为10分钟(600,000毫秒) _logClearTimer.Interval = TimeSpan.FromMinutes(1); _logClearTimer.Tick += LogClearTimer_Tick; _logClearTimer.Start(); AddLog("日志自动清除功能已启动(每隔10分钟清除一次)"); } private void LogClearTimer_Tick(object? sender, EventArgs e) { ClearCommunicationLog(); } private void ClearCommunicationLog() { if (!Application.Current.Dispatcher.CheckAccess()) { Application.Current.Dispatcher.Invoke(ClearCommunicationLog); return; } // 记录清除操作 string clearMessage = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 已自动清除通讯日志"; // 清除日志 txtCommunicationLog.Text = clearMessage + Environment.NewLine; // 可选:添加日志记录表明已执行清除操作 AddLog("通讯日志已自动清除"); } private void InitializePlc() { try { _plc = new ActUtlType64(); AddLog("PLC通信库初始化成功"); } catch (Exception ex) { MessageBox.Show($"初始化PLC通信失败: {ex.Message}\n请确保已安装MX Component运行时库。"); AddLog($"PLC通信库初始化失败: {ex.Message}"); } } //UI界面隐藏触发Click private bool _isPanelExpanded = false; private void TogglePanel_Click(object sender, RoutedEventArgs e) { if (_isPanelExpanded) { // 收起面板 Storyboard collapseAnimation = (Storyboard)FindResource("CollapsePanel"); collapseAnimation.Begin(plcPanel); btnTogglePanel.Content = "▶"; btnTogglePanel.ToolTip = "展开PLC设置"; } else { // 展开面板 Storyboard expandAnimation = (Storyboard)FindResource("ExpandPanel"); expandAnimation.Begin(plcPanel); btnTogglePanel.Content = "◀"; btnTogglePanel.ToolTip = "收起PLC设置"; } _isPanelExpanded = !_isPanelExpanded; } // 连接PLC private void Connect_Click(object sender, RoutedEventArgs e) { // 验证站号输入 if (!int.TryParse(txtStationNumber.Text, out int stationNumber) || stationNumber < 1 || stationNumber > 255) { MessageBox.Show("请输入有效的站号 (1-255)"); return; } _stationNumber = stationNumber; // 保存站号用于重连 _connectionRetryCount = 0; // 重置重试计数 try { // 设置站号 _plc.ActLogicalStationNumber = stationNumber; int result = _plc.Open(); if (result == 0) // 返回0表示成功 { _isConnected = true; tbConnectionStatus.Text = $"已连接 (站号: {stationNumber})"; tbHeartbeatStatus.Text = "连接正常"; tbHeartbeatStatus.Foreground = Brushes.Green; btnConnect.IsEnabled = false; btnDisconnect.IsEnabled = true; txtStationNumber.IsEnabled = false; StartMonitoring(); AddLog($"PLC连接成功 (站号: {stationNumber})"); } else { MessageBox.Show($"连接失败,错误代码: {result}"); AddLog($"PLC连接失败,错误代码: {result}"); } } catch (Exception ex) { MessageBox.Show($"连接失败: {ex.Message}"); AddLog($"PLC连接异常: {ex.Message}"); } } // 启动监控定时器 private void StartMonitoring() { _readTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) // 200ms采集周期 }; _readTimer.Tick += async (s, e) => await ReadPlcDataAsync(); _readTimer.Start(); AddLog("开始监控PLC数据"); } // 重构异步读取逻辑 private async Task ReadPlcDataAsync() { if (!_isConnected) return; try { // 并行读取三类数据 var tasks = new List<Task> { Task.Run(ReadMotorData), Task.Run(ReadRhythmData), Task.Run(ReadLoadData) }; await Task.WhenAll(tasks); } catch (Exception ex) { AddLog($"读取数据异常: {ex.Message}"); } } // 优化电机数据读取 private void ReadMotorData() { for (int i = 0; i < _viewModel.MotorDataList.Count; i++) { short value = 0; int result = _plc.ReadDeviceBlock2(_viewModel.MotorDataList[i].Address, 1, out value); bool valueChanged = false; string? logMessage = null; if (result == 0) { float actualValue = value; var data = _viewModel.MotorDataList[i]; // 检查值是否发生变化 if (_lastMotorValues.Count <= i) { _lastMotorValues.Add(actualValue); valueChanged = true; if (actualValue != data.StandardValue) { logMessage = $"点位 {data.PointName} 异常: 实际值 {actualValue} ≠ 标准值 {data.StandardValue}"; } } else if (_lastMotorValues[i] != actualValue) { _lastMotorValues[i] = actualValue; valueChanged = true; if (actualValue == data.StandardValue) { logMessage = $"点位 {data.PointName} 恢复正常"; } else { logMessage = $"点位 {data.PointName} 异常: 实际值 {actualValue} ≠ 标准值 {data.StandardValue}"; } } // 只在值变化时更新UI if (valueChanged) { Application.Current.Dispatcher.Invoke(() => { data.ActualValue = actualValue; data.Status = (actualValue == data.StandardValue) ? "正常" : "异常"; }); if (!string.IsNullOrEmpty(logMessage)) { AddLog(logMessage); } } } else { AddLog($"读取点位 {_viewModel.MotorDataList[i].PointName} 失败,错误代码: {result}"); } } } // 优化节拍数据读取 private void ReadRhythmData() { for (int i = 0; i < _viewModel.RhythmDataList.Count; i++) { short value = 0; int result = _plc.ReadDeviceBlock2(_viewModel.RhythmDataList[i].Address, 1, out value); if (result == 0) { float rhythmValue = value / 10.0f; var data = _viewModel.RhythmDataList[i]; bool valueChanged = Math.Abs(data.Value - rhythmValue) > 0.01f; if (valueChanged) { // 更新UI线程中的值 Application.Current.Dispatcher.Invoke(() => { data.Value = rhythmValue; _rhythmNeedsRedraw = true; }); // 检查并记录报警 CheckRhythmAlarm(data, rhythmValue); // 异步保存数据 Task.Run(() => SaveRhythmData(data, rhythmValue)); } } else { AddLog($"读取节拍数据 {_viewModel.RhythmDataList[i].Name} 失败,错误代码: {result}"); } } } // 检查节拍时间报警 private void CheckRhythmAlarm(ChartData data, float value) { if (string.IsNullOrEmpty(data.Address)) return; string address = data.Address; // 初始化报警状态 if (!_rhythmAlarmStatus.ContainsKey(address)) { _rhythmAlarmStatus[address] = false; // 初始状态为正常 } bool isAlarm = value > 30; // 超过30秒为异常 // 状态发生变化时记录日志 if (_rhythmAlarmStatus[address] != isAlarm) { string name = data.Name ?? "未知部位"; if (isAlarm) { AddLog($"【节拍时间报警】部位 {name} ({address}) 节拍时间 {value:F1}s 超出正常范围(>30s)"); } else { AddLog($"【节拍时间恢复】部位 {name} ({address}) 节拍时间 {value:F1}s 已恢复正常(≤30s)"); } // 更新状态 _rhythmAlarmStatus[address] = isAlarm; } } // 优化负载数据读取 private void ReadLoadData() { for (int i = 0; i < _viewModel.LoadDataList.Count; i++) { short value = 0; int result = _plc.ReadDeviceBlock2(_viewModel.LoadDataList[i].Address, 1, out value); if (result == 0) { float loadValue = value; var data = _viewModel.LoadDataList[i]; bool valueChanged = Math.Abs(data.Value - loadValue) > 0.01f; if (valueChanged) { // 更新UI线程中的值 Application.Current.Dispatcher.Invoke(() => { data.Value = loadValue; _loadNeedsRedraw = true; }); // 检查并记录报警 CheckLoadAlarm(data, loadValue); // 异步保存数据 Task.Run(() => SaveLoadData(data, loadValue)); } } else { AddLog($"读取负载数据 {_viewModel.LoadDataList[i].Name} 失败,错误代码: {result}"); } } } // 检查负载率报警 private void CheckLoadAlarm(ChartData data, float value) { if (string.IsNullOrEmpty(data.Address)) return; string address = data.Address; // 初始化报警状态 if (!_loadAlarmStatus.ContainsKey(address)) { _loadAlarmStatus[address] = false; // 初始状态为正常 } bool isAlarm = value >= 80; // 超过80%为异常 // 状态发生变化时记录日志 if (_loadAlarmStatus[address] != isAlarm) { string name = data.Name ?? "未知电机"; if (isAlarm) { AddLog($"【负载率报警】电机 {name} ({address}) 负载率 {value:F1}% 超出正常范围(≥80%)"); } else { AddLog($"【负载率恢复】电机 {name} ({address}) 负载率 {value:F1}% 已恢复正常(<80%)"); } // 更新状态 _loadAlarmStatus[address] = isAlarm; } } // 绘制柱状图 private void DrawBarChart(Canvas canvas, ObservableCollection<ChartData> dataList, bool showValue, string chartType) { if (!Application.Current.Dispatcher.CheckAccess()) { Application.Current.Dispatcher.Invoke(() => DrawBarChart(canvas, dataList, showValue, chartType)); return; } canvas.Children.Clear(); if (dataList.Count == 0) return; double canvasWidth = canvas.ActualWidth; double canvasHeight = canvas.ActualHeight; double barWidth = (canvasWidth - 40) / dataList.Count - 10; double maxValue = GetMaxValue(dataList); // 绘制Y轴 Line yAxis = new Line { X1 = 30, Y1 = 20, X2 = 30, Y2 = canvasHeight - 30, Stroke = Brushes.Black, StrokeThickness = 2 }; canvas.Children.Add(yAxis); // 绘制X轴 Line xAxis = new Line { X1 = 30, Y1 = canvasHeight - 30, X2 = canvasWidth - 10, Y2 = canvasHeight - 30, Stroke = Brushes.Black, StrokeThickness = 2 }; canvas.Children.Add(xAxis); // 绘制Y轴刻度 for (int i = 0; i <= 5; i++) { double yPos = canvasHeight - 30 - (i * (canvasHeight - 50) / 5); double value = i * maxValue / 5; Line tick = new Line { X1 = 25, Y1 = yPos, X2 = 30, Y2 = yPos, Stroke = Brushes.Black, StrokeThickness = 1 }; canvas.Children.Add(tick); TextBlock tickValue = new TextBlock { Text = value.ToString("F1"), FontSize = 10, Foreground = Brushes.Black }; Canvas.SetLeft(tickValue, 5); Canvas.SetTop(tickValue, yPos - 8); canvas.Children.Add(tickValue); } for (int i = 0; i < dataList.Count; i++) { double barHeight = (dataList[i].Value / maxValue) * (canvasHeight - 60); double x = 40 + i * (barWidth + 10); double y = canvasHeight - 30 - barHeight; // 根据图表类型数值确定颜色 Brush barColor = GetBarColor(chartType, dataList[i].Value); // 绘制柱形 System.Windows.Shapes.Rectangle rect = new System.Windows.Shapes.Rectangle { Width = barWidth, Height = barHeight, Fill = barColor, Stroke = Brushes.Black, StrokeThickness = 1 }; Canvas.SetLeft(rect, x); Canvas.SetTop(rect, y); canvas.Children.Add(rect); // 绘制数值 if (showValue) { TextBlock text = new TextBlock { Text = dataList[i].Value.ToString("F1"), FontSize = 10, Foreground = Brushes.Black }; Canvas.SetLeft(text, x + barWidth / 2 - 10); Canvas.SetTop(text, y - 20); canvas.Children.Add(text); } // 绘制名称 TextBlock nameText = new TextBlock { Text = $"{dataList[i].Name}\n{dataList[i].Address}", FontSize = 10, Foreground = Brushes.Black, TextAlignment = TextAlignment.Center, Width = barWidth + 10 }; Canvas.SetLeft(nameText, x - 5); Canvas.SetTop(nameText, canvasHeight - 25); canvas.Children.Add(nameText); } } //历史数据 //历史数据 private async void LoadHistoryData_Click(object sender, RoutedEventArgs e) { // 保存原始按钮内容 var originalContent = btnLoadHistory.Content; // 创建进度报告器 var progress = new Progress<int>(percent => { progressBar.Value = percent; }); //// 在开始加载历史数据时暂停监控 _readTimer?.Stop(); _renderTimer?.Stop(); // 暂停图表渲染 try { // 安全获取 ComboBox 的选中项 if (cmbDataType.SelectedItem is not ComboBoxItem selectedItem || selectedItem.Content == null) { MessageBox.Show("请选择数据类型"); return; } string? dataType = selectedItem.Content.ToString(); // 检查是否为空 if (string.IsNullOrWhiteSpace(dataType)) { MessageBox.Show("所选数据类型无效"); return; } if (dpStartDate.SelectedDate == null || dpEndDate.SelectedDate == null) { MessageBox.Show("请选择开始日期结束日期"); return; } // 解析时间 DateTime startDate = dpStartDate.SelectedDate.Value; DateTime endDate = dpEndDate.SelectedDate.Value; if (!TimeSpan.TryParseExact(txtStartTime.Text, "hh\\:mm\\:ss", CultureInfo.InvariantCulture, out TimeSpan startTime)) { MessageBox.Show("开始时间格式不正确,请使用HH:mm:ss格式"); return; } if (!TimeSpan.TryParseExact(txtEndTime.Text, "hh\\:mm\\:ss", CultureInfo.InvariantCulture, out TimeSpan endTime)) { MessageBox.Show("结束时间格式不正确,请使用HH:mm:ss格式"); return; } DateTime startDateTime = startDate.Date + startTime; DateTime endDateTime = endDate.Date + endTime; if (startDateTime >= endDateTime) { MessageBox.Show("开始时间必须早于结束时间"); return; } // 禁用按钮并显示加载状态 btnLoadHistory.Content = "加载中..."; btnLoadHistory.IsEnabled = false; // 显示进度条 progressBar.Visibility = Visibility.Visible; progressBar.Value = 0; Cursor = Cursors.Wait; // 添加资源优化提示 AddLog("开始加载历史数据,监控功能将临时暂停..."); await LoadAndDrawHistoryDataAsync(dataType, startDateTime, endDateTime, progress); } catch (Exception ex) { MessageBox.Show($"加载历史数据失败: {ex.Message}"); AddLog($"加载历史数据出错: {ex.Message}"); } finally { // 恢复UI状态监控功能 btnLoadHistory.Content = originalContent; btnLoadHistory.IsEnabled = true; progressBar.Visibility = Visibility.Collapsed; Cursor = Cursors.Arrow; // 重新启动监控 if (_isConnected) { _readTimer?.Start(); _renderTimer?.Start(); AddLog("历史数据加载完成,监控功能已恢复"); } } } private async Task LoadAndDrawHistoryDataAsync(string dataType, DateTime startTime, DateTime endTime, IProgress<int> progress) { var stopwatch = System.Diagnostics.Stopwatch.StartNew(); try { canvasHistoryChart.Children.Clear(); AddLog($"开始加载{dataType}历史数据 ({startTime:yyyy-MM-dd HH:mm:ss} 到 {endTime:yyyy-MM-dd HH:mm:ss})"); // 获取文件列表 var files = GetDataFilesInRange(dataType, startTime, endTime); AddLog($"找到 {files.Count} 个数据文件"); if (files.Count == 0) { MessageBox.Show($"在指定时间段内找不到{dataType}的历史数据文件"); return; } // 计算平均值 var averagedData = await Task.Run(() => CalculateAveragesAsync(files, startTime, endTime, dataType, progress)); AddLog($"数据处理完成,共 {averagedData.Count} 个点位,耗时 {stopwatch.Elapsed.TotalSeconds:F2} 秒"); if (averagedData.Count == 0) { MessageBox.Show("在指定时间段内没有找到有效数据"); return; } // 绘制图表 await Dispatcher.InvokeAsync(() => { DrawHistoryBarChart(averagedData, dataType, startTime, endTime); AddLog($"图表绘制完成,总耗时 {stopwatch.Elapsed.TotalSeconds:F2} 秒"); }); } catch (Exception ex) { AddLog($"加载历史数据出错: {ex.Message},总耗时 {stopwatch.Elapsed.TotalSeconds:F2} 秒"); throw; } finally { stopwatch.Stop(); } } // 获取时间段内的数据文件 private List<string> GetDataFilesInRange(string dataType, DateTime startTime, DateTime endTime) { var files = new List<string>(); string filePrefix = dataType == "节拍时间" ? "rhythm" : "load"; for (DateTime date = startTime.Date; date <= endTime.Date; date = date.AddDays(1)) { string fileName = $"{filePrefix}_{date:yyyyMMdd}.csv"; string filePath = Path.Combine(_saveDirectory, fileName); if (File.Exists(filePath)) { files.Add(filePath); } } return files; } // 计算每个点位的平均值 - 添加进度报告 // 改进的文件读取方法,减少内存分配磁盘I/O private Dictionary<string, double> CalculateAveragesAsync( List<string> files, DateTime startTime, DateTime endTime, string dataType, IProgress<int> progress) { var pointData = new ConcurrentDictionary<string, (double Sum, int Count)>(); int totalFiles = files.Count; int processedFiles = 0; // 限制并行度,避免过度竞争资源 var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2) // 减少并行度 }; Parallel.ForEach(files, parallelOptions, filePath => { try { // 使用更高效的文件读取方式 using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using var reader = new StreamReader(fs, Encoding.UTF8, false, 8192); // 增加缓冲区大小 // 跳过标题行 reader.ReadLine(); string? line; while ((line = reader.ReadLine()) != null) { if (string.IsNullOrWhiteSpace(line)) continue; var parts = line.Split(',', 4); // 限制分割数量,提高性能 if (parts.Length < 4) continue; // 解析时间戳数值 if (DateTime.TryParseExact(parts[0], "yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime timestamp) && double.TryParse(parts[3], out double value)) { // 检查时间是否在指定范围内 if (timestamp >= startTime && timestamp <= endTime) { string pointName = parts[1]; // 使用元组减少内存分配 pointData.AddOrUpdate( pointName, (value, 1), (key, oldValue) => (oldValue.Sum + value, oldValue.Count + 1)); } } } } catch (Exception ex) { // 记录错误但继续处理其他文件 System.Diagnostics.Debug.WriteLine($"读取文件 {filePath} 时出错: {ex.Message}"); } finally { // 更新进度 int newProcessed = Interlocked.Increment(ref processedFiles); int percent = (int)((double)newProcessed / totalFiles * 100); progress?.Report(percent); } }); // 计算平均值 var averages = new ConcurrentDictionary<string, double>(); Parallel.ForEach(pointData, kvp => { if (kvp.Value.Count > 0) { averages[kvp.Key] = kvp.Value.Sum / kvp.Value.Count; } }); return new Dictionary<string, double>(averages); } // 绘制历史数据柱形图 // 改进的图表渲染方法,减少UI操作 private void DrawHistoryBarChart(Dictionary<string, double> data, string dataType, DateTime startTime, DateTime endTime) { canvasHistoryChart.Children.Clear(); if (data.Count == 0) { // 绘制空状态提示 var emptyText = new TextBlock { Text = "没有找到符合条件的数据", FontSize = 14, Foreground = Brushes.Gray, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center }; Canvas.SetLeft(emptyText, canvasHistoryChart.ActualWidth / 2 - 70); Canvas.SetTop(emptyText, canvasHistoryChart.ActualHeight / 2 - 10); canvasHistoryChart.Children.Add(emptyText); return; } // 使用更简单的绘制方式,避免复杂的Visual树操作 double canvasWidth = canvasHistoryChart.ActualWidth; double canvasHeight = canvasHistoryChart.ActualHeight; if (canvasWidth <= 0 || canvasHeight <= 0) return; const double margin = 40; double availableWidth = canvasWidth - 2 * margin; double availableHeight = canvasHeight - 2 * margin; // 限制显示的点位数量,避免过度渲染 var displayData = data.Take(50).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); if (data.Count > 50) { AddLog($"数据量较大,仅显示前50个点位(共{data.Count}个)"); } double barWidth = Math.Max(10, (availableWidth - (displayData.Count - 1) * 5) / displayData.Count); double maxValue = displayData.Values.Max(); if (maxValue == 0) maxValue = 1; // 绘制坐标轴 var yAxis = new Line { X1 = margin, Y1 = margin, X2 = margin, Y2 = canvasHeight - margin, Stroke = Brushes.Black, StrokeThickness = 2 }; canvasHistoryChart.Children.Add(yAxis); var xAxis = new Line { X1 = margin, Y1 = canvasHeight - margin, X2 = canvasWidth - margin, Y2 = canvasHeight - margin, Stroke = Brushes.Black, StrokeThickness = 2 }; canvasHistoryChart.Children.Add(xAxis); // 简化的刻度绘制 for (int i = 0; i <= 5; i++) { double y = canvasHeight - margin - (i * availableHeight / 5); double value = i * maxValue / 5; var tick = new Line { X1 = margin - 5, Y1 = y, X2 = margin, Y2 = y, Stroke = Brushes.Black, StrokeThickness = 1 }; canvasHistoryChart.Children.Add(tick); var tickText = new TextBlock { Text = value.ToString("F1"), FontSize = 9, Foreground = Brushes.Black }; Canvas.SetLeft(tickText, margin - 25); Canvas.SetTop(tickText, y - 8); canvasHistoryChart.Children.Add(tickText); } // 绘制柱形 int index = 0; foreach (var kvp in displayData) { double value = kvp.Value; double barHeight = (value / maxValue) * availableHeight; double x = margin + index * (barWidth + 5); double y = canvasHeight - margin - barHeight; Brush barColor = GetBarColor(dataType == "节拍时间" ? "rhythm" : "load", value); var rect = new Rectangle { Width = barWidth, Height = barHeight, Fill = barColor, Stroke = Brushes.Black, StrokeThickness = 1 }; Canvas.SetLeft(rect, x); Canvas.SetTop(rect, y); canvasHistoryChart.Children.Add(rect); // 只在有足够空间时显示数值 if (barWidth > 20) { var valueText = new TextBlock { Text = value.ToString("F1"), FontSize = 9, Foreground = Brushes.Black, Width = barWidth, TextAlignment = TextAlignment.Center }; Canvas.SetLeft(valueText, x); Canvas.SetTop(valueText, y - 15); canvasHistoryChart.Children.Add(valueText); } // 点位名称(简化显示) var nameText = new TextBlock { Text = kvp.Key.Length > 8 ? kvp.Key.Substring(0, 8) + "..." : kvp.Key, FontSize = 8, Foreground = Brushes.Black, Width = barWidth + 10, TextAlignment = TextAlignment.Center }; Canvas.SetLeft(nameText, x - 5); Canvas.SetTop(nameText, canvasHeight - margin + 5); canvasHistoryChart.Children.Add(nameText); index++; } // 添加统计信息 var statsText = new TextBlock { Text = $"统计: {displayData.Count}个点位 | 最大值: {displayData.Values.Max():F2} | 平均值: {displayData.Values.Average():F2}", FontSize = 10, Foreground = Brushes.DarkBlue, FontWeight = FontWeights.Bold }; Canvas.SetLeft(statsText, canvasWidth / 2 - 150); Canvas.SetTop(statsText, 10); canvasHistoryChart.Children.Add(statsText); } // DrawingVisual容器类 private class VisualHost : FrameworkElement { private readonly Visual _visual; public VisualHost(Visual visual) { _visual = visual; AddVisualChild(_visual); } protected override int VisualChildrenCount => 1; protected override Visual GetVisualChild(int index) { if (index != 0) throw new ArgumentOutOfRangeException(nameof(index)); return _visual; } // 移除 MeasureOverride ArrangeOverride 方法 // 因为我们不需要自定义布局逻辑 } private void ClearHistoryChart_Click(object sender, RoutedEventArgs e) { // 清除历史图表区域 canvasHistoryChart.Children.Clear(); // 添加日志记录清理操作 AddLog("已清除历史数据图表"); } // 根据图表类型数值获取对应的颜色 private Brush GetBarColor(string chartType, double value) { switch (chartType) { case "rhythm": // 设备节拍时间 if (value < 10) return Brushes.Green; else if (value >= 10 && value < 20) return Brushes.YellowGreen; else if (value >= 20 && value < 30) return Brushes.Orange; else return Brushes.Red; case "load": // 伺服电机负载率 return value < 80 ? Brushes.Green : Brushes.Red; default: // 默认颜色 return Brushes.SteelBlue; } } // 获取最大值用于比例计算 private float GetMaxValue(ObservableCollection<ChartData> dataList) { float max = 0; foreach (var item in dataList) { if (item.Value > max) max = item.Value; } return max > 0 ? max : 1; } // 添加日志 private void AddLog(string message) { Application.Current.Dispatcher.Invoke(() => { txtCommunicationLog.AppendText($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {message}\n"); txtCommunicationLog.ScrollToEnd(); }); } // 电机点位配置保存 private void SaveMotorConfig_Click(object sender, RoutedEventArgs e) { try { SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.Filter = "JSON文件 (*.json)|*.json|所有文件 (*.*)|*.*"; saveFileDialog.DefaultExt = "json"; saveFileDialog.InitialDirectory = @"D:\"; // 默认指向D盘 saveFileDialog.FileName = "motor_config.json"; if (saveFileDialog.ShowDialog() == true) { string jsonString = JsonSerializer.Serialize(_viewModel.MotorDataList); File.WriteAllText(saveFileDialog.FileName, jsonString); AddLog($"电机配置已保存到: {saveFileDialog.FileName}"); } } catch (Exception ex) { MessageBox.Show($"保存配置失败: {ex.Message}"); AddLog($"保存电机配置异常: {ex.Message}"); } } // 电机点位配置读取 private void LoadMotorConfig_Click(object sender, RoutedEventArgs e) { try { OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.Filter = "JSON文件 (*.json)|*.json|所有文件 (*.*)|*.*"; openFileDialog.DefaultExt = "json"; openFileDialog.InitialDirectory = @"D:\"; // 默认指向D盘 if (openFileDialog.ShowDialog() == true) { string jsonString = File.ReadAllText(openFileDialog.FileName); var data = JsonSerializer.Deserialize<ObservableCollection<MotorPointData>>(jsonString); if (data != null) { _viewModel.MotorDataList.Clear(); foreach (var item in data) { _viewModel.MotorDataList.Add(item); } AddLog($"电机配置已从 {openFileDialog.FileName} 读取"); } } } catch (Exception ex) { MessageBox.Show($"读取配置失败: {ex.Message}"); AddLog($"读取电机配置异常: {ex.Message}"); } } // 节拍配置保存 private void SaveRhythmConfig_Click(object sender, RoutedEventArgs e) { try { SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.Filter = "JSON文件 (*.json)|*.json|所有文件 (*.*)|*.*"; saveFileDialog.DefaultExt = "json"; saveFileDialog.InitialDirectory = @"D:\"; saveFileDialog.FileName = "rhythm_config.json"; if (saveFileDialog.ShowDialog() == true) { string jsonString = JsonSerializer.Serialize(_viewModel.RhythmDataList); File.WriteAllText(saveFileDialog.FileName, jsonString); AddLog($"节拍配置已保存到: {saveFileDialog.FileName}"); } } catch (Exception ex) { MessageBox.Show($"保存配置失败: {ex.Message}"); AddLog($"保存节拍配置异常: {ex.Message}"); } } // 节拍配置读取 private void LoadRhythmConfig_Click(object sender, RoutedEventArgs e) { try { OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.Filter = "JSON文件 (*.json)|*.json|所有文件 (*.*)|*.*"; openFileDialog.DefaultExt = "json"; openFileDialog.InitialDirectory = @"D:\"; if (openFileDialog.ShowDialog() == true) { string jsonString = File.ReadAllText(openFileDialog.FileName); var data = JsonSerializer.Deserialize<ObservableCollection<ChartData>>(jsonString); if (data != null) { _viewModel.RhythmDataList.Clear(); // 清空节拍报警状态 _rhythmAlarmStatus.Clear(); foreach (var item in data) { _viewModel.RhythmDataList.Add(item); } AddLog($"节拍配置已从 {openFileDialog.FileName} 读取"); } } } catch (Exception ex) { MessageBox.Show($"读取配置失败: {ex.Message}"); AddLog($"读取节拍配置异常: {ex.Message}"); } } // 负载配置保存 private void SaveLoadConfig_Click(object sender, RoutedEventArgs e) { try { SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.Filter = "JSON文件 (*.json)|*.json|所有文件 (*.*)|*.*"; saveFileDialog.DefaultExt = "json"; saveFileDialog.InitialDirectory = @"D:\"; saveFileDialog.FileName = "load_config.json"; if (saveFileDialog.ShowDialog() == true) { string jsonString = JsonSerializer.Serialize(_viewModel.LoadDataList); File.WriteAllText(saveFileDialog.FileName, jsonString); AddLog($"负载配置已保存到: {saveFileDialog.FileName}"); } } catch (Exception ex) { MessageBox.Show($"保存配置失败: {ex.Message}"); AddLog($"保存负载配置异常: {ex.Message}"); } } // 负载配置读取 private void LoadLoadConfig_Click(object sender, RoutedEventArgs e) { try { OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.Filter = "JSON文件 (*.json)|*.json|所有文件 (*.*)|*.*"; openFileDialog.DefaultExt = "json"; openFileDialog.InitialDirectory = @"D:\"; if (openFileDialog.ShowDialog() == true) { string jsonString = File.ReadAllText(openFileDialog.FileName); var data = JsonSerializer.Deserialize<ObservableCollection<ChartData>>(jsonString); if (data != null) { _viewModel.LoadDataList.Clear(); // 清空负载报警状态 _loadAlarmStatus.Clear(); foreach (var item in data) { _viewModel.LoadDataList.Add(item); } AddLog($"负载配置已从 {openFileDialog.FileName} 读取"); } } } catch (Exception ex) { MessageBox.Show($"读取配置失败: {ex.Message}"); AddLog($"读取负载配置异常: {ex.Message}"); } } // 断开连接 private void Disconnect_Click(object sender, RoutedEventArgs e) { try { _plc.Close(); _isConnected = false; _readTimer.Stop(); tbConnectionStatus.Text = "未连接"; tbHeartbeatStatus.Text = "未连接"; tbHeartbeatStatus.Foreground = Brushes.Gray; btnConnect.IsEnabled = true; btnDisconnect.IsEnabled = false; txtStationNumber.IsEnabled = true; _connectionRetryCount = 0; // 重置重试计数 AddLog("PLC连接已断开"); } catch (Exception ex) { MessageBox.Show($"断开连接失败: {ex.Message}"); AddLog($"断开连接异常: {ex.Message}"); } } // 添加监控点 private void AddMonitorPoint_Click(object sender, RoutedEventArgs e) { if (string.IsNullOrWhiteSpace(txtPointName.Text) || string.IsNullOrWhiteSpace(txtPointAddress.Text) || string.IsNullOrWhiteSpace(txtStandardValue.Text)) { MessageBox.Show("请填写完整的监控点信息"); return; } if (!float.TryParse(txtStandardValue.Text, out float standardValue)) { MessageBox.Show("标准值必须是有效的数字"); return; } _viewModel.MotorDataList.Add(new MotorPointData { PointName = txtPointName.Text, Address = txtPointAddress.Text, StandardValue = standardValue, ActualValue = 0, Status = "未知" }); txtPointName.Text = ""; txtPointAddress.Text = ""; txtStandardValue.Text = ""; } // 添加节拍监控部位 private void AddSection_Click(object sender, RoutedEventArgs e) { if (string.IsNullOrWhiteSpace(txtSectionName.Text) || string.IsNullOrWhiteSpace(txtSectionAddress.Text)) { MessageBox.Show("请填写完整的部位信息"); return; } var newData = new ChartData { Name = txtSectionName.Text, Address = txtSectionAddress.Text, Value = 0 }; _viewModel.RhythmDataList.Add(newData); // 初始化报警状态 _rhythmAlarmStatus[newData.Address] = false; txtSectionName.Text = ""; txtSectionAddress.Text = ""; } // 添加负载监控电机 private void AddMotor_Click(object sender, RoutedEventArgs e) { if (string.IsNullOrWhiteSpace(txtMotorName.Text) || string.IsNullOrWhiteSpace(txtMotorAddress.Text)) { MessageBox.Show("请填写完整的电机信息"); return; } var newData = new ChartData { Name = txtMotorName.Text, Address = txtMotorAddress.Text, Value = 0 }; _viewModel.LoadDataList.Add(newData); // 初始化报警状态 _loadAlarmStatus[newData.Address] = false; txtMotorName.Text = ""; txtMotorAddress.Text = ""; } // 窗口关闭时清理所有资源 private void Window_Closed(object sender, EventArgs e) { try { // 停止所有定时器 _readTimer?.Stop(); _logClearTimer?.Stop(); _heartbeatTimer?.Stop(); _renderTimer?.Stop(); // 新增渲染定时器停止 // 关闭PLC连接 if (_isConnected) { _plc.Close(); } // 关闭文件流 if (_rhythmDataWriter != null) { _rhythmDataWriter.Close(); _rhythmDataWriter.Dispose(); } if (_loadDataWriter != null) { _loadDataWriter.Close(); _loadDataWriter.Dispose(); } AddLog("应用程序已关闭,资源已释放"); } catch (Exception ex) { MessageBox.Show($"关闭时发生错误: {ex.Message}"); } } } } 上面代码运行后,在加载历史数据时已经停掉了电极电位监控,节拍时间监控,负载率监控,为什么在点击历史数据时电极电位监控还是实时显示数值?
最新发布
10-06
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值