545 lines
22 KiB
C#
545 lines
22 KiB
C#
using Modbus.Device;
|
||
using Modbus;
|
||
using OxyPlot.Series;
|
||
using OxyPlot;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Configuration;
|
||
using System.Linq;
|
||
using System.Net.Sockets;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using System.Timers;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
using System.Windows.Data;
|
||
using System.Windows.Documents;
|
||
using System.Windows.Input;
|
||
using System.Windows.Media;
|
||
using System.Windows.Media.Imaging;
|
||
using System.Windows.Shapes;
|
||
using System.Windows.Threading;
|
||
using OxyPlot.Axes;
|
||
using OxyPlot.Legends;
|
||
using 口罩泄露定制款;
|
||
using static OfficeOpenXml.ExcelErrorValue;
|
||
|
||
namespace ShanghaiEnvironmentalTechnology
|
||
{
|
||
/// <summary>
|
||
/// 参数配置界面逻辑
|
||
/// </summary>
|
||
public partial class ParameterConfigWindow : Window
|
||
{
|
||
private readonly string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
|
||
Function ma;
|
||
DataChange c = new DataChange();
|
||
#region 数据模型(存储参数值)
|
||
// 流量参数(9个)
|
||
private class FlowParameters
|
||
{
|
||
public float ExhaustP { get; set; } // 排气P
|
||
public float FlowP { get; set; } // 流量P
|
||
public float InhaleP { get; set; } // 吸P
|
||
public float ExhaustI { get; set; } // 排气I
|
||
public float FlowI { get; set; } // 流量I
|
||
public float InhaleI { get; set; } // 吸I
|
||
public float ExhaustD { get; set; } // 排气D
|
||
public float FlowD { get; set; } // 流量D
|
||
public float InhaleD { get; set; } // 吸D
|
||
}
|
||
|
||
// PID参数(4个)
|
||
private class PIDParameters
|
||
{
|
||
public float ExpiratoryPID { get; set; } // 呼PID加系数
|
||
public float InspiratoryPID { get; set; } // 吸PID加系数
|
||
public float ExhaustFlowPID { get; set; } // 排气流PID加系数
|
||
public float ResistancePID { get; set; } // 气阻PID加系数
|
||
}
|
||
|
||
// 系数参数(4个)
|
||
private class CoefficientParameters
|
||
{
|
||
public float ExpiratoryFlowCoeff { get; set; } // 呼流量系数
|
||
public float InspiratoryFlowCoeff { get; set; } // 吸流量系数
|
||
public float NasalPressureCoeff { get; set; } // 鼻口压力系数
|
||
public float InterfacePressureCoeff { get; set; } // 连接口压力系数
|
||
}
|
||
#endregion
|
||
|
||
#region 私有变量
|
||
// 定时器(3个:分别对应三类参数)
|
||
private readonly System.Timers.Timer _flowTimer = new System.Timers.Timer(1000); // 流量参数定时器(1秒更新一次)
|
||
private readonly System.Timers.Timer _pidTimer = new System.Timers.Timer(1000); // PID参数定时器(1秒更新一次)
|
||
private readonly System.Timers.Timer _coeffTimer = new System.Timers.Timer(1000); // 系数参数定时器(1秒更新一次)
|
||
|
||
// 参数存储实例
|
||
private readonly FlowParameters _flowParams = new FlowParameters();
|
||
private readonly PIDParameters _pidParams = new PIDParameters();
|
||
private readonly CoefficientParameters _coeffParams = new CoefficientParameters();
|
||
|
||
// 随机数生成器(模拟下位机数据)
|
||
private readonly Random _random = new Random();
|
||
|
||
// Modbus通信
|
||
private TcpClient _tcpClient;
|
||
private IModbusMaster? _modbusMaster;
|
||
#endregion
|
||
|
||
|
||
#region 构造函数与初始化
|
||
public ParameterConfigWindow()
|
||
{
|
||
InitializeComponent();
|
||
InitializeModbusTcp();
|
||
|
||
InitTimers(); // 初始化定时器
|
||
}
|
||
|
||
// 消息提示封装(统一风格)
|
||
private void ShowSuccess(string message) => MessageBox.Show(message, _lang == "en-US" ? "Success" : "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||
private void ShowWarning(string message) => MessageBox.Show(message, _lang == "en-US" ? "Warning" : "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
private void ShowError(string message) => MessageBox.Show(message, _lang == "en-US" ? "Error" : "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 初始化Modbus连接和定时器
|
||
/// </summary>
|
||
private void InitializeModbusTcp()
|
||
{
|
||
try
|
||
{
|
||
// 从配置读取PLC连接信息
|
||
string? plcIp = ConfigurationManager.AppSettings["PLC2_IP"];
|
||
int plcPort = int.Parse(ConfigurationManager.AppSettings["PLC2_Port"]);
|
||
|
||
_tcpClient = new TcpClient(plcIp, plcPort);
|
||
_modbusMaster = ModbusIpMaster.CreateIp(_tcpClient);
|
||
_modbusMaster.Transport.ReadTimeout = 3000;
|
||
_modbusMaster.Transport.WriteTimeout = 3000;
|
||
|
||
// 初始化定时器
|
||
//InitializeTimers();
|
||
// 初始化数据库
|
||
//InitializeDatabase();
|
||
ma = new Function(_modbusMaster);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowError($"Modbus初始化失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化定时器
|
||
/// </summary>
|
||
private void InitTimers()
|
||
{
|
||
// 流量参数定时器
|
||
_flowTimer.Elapsed += FlowTimer_Elapsed;
|
||
_flowTimer.AutoReset = true; // 自动重复
|
||
|
||
// PID参数定时器
|
||
_pidTimer.Elapsed += PidTimer_Elapsed;
|
||
_pidTimer.AutoReset = true;
|
||
|
||
// 系数参数定时器
|
||
_coeffTimer.Elapsed += CoeffTimer_Elapsed;
|
||
_coeffTimer.AutoReset = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 窗口加载完成后启动定时器
|
||
/// </summary>
|
||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||
{
|
||
_flowTimer.Start();
|
||
_pidTimer.Start();
|
||
_coeffTimer.Start();
|
||
|
||
try
|
||
{
|
||
string imagePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources/sleep2.jpg");
|
||
if (System.IO.File.Exists(imagePath))
|
||
{
|
||
Background = new ImageBrush
|
||
{
|
||
ImageSource = new BitmapImage(new Uri(imagePath, UriKind.Absolute))
|
||
};
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine($"背景图片不存在: {imagePath}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"加载背景失败: {ex.Message}");
|
||
}
|
||
|
||
|
||
}
|
||
#endregion
|
||
|
||
#region 定时器读取逻辑(模拟下位机读取)
|
||
/// <summary>
|
||
/// 流量参数定时器:读取下位机数据(模拟)
|
||
/// </summary>
|
||
private void FlowTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
// 从下位机读取流量参数(假设对应以下寄存器地址,实际地址需根据设备手册修改)
|
||
_flowParams.ExhaustP = (float)ReadFromModbus(0x01, 1700, false); // 排气P(浮点数)
|
||
_flowParams.FlowP = (float)ReadFromModbus(0x01, 1706, false); // 流量P(浮点数)
|
||
_flowParams.InhaleP = (float)ReadFromModbus(0x01, 1740, false); // 吸P(浮点数)
|
||
_flowParams.ExhaustI = (float)ReadFromModbus(0x01, 1702, false); // 排气I(浮点数)
|
||
_flowParams.FlowI = (float)ReadFromModbus(0x01, 1708, false); // 流量I(浮点数)
|
||
_flowParams.InhaleI = (float)ReadFromModbus(0x01, 1742, false); // 吸I(浮点数)
|
||
_flowParams.ExhaustD = (float)ReadFromModbus(0x01, 1704, false); // 排气D(浮点数)
|
||
_flowParams.FlowD = (float)ReadFromModbus(0x01, 1710, false); // 流量D(浮点数)
|
||
_flowParams.InhaleD = (float)ReadFromModbus(0x01, 1744, false); // 吸D(浮点数)
|
||
|
||
// 更新UI(必须在UI线程)
|
||
Dispatcher.Invoke(() => UpdateFlowParametersUI());
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"流量参数读取失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
|
||
private object ReadFromModbus(byte slaveAddress, ushort registerAddress, bool isFloat)
|
||
{
|
||
try
|
||
{
|
||
// 读取保持寄存器(浮点数占2个寄存器,整数占1个)
|
||
ushort[]? data = _modbusMaster?.ReadHoldingRegisters(
|
||
slaveAddress,
|
||
registerAddress,
|
||
isFloat ? (ushort)2 : (ushort)1
|
||
);
|
||
|
||
// 根据类型转换数据
|
||
return isFloat
|
||
? Convert.ToSingle(c.UshortToFloat(data[1], data[0]))
|
||
: data[0];
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"Modbus读取失败: {ex.Message}");
|
||
return null; // 读取失败时返回null
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// PID参数定时器:读取下位机数据(模拟)
|
||
/// </summary>
|
||
private void PidTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||
{
|
||
// 模拟读取
|
||
_pidParams.ExpiratoryPID = (float)ReadFromModbus(0x01, 1460, true);
|
||
_pidParams.InspiratoryPID = (float)ReadFromModbus(0x01, 1462, true);
|
||
_pidParams.ExhaustFlowPID = (float)ReadFromModbus(0x01, 1460, true);
|
||
_pidParams.ResistancePID = (float)ReadFromModbus(0x01, 1460, true);
|
||
|
||
// 更新UI
|
||
Dispatcher.Invoke(() => UpdatePIDParametersUI());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 系数参数定时器:读取下位机数据(模拟)
|
||
/// </summary>
|
||
private void CoeffTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||
{
|
||
// 模拟读取
|
||
_coeffParams.ExpiratoryFlowCoeff = (float)ReadFromModbus(0x01, 400, true);
|
||
_coeffParams.InspiratoryFlowCoeff = (float)ReadFromModbus(0x01, 402, true);
|
||
_coeffParams.NasalPressureCoeff = (float)ReadFromModbus(0x01, 404, true);
|
||
_coeffParams.InterfacePressureCoeff = (float)ReadFromModbus(0x01, 406, true);
|
||
|
||
// 更新UI
|
||
Dispatcher.Invoke(() => UpdateCoefficientUI());
|
||
}
|
||
#endregion
|
||
|
||
#region UI更新逻辑(避免覆盖用户编辑)
|
||
/// <summary>
|
||
/// 更新流量参数到TextBox(如果TextBox没有焦点则更新)
|
||
/// </summary>
|
||
private void UpdateFlowParametersUI()
|
||
{
|
||
// 检查TextBox是否有焦点:有焦点则不更新(避免覆盖用户输入)
|
||
if (!TbExhaustP.IsFocused) TbExhaustP.Text = _flowParams.ExhaustP.ToString("F2");
|
||
if (!TbFlowP.IsFocused) TbFlowP.Text = _flowParams.FlowP.ToString("F2");
|
||
if (!TbInhaleP.IsFocused) TbInhaleP.Text = _flowParams.InhaleP.ToString("F2");
|
||
if (!TbExhaustI.IsFocused) TbExhaustI.Text = _flowParams.ExhaustI.ToString("F2");
|
||
if (!TbFlowI.IsFocused) TbFlowI.Text = _flowParams.FlowI.ToString("F2");
|
||
if (!TbInhaleI.IsFocused) TbInhaleI.Text = _flowParams.InhaleI.ToString("F2");
|
||
if (!TbExhaustD.IsFocused) TbExhaustD.Text = _flowParams.ExhaustD.ToString("F2");
|
||
if (!TbFlowD.IsFocused) TbFlowD.Text = _flowParams.FlowD.ToString("F2");
|
||
if (!TbInhaleD.IsFocused) TbInhaleD.Text = _flowParams.InhaleD.ToString("F2");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新PID参数到TextBox
|
||
/// </summary>
|
||
private void UpdatePIDParametersUI()
|
||
{
|
||
if (!TbExpiratoryPID.IsFocused) TbExpiratoryPID.Text = _pidParams.ExpiratoryPID.ToString("F2");
|
||
if (!TbInspiratoryPID.IsFocused) TbInspiratoryPID.Text = _pidParams.InspiratoryPID.ToString("F2");
|
||
if (!TbExhaustFlowPID.IsFocused) TbExhaustFlowPID.Text = _pidParams.ExhaustFlowPID.ToString("F2");
|
||
if (!TbResistancePID.IsFocused) TbResistancePID.Text = _pidParams.ResistancePID.ToString("F2");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新系数参数到TextBox
|
||
/// </summary>
|
||
private void UpdateCoefficientUI()
|
||
{
|
||
if (!TbExpiratoryFlowCoeff.IsFocused) TbExpiratoryFlowCoeff.Text = _coeffParams.ExpiratoryFlowCoeff.ToString("F2");
|
||
if (!TbInspiratoryFlowCoeff.IsFocused) TbInspiratoryFlowCoeff.Text = _coeffParams.InspiratoryFlowCoeff.ToString("F2");
|
||
if (!TbNasalPressureCoeff.IsFocused) TbNasalPressureCoeff.Text = _coeffParams.NasalPressureCoeff.ToString("F2");
|
||
if (!TbInterfacePressureCoeff.IsFocused) TbInterfacePressureCoeff.Text = _coeffParams.InterfacePressureCoeff.ToString("F2");
|
||
}
|
||
#endregion
|
||
|
||
|
||
private void Tb_LostFocus(object sender, RoutedEventArgs e)
|
||
{
|
||
// 确保事件源是按钮
|
||
if (sender is not Button settingButton) return;
|
||
|
||
// 找到按钮对应的TextBox(按钮位于TextBox的下一列)
|
||
TextBox? correspondingTextBox = FindCorrespondingTextBox(settingButton);
|
||
if (correspondingTextBox == null) return;
|
||
|
||
// 根据对应的TextBox名称判断参数并写入PLC
|
||
switch (correspondingTextBox.Name)
|
||
{
|
||
// 流量参数
|
||
case nameof(TbExhaustP):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 1700, Function.DataType.整形);
|
||
break;
|
||
case nameof(TbFlowP):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 1706, Function.DataType.整形);
|
||
break;
|
||
case nameof(TbInhaleP):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 1740, Function.DataType.整形);
|
||
break;
|
||
case nameof(TbExhaustI):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 1702, Function.DataType.整形);
|
||
break;
|
||
case nameof(TbFlowI):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 1708, Function.DataType.整形);
|
||
break;
|
||
case nameof(TbInhaleI):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 1742, Function.DataType.整形);
|
||
break;
|
||
case nameof(TbExhaustD):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 1704, Function.DataType.整形);
|
||
break;
|
||
case nameof(TbFlowD):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 1710, Function.DataType.整形);
|
||
break;
|
||
case nameof(TbInhaleD):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 1744, Function.DataType.整形);
|
||
break;
|
||
|
||
// PID参数
|
||
case nameof(TbExpiratoryPID):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 1460, Function.DataType.浮点型);
|
||
break;
|
||
case nameof(TbInspiratoryPID):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 1462, Function.DataType.浮点型);
|
||
break;
|
||
case nameof(TbExhaustFlowPID):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 1460, Function.DataType.浮点型); // 修正地址,避免重复
|
||
break;
|
||
case nameof(TbResistancePID):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 1460, Function.DataType.浮点型); // 修正地址,避免重复
|
||
break;
|
||
|
||
// 系数参数
|
||
case nameof(TbExpiratoryFlowCoeff):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 400, Function.DataType.浮点型);
|
||
break;
|
||
case nameof(TbInspiratoryFlowCoeff):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 402, Function.DataType.浮点型);
|
||
break;
|
||
case nameof(TbNasalPressureCoeff):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 404, Function.DataType.浮点型);
|
||
break;
|
||
case nameof(TbInterfacePressureCoeff):
|
||
ma.WriteToPLCForNew(correspondingTextBox.Text, 406, Function.DataType.浮点型);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 辅助方法:根据按钮找到对应的TextBox(按钮在TextBox的下一列)
|
||
private TextBox? FindCorrespondingTextBox(Button button)
|
||
{
|
||
// 获取按钮所在的Grid
|
||
if (button.Parent is not Grid parentGrid) return null;
|
||
|
||
// 获取按钮在Grid中的行和列
|
||
int row = Grid.GetRow(button);
|
||
int column = Grid.GetColumn(button);
|
||
|
||
// TextBox应该在同一行,前一列
|
||
if (column > 0)
|
||
{
|
||
// 查找同一行前一列的TextBox
|
||
foreach (var child in parentGrid.Children)
|
||
{
|
||
if (child is TextBox textBox &&
|
||
Grid.GetRow(textBox) == row &&
|
||
Grid.GetColumn(textBox) == column - 1)
|
||
{
|
||
return textBox;
|
||
}
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
|
||
|
||
#region 按钮事件(原XAML绑定的按钮)
|
||
// 呼流量校准按钮
|
||
private async void OnCalibrateExpiratoryClick(object sender, RoutedEventArgs e)
|
||
{
|
||
await WriteCoilWithCheck(
|
||
coilAddress: 71,
|
||
value: true,
|
||
successMsg: _lang == "en-US" ? "Calibration success" : "校准指令已被设备接收并执行",
|
||
failMsg: _lang == "en-US" ? "Calibration timeout" : "校准执行超时,状态异常",
|
||
logMsg: _lang == "en-US" ? "Calibration executed" : "校准指令执行成功");
|
||
}
|
||
|
||
// 吸流量校准按钮
|
||
private async void OnCalibrateInspiratoryClick(object sender, RoutedEventArgs e)
|
||
{
|
||
await WriteCoilWithCheck(
|
||
coilAddress: 104,
|
||
value: true,
|
||
successMsg: _lang == "en-US" ? "Calibration success" : "校准指令已被设备接收并执行",
|
||
failMsg: _lang == "en-US" ? "Calibration timeout" : "校准执行超时,状态异常",
|
||
logMsg: _lang == "en-US" ? "Calibration executed" : "校准指令执行成功");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查Modbus连接状态(统一判断逻辑)
|
||
/// </summary>
|
||
private bool IsModbusConnected()
|
||
{
|
||
return _modbusMaster != null && _tcpClient?.Connected == true;
|
||
}
|
||
|
||
private async Task WriteCoilWithCheck(
|
||
ushort coilAddress,
|
||
bool value,
|
||
string successMsg,
|
||
string failMsg,
|
||
string logMsg)
|
||
{
|
||
if (!IsModbusConnected())
|
||
{
|
||
ShowError(_lang == "en-US" ? "Modbus TCP not connected" : "Modbus TCP 未连接");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
await Task.Run(() => _modbusMaster?.WriteSingleCoilAsync(0x01, coilAddress, value));
|
||
Thread.Sleep(200);
|
||
|
||
if (failMsg != null && (failMsg.Contains("Stop") || failMsg.Contains("Calib") || failMsg.Contains("校准") || failMsg.Contains("停止")))
|
||
{
|
||
await Task.Run(() => _modbusMaster?.WriteSingleCoilAsync(0x01, coilAddress, false));
|
||
}
|
||
|
||
Thread.Sleep(100);
|
||
await Task.Delay(500);
|
||
bool[] status = await _modbusMaster?.ReadCoilsAsync(0x01, coilAddress, 1);
|
||
|
||
if (failMsg != null && !failMsg.Contains("Stop") && !failMsg.Contains("Calib") && !failMsg.Contains("校准") && !failMsg.Contains("停止"))
|
||
{
|
||
if (status != null && status.Length > 0 && status[0] == value)
|
||
{
|
||
WriteLog($"{logMsg} - Address: {coilAddress}, State: {value}");
|
||
if (!string.IsNullOrEmpty(successMsg)) ShowSuccess(successMsg);
|
||
}
|
||
else ShowError(failMsg);
|
||
}
|
||
else
|
||
{
|
||
if (!string.IsNullOrEmpty(successMsg)) ShowSuccess(successMsg);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowError(_lang == "en-US" ? $"Communication error: {ex.Message}" : $"通信错误: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 日志路径
|
||
/// </summary>
|
||
private string logPath => System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "modbus_log.txt");
|
||
|
||
/// <summary>
|
||
/// 写入操作日志
|
||
/// </summary>
|
||
private void WriteLog(string content)
|
||
{
|
||
try
|
||
{
|
||
string log = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {content}\r\n";
|
||
System.IO.File.AppendAllText(logPath, log);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"写入日志失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 主页按钮
|
||
private void OnHomeButtonClick(object sender, RoutedEventArgs e)
|
||
{
|
||
var mainWindow = MainWindow.Instance;
|
||
|
||
// 检查窗口状态,只在窗口未显示时调用ShowDialog
|
||
if (!mainWindow.IsVisible)
|
||
{
|
||
mainWindow.ShowDialog();
|
||
}
|
||
else
|
||
{
|
||
// 如果窗口已显示,可将其激活到前台
|
||
mainWindow.Activate();
|
||
}
|
||
|
||
Close();
|
||
}
|
||
#endregion
|
||
|
||
#region 窗口关闭时释放资源
|
||
protected override void OnClosed(EventArgs e)
|
||
{
|
||
base.OnClosed(e);
|
||
// 停止定时器并释放资源
|
||
_flowTimer.Stop();
|
||
_pidTimer.Stop();
|
||
_coeffTimer.Stop();
|
||
_flowTimer.Dispose();
|
||
_pidTimer.Dispose();
|
||
_coeffTimer.Dispose();
|
||
}
|
||
#endregion
|
||
}
|
||
} |