代码重构-3 用Tuple代替 out与ref

本文探讨了良好的编程习惯,特别是关于返回单一值的原则。通过一个具体的代码重构案例,展示了如何将复杂的函数输出转化为更易理解和维护的元组类型。

返回单一值是良好的编程习惯

原代码:

public LotteryViewModel ValidateLottery(LotteryBaseData baseData, int authTime, out GetDataForValidateLotteryResultModel data)
{

....

}

重构后代码:

 public Tuple<LotteryViewModel, GetDataForValidateLotteryResultModel> ValidateLottery(LotteryBaseData baseData, Lottery_AuthTimeEnum authTime )

{

...

}

转载于:https://www.cnblogs.com/zhshlimi/p/5101792.html

<Window x:Class="_9696999.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:_9696999" mc:Ignorable="d" Title="PLC监控系统" Height="800" Width="1400" Closed="Window_Closed"> <Window.Resources> <local:StatusColorConverter x:Key="StatusColorConverter"/> <!-- 展开面板动画 --> <Storyboard x:Key="ExpandPanel"> <DoubleAnimation Storyboard.TargetProperty="Width" To="300" Duration="0:0:0.3"/> </Storyboard> <Storyboard x:Key="CollapsePanel"> <DoubleAnimation Storyboard.TargetProperty="Width" To="0" Duration="0:0:0.3"/> </Storyboard> </Window.Resources> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <!-- PLC设置面板(可折叠) --> <Border x:Name="plcPanel" Grid.Column="0" Width="0" Background="#FFE0E0E0" BorderBrush="Gray" BorderThickness="0,0,1,0"> <GroupBox Header="PLC连接设置" Margin="5"> <StackPanel Orientation="Vertical"> <StackPanel Orientation="Horizontal" Margin="0,5"> <TextBlock Text="站号:" VerticalAlignment="Center" Margin="5" Width="40"/> <TextBox x:Name="txtStationNumber" Width="50" Margin="5" Text="1"/> </StackPanel> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,10"> <Button x:Name="btnConnect" Content="连接PLC" Margin="5" Click="Connect_Click" Width="80"/> <Button x:Name="btnDisconnect" Content="断开连接" Margin="5" Click="Disconnect_Click" Width="80" IsEnabled="False"/> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0,10"> <TextBlock Text="连接状态:" Margin="5" VerticalAlignment="Center" Width="60"/> <TextBlock x:Name="tbConnectionStatus" Text="未连接" Margin="5" VerticalAlignment="Center" FontWeight="Bold" Foreground="Red"/> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0,5"> <TextBlock Text="心跳状态:" Margin="5" VerticalAlignment="Center" Width="60"/> <TextBlock x:Name="tbHeartbeatStatus" Text="未连接" Margin="5" VerticalAlignment="Center" FontWeight="Bold" Foreground="Gray"/> </StackPanel> </StackPanel> </GroupBox> </Border> <!-- 折叠/展开按钮 --> <Button x:Name="btnTogglePanel" Grid.Column="0" Content="▶" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5,50,0,0" Width="25" Height="25" FontSize="10" Background="#FFE0E0E0" Click="TogglePanel_Click" ToolTip="展开PLC设置"/> <!-- 左侧区域 --> <Grid Grid.Column="1" Margin="5,0,0,0"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="220"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!-- 电机监控配置 --> <GroupBox Grid.Row="0" Header="电机监控点位配置" Margin="0,5,5,3"> <StackPanel Orientation="Horizontal"> <TextBlock Text="点位名称:" VerticalAlignment="Center" Margin="5"/> <TextBox x:Name="txtPointName" Width="120" Margin="5"/> <TextBlock Text="点位地址:" VerticalAlignment="Center" Margin="5"/> <TextBox x:Name="txtPointAddress" Width="80" Margin="5"/> <TextBlock Text="标准值:" VerticalAlignment="Center" Margin="5"/> <TextBox x:Name="txtStandardValue" Width="80" Margin="5"/> <Button x:Name="btnAddPoint" Content="添加点位" Margin="5" Click="AddMonitorPoint_Click" Width="80"/> <Button x:Name="btnSaveMotorConfig" Content="保存配置" Margin="5" Click="SaveMotorConfig_Click" Width="80"/> <Button x:Name="btnLoadMotorConfig" Content="加载配置" Margin="5" Click="LoadMotorConfig_Click" Width="80"/> </StackPanel> </GroupBox> <!-- 电机监控数据表格 --> <GroupBox Grid.Row="1" Header="电机点位实时监控" Margin="0,3,5,3"> <DataGrid ItemsSource="{Binding MotorDataList}" AutoGenerateColumns="False" Margin="5"> <DataGrid.Columns> <DataGridTextColumn Header="点位名称" Binding="{Binding PointName}" Width="120"/> <DataGridTextColumn Header="地址" Binding="{Binding Address}" Width="80"/> <DataGridTextColumn Header="标准值" Binding="{Binding StandardValue}" Width="80"/> <DataGridTextColumn Header="实际值" Binding="{Binding ActualValue}" Width="80"/> <DataGridTextColumn Header="状态" Binding="{Binding Status}" Width="100"> <DataGridTextColumn.ElementStyle> <Style TargetType="TextBlock"> <Setter Property="Foreground" Value="{Binding Status, Converter={StaticResource StatusColorConverter}}"/> <Setter Property="FontWeight" Value="Bold"/> </Style> </DataGridTextColumn.ElementStyle> </DataGridTextColumn> </DataGrid.Columns> </DataGrid> </GroupBox> <!-- 日志区域 --> <GroupBox Grid.Row="2" Header="通讯日志" Margin="0,3,5,5"> <TextBox x:Name="txtCommunicationLog" IsReadOnly="True" VerticalScrollBarVisibility="Auto" TextWrapping="Wrap" FontFamily="Consolas" Height="177"/> </GroupBox> <!--设备节拍时间/设备伺服电机负载率历史数据查看--> <GroupBox Grid.Row="3" Header="节拍时间和负载率历史数据查看" Margin="0,3,5,5"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" Margin="5"> <TextBlock Text="数据类型:" VerticalAlignment="Center" Margin="5"/> <ComboBox x:Name="cmbDataType" Width="100" Margin="5"> <ComboBoxItem Content="节拍时间"/> <ComboBoxItem Content="负载率"/> </ComboBox> <TextBlock Text="选择日期:" VerticalAlignment="Center" Margin="5"/> <DatePicker x:Name="dpHistoryDate" Width="120" Margin="5"/> <Button x:Name="btnLoadHistory" Content="加载历史数据" Margin="5" Click="LoadHistoryData_Click" Width="100"/> <Button x:Name="btnClearHistory" Content="清除图表" Margin="5" Click="ClearHistoryChart_Click" Width="80"/> </StackPanel> <Canvas x:Name="canvasHistoryChart" Grid.Row="1" Background="White" Margin="5"/> </Grid> </GroupBox> </Grid> <!-- 右侧区域 --> <Grid Grid.Column="2" Margin="5,0,5,0"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!-- 节拍监控配置 --> <GroupBox Grid.Row="0" Header="节拍时间监控配置" Margin="5,5,0,3"> <StackPanel Orientation="Horizontal"> <TextBlock Text="部位名称:" VerticalAlignment="Center" Margin="5"/> <TextBox x:Name="txtSectionName" Width="120" Margin="5"/> <TextBlock Text="监控地址:" VerticalAlignment="Center" Margin="5"/> <TextBox x:Name="txtSectionAddress" Width="80" Margin="5"/> <Button x:Name="btnAddSection" Content="添加部位" Margin="5" Click="AddSection_Click" Width="80"/> <Button x:Name="btnSaveRhythmConfig" Content="保存配置" Margin="5" Click="SaveRhythmConfig_Click" Width="80"/> <Button x:Name="btnLoadRhythmConfig" Content="加载配置" Margin="5" Click="LoadRhythmConfig_Click" Width="80"/> </StackPanel> </GroupBox> <!-- 节拍时间图表 --> <GroupBox Grid.Row="1" Header="设备节拍时间监控" Margin="5,3,0,3"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <Canvas x:Name="canvasRhythmChart" Background="White"/> <StackPanel Grid.Column="1" Orientation="Vertical" VerticalAlignment="Center" Margin="20,0,10,0"> <Rectangle Width="20" Height="10" Fill="Green" Margin="0,5"/> <TextBlock Text="< 10s" Margin="0,5"/> <Rectangle Width="20" Height="10" Fill="YellowGreen" Margin="0,5"/> <TextBlock Text="10-20s" Margin="0,5"/> <Rectangle Width="20" Height="10" Fill="Orange" Margin="0,5"/> <TextBlock Text="20-30s" Margin="0,5"/> <Rectangle Width="20" Height="10" Fill="Red" Margin="0,5"/> <TextBlock Text="> 30s" Margin="0,5"/> </StackPanel> </Grid> </GroupBox> <!-- 负载监控配置 --> <GroupBox Grid.Row="2" Header="伺服电机负载率监控配置" Margin="5,3,0,3"> <StackPanel Orientation="Horizontal"> <TextBlock Text="电机名称:" VerticalAlignment="Center" Margin="5"/> <TextBox x:Name="txtMotorName" Width="120" Margin="5"/> <TextBlock Text="监控地址:" VerticalAlignment="Center" Margin="5"/> <TextBox x:Name="txtMotorAddress" Width="80" Margin="5"/> <Button x:Name="btnAddMotor" Content="添加电机" Margin="5" Click="AddMotor_Click" Width="80"/> <Button x:Name="btnSaveLoadConfig" Content="保存配置" Margin="5" Click="SaveLoadConfig_Click" Width="80"/> <Button x:Name="btnLoadLoadConfig" Content="加载配置" Margin="5" Click="LoadLoadConfig_Click" Width="80"/> </StackPanel> </GroupBox> <!-- 负载率图表 --> <GroupBox Grid.Row="3" Header="伺服电机负载率监控" Margin="5,3,0,5"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <Canvas x:Name="canvasLoadChart" Background="White"/> <StackPanel Grid.Column="1" Orientation="Vertical" VerticalAlignment="Center" Margin="20,0,10,0"> <Rectangle Width="20" Height="10" Fill="Green" Margin="0,5"/> <TextBlock Text="< 80%" Margin="0,5"/> <Rectangle Width="20" Height="10" Fill="Red" Margin="0,5"/> <TextBlock Text="≥ 80%" Margin="0,5"/> </StackPanel> </Grid> </GroupBox> </Grid> </Grid> </Window> 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; 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 ObservableCollection<float> _lastRhythmValues = new ObservableCollection<float>(); private ObservableCollection<float> _lastLoadValues = new ObservableCollection<float>(); public MainWindow() { InitializeComponent(); _viewModel = new PlcMonitorViewModel(); DataContext = _viewModel; _plc = new ActUtlType64(); _readTimer = new DispatcherTimer(); _logClearTimer = new DispatcherTimer(); _heartbeatTimer = new DispatcherTimer(); // 初始化心跳定时器 InitializePlc(); InitializeLogClearTimer(); InitializeHeartbeatTimer(); // 初始化心跳定时器 InitializeDataSaving(); //数据保存 } 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.OpenOrCreate和FileShare.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) { AddLog("节拍数据写入器未初始化,跳过保存"); return; } // 写入CSV数据 string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); string csvLine = $"{timestamp},{data.Name},{data.Address},{value}"; _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) { AddLog("负载数据写入器未初始化,跳过保存"); return; } // 写入CSV数据 string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); string csvLine = $"{timestamp},{data.Name},{data.Address},{value}"; _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数据"); } // 异步读取PLC数据 private async System.Threading.Tasks.Task ReadPlcDataAsync() { if (!_isConnected) return; try { await System.Threading.Tasks.Task.Run(() => { // 读取电机点位数据 ReadMotorData(); // 读取节拍时间数据 ReadRhythmData(); // 读取负载率数据 ReadLoadData(); }); } 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); if (result == 0) { float actualValue = value; Application.Current.Dispatcher.Invoke(() => { var data = _viewModel.MotorDataList[i]; data.ActualValue = actualValue; // 检查值是否发生变化 if (_lastMotorValues.Count <= i) { _lastMotorValues.Add(actualValue); data.Status = (actualValue == data.StandardValue) ? "正常" : "异常"; if (data.Status == "异常") { AddLog($"点位 {data.PointName} 异常: 实际值 {actualValue} ≠ 标准值 {data.StandardValue}"); } else { AddLog("点位数据已恢复正常"); } } else if (_lastMotorValues[i] != actualValue) { data.Status = (actualValue == data.StandardValue) ? "正常" : "异常"; if (data.Status == "异常") { AddLog($"点位 {data.PointName} 异常: 实际值 {actualValue} ≠ 标准值 {data.StandardValue}"); } else { AddLog("点位数据已恢复正常"); } _lastMotorValues[i] = actualValue; } }); } 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; Application.Current.Dispatcher.Invoke(() => { _viewModel.RhythmDataList[i].Value = rhythmValue; DrawBarChart(canvasRhythmChart, _viewModel.RhythmDataList, true, "rhythm"); }); // 立即保存节拍数据 SaveRhythmData(_viewModel.RhythmDataList[i], rhythmValue); } else { AddLog($"读取节拍数据 {_viewModel.RhythmDataList[i].Name} 失败,错误代码: {result}"); } } } // 读取负载率数据并绘制柱状图 // 在读取负载数据后立即保存 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; Application.Current.Dispatcher.Invoke(() => { _viewModel.LoadDataList[i].Value = value; DrawBarChart(canvasLoadChart, _viewModel.LoadDataList, true, "load"); }); // 立即保存负载数据 SaveLoadData(_viewModel.LoadDataList[i], loadValue); } else { AddLog($"读取负载数据 {_viewModel.LoadDataList[i].Name} 失败,错误代码: {result}"); } } } // 绘制柱状图 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 void LoadHistoryData_Click(object sender, RoutedEventArgs e) { // 安全获取 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 (dpHistoryDate.SelectedDate == null) { MessageBox.Show("请选择日期"); return; } DateTime selectedDate = dpHistoryDate.SelectedDate.Value; LoadAndDrawHistoryData(dataType!, selectedDate); // 使用 ! 表示我们知道它不是 null } private void LoadAndDrawHistoryData(string dataType, DateTime date) { try { // 添加空值检查 if (string.IsNullOrEmpty(dataType)) { MessageBox.Show("数据类型无效"); return; } canvasHistoryChart.Children.Clear(); AddLog($"开始加载{dataType}历史数据 ({date:yyyy-MM-dd})"); // 根据数据类型构建文件路径 string fileName = dataType == "节拍时间" ? $"rhythm_{date:yyyyMMdd}.csv" : $"load_{date:yyyyMMdd}.csv"; string filePath = Path.Combine(_saveDirectory, fileName); // 文件不存在处理 if (!File.Exists(filePath)) { MessageBox.Show($"找不到{dataType}的历史数据文件"); AddLog($"未找到历史数据文件: {filePath}"); return; } // 读取数据 var lines = File.ReadAllLines(filePath).Skip(1); // 跳过标题行 var dataPoints = new List<Tuple<DateTime, float>>(); // 修改时间解析部分 foreach (var line in lines) { var parts = line.Split(&#39;,&#39;); if (parts.Length < 4) continue; // 使用确切格式解析时间戳 if (DateTime.TryParseExact(parts[0], "yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime timestamp) && float.TryParse(parts[3], out float value)) { dataPoints.Add(new Tuple<DateTime, float>(timestamp, value)); } } if (dataPoints.Count == 0) { MessageBox.Show("没有找到有效数据"); return; } // 绘制图表 DrawHistoryChart(dataPoints, dataType); AddLog($"已加载{dataType}历史数据: {dataPoints.Count}条记录"); } catch (Exception ex) { MessageBox.Show($"加载历史数据失败: {ex.Message}"); AddLog($"加载历史数据出错: {ex.Message}"); } } private void DrawHistoryChart(List<Tuple<DateTime, float>> dataPoints, string dataType) { double canvasWidth = canvasHistoryChart.ActualWidth; double canvasHeight = canvasHistoryChart.ActualHeight; const double margin = 30; // 清除现有内容 canvasHistoryChart.Children.Clear(); if (canvasWidth <= 0 || canvasHeight <= 0) return; // 计算最小/最大时间和值 var minTime = dataPoints.Min(p => p.Item1); var maxTime = dataPoints.Max(p => p.Item1); var minValue = dataPoints.Min(p => p.Item2); var maxValue = dataPoints.Max(p => p.Item2); double timeRange = (maxTime - minTime).TotalSeconds; double valueRange = maxValue - minValue; if (valueRange == 0) valueRange = 1; // 防止除零 // 绘制坐标轴 DrawHistoryAxis(minTime, maxTime, minValue, maxValue, canvasWidth, canvasHeight, margin); // 绘制数据点 for (int i = 0; i < dataPoints.Count - 1; i++) { var p1 = dataPoints[i]; var p2 = dataPoints[i + 1]; double x1 = margin + ((p1.Item1 - minTime).TotalSeconds / timeRange) * (canvasWidth - 2 * margin); double y1 = canvasHeight - margin - ((p1.Item2 - minValue) / valueRange) * (canvasHeight - 2 * margin); double x2 = margin + ((p2.Item1 - minTime).TotalSeconds / timeRange) * (canvasWidth - 2 * margin); double y2 = canvasHeight - margin - ((p2.Item2 - minValue) / valueRange) * (canvasHeight - 2 * margin); // 绘制连线 var line = new Line { X1 = x1, Y1 = y1, X2 = x2, Y2 = y2, Stroke = dataType == "节拍时间" ? Brushes.Blue : Brushes.Red, StrokeThickness = 2 }; canvasHistoryChart.Children.Add(line); // 绘制数据点 var ellipse = new Ellipse { Width = 6, Height = 6, Fill = Brushes.Black }; Canvas.SetLeft(ellipse, x1 - 3); Canvas.SetTop(ellipse, y1 - 3); canvasHistoryChart.Children.Add(ellipse); } // 添加标题 var title = new TextBlock { Text = $"{dataType}历史数据 ({dataPoints.Count}条记录)", FontWeight = FontWeights.Bold, FontSize = 14, Foreground = Brushes.Black }; Canvas.SetLeft(title, canvasWidth / 2 - 50); Canvas.SetTop(title, 5); canvasHistoryChart.Children.Add(title); } private void DrawHistoryAxis(DateTime minTime, DateTime maxTime, double minValue, double maxValue, double canvasWidth, double canvasHeight, double margin) { // 绘制X轴(时间轴) var xAxis = new Line { X1 = margin, Y1 = canvasHeight - margin, X2 = canvasWidth - margin, Y2 = canvasHeight - margin, Stroke = Brushes.Black, StrokeThickness = 2 }; canvasHistoryChart.Children.Add(xAxis); // 绘制Y轴(数值轴) var yAxis = new Line { X1 = margin, Y1 = margin, X2 = margin, Y2 = canvasHeight - margin, Stroke = Brushes.Black, StrokeThickness = 2 }; canvasHistoryChart.Children.Add(yAxis); // 添加时间刻度 TimeSpan timeInterval = TimeSpan.FromMinutes(30); // 每30分钟一个刻度 DateTime current = minTime; while (current <= maxTime) { double ratio = (current - minTime).TotalSeconds / (maxTime - minTime).TotalSeconds; double x = margin + ratio * (canvasWidth - 2 * margin); // 刻度线 var tick = new Line { X1 = x, Y1 = canvasHeight - margin, X2 = x, Y2 = canvasHeight - margin + 5, Stroke = Brushes.Black }; canvasHistoryChart.Children.Add(tick); // 时间标签 var label = new TextBlock { Text = current.ToString("HH:mm"), FontSize = 10, Foreground = Brushes.Black }; Canvas.SetLeft(label, x - 15); Canvas.SetTop(label, canvasHeight - margin + 10); canvasHistoryChart.Children.Add(label); current = current.Add(timeInterval); } // 添加数值刻度 double valueInterval = (maxValue - minValue) / 5; for (int i = 0; i <= 5; i++) { double value = minValue + i * valueInterval; double y = canvasHeight - margin - (i / 5.0) * (canvasHeight - 2 * margin); // 刻度线 var tick = new Line { X1 = margin, Y1 = y, X2 = margin - 5, Y2 = y, Stroke = Brushes.Black }; canvasHistoryChart.Children.Add(tick); // 数值标签 var label = new TextBlock { Text = value.ToString("F1"), FontSize = 10, Foreground = Brushes.Black }; Canvas.SetRight(label, canvasWidth - margin + 5); Canvas.SetTop(label, y - 8); canvasHistoryChart.Children.Add(label); } } 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盘[1](@ref) 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(); 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(); 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; } _viewModel.RhythmDataList.Add(new ChartData { Name = txtSectionName.Text, Address = txtSectionAddress.Text, Value = 0 }); txtSectionName.Text = ""; txtSectionAddress.Text = ""; } // 添加负载监控电机 private void AddMotor_Click(object sender, RoutedEventArgs e) { if (string.IsNullOrWhiteSpace(txtMotorName.Text) || string.IsNullOrWhiteSpace(txtMotorAddress.Text)) { MessageBox.Show("请填写完整的电机信息"); return; } _viewModel.LoadDataList.Add(new ChartData { Name = txtMotorName.Text, Address = txtMotorAddress.Text, Value = 0 }); txtMotorName.Text = ""; txtMotorAddress.Text = ""; } // 窗口关闭时清理资源 private void Window_Closed(object sender, EventArgs e) { if (_isConnected) { _plc.Close(); _readTimer.Stop(); // 关闭文件流(添加空值检查) _rhythmDataWriter?.Close(); _loadDataWriter?.Close(); AddLog("数据文件已关闭"); } // 停止所有定时器 _logClearTimer?.Stop(); _heartbeatTimer?.Stop(); } } }上面代码运行后报以下错误:加载历史数据出错: The process cannot access the file &#39;D:\PLC_Monitor_Data\rhythm_20251004.csv&#39; because it is being used by another process.
10-05
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值