2026-02-12 17:03:23 +08:00
|
|
|
|
using Microsoft.VisualBasic;
|
|
|
|
|
|
using PetroleumViscosityTest.Controls;
|
|
|
|
|
|
using PetroleumViscosityTest.Models;
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
2026-02-24 15:09:57 +08:00
|
|
|
|
using System.IO.Ports;
|
2026-02-12 17:03:23 +08:00
|
|
|
|
using System.Linq;
|
2026-02-24 15:09:57 +08:00
|
|
|
|
using System.Timers;
|
2026-02-12 17:03:23 +08:00
|
|
|
|
using System.Windows;
|
|
|
|
|
|
using System.Windows.Controls;
|
|
|
|
|
|
|
|
|
|
|
|
namespace PetroleumViscosityTest
|
|
|
|
|
|
{
|
|
|
|
|
|
public partial class MainWindow : Window
|
|
|
|
|
|
{
|
|
|
|
|
|
private ViscosityTestData currentData;
|
|
|
|
|
|
private List<double> rawTimes = new List<double>();
|
2026-02-24 15:09:57 +08:00
|
|
|
|
// --- 新增 Modbus 通信相关字段 ---
|
|
|
|
|
|
private SerialPort serialPort;
|
|
|
|
|
|
private System.Timers.Timer readTimer;
|
|
|
|
|
|
private byte slaveId = 0x01; // 从机地址,根据实际修改
|
|
|
|
|
|
private readonly object portLock = new object();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private System.Diagnostics.Stopwatch stopwatch1 = new System.Diagnostics.Stopwatch();
|
|
|
|
|
|
private System.Diagnostics.Stopwatch stopwatch2 = new System.Diagnostics.Stopwatch();
|
|
|
|
|
|
private System.Diagnostics.Stopwatch stopwatch3 = new System.Diagnostics.Stopwatch();
|
|
|
|
|
|
private System.Diagnostics.Stopwatch stopwatch4 = new System.Diagnostics.Stopwatch();
|
|
|
|
|
|
// --------------------------------
|
2026-02-12 17:03:23 +08:00
|
|
|
|
|
|
|
|
|
|
public MainWindow()
|
|
|
|
|
|
{
|
|
|
|
|
|
InitializeComponent();
|
|
|
|
|
|
currentData = new ViscosityTestData();
|
|
|
|
|
|
dpTestDate.SelectedDate = DateTime.Today;
|
|
|
|
|
|
UpdateThermostatTime();
|
2026-02-24 15:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
// --- 新增:初始化串口通信 ---
|
|
|
|
|
|
InitSerialPort();
|
|
|
|
|
|
// --------------------------------
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void InitSerialPort()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
serialPort = new SerialPort("COM6", 9600, Parity.None, 8, StopBits.One); // 请根据实际COM口修改
|
|
|
|
|
|
serialPort.DataReceived += SerialPort_DataReceived;
|
|
|
|
|
|
serialPort.Open();
|
|
|
|
|
|
|
|
|
|
|
|
// 启动定时器,每隔 5 秒读取一次
|
|
|
|
|
|
readTimer = new System.Timers.Timer(2000);
|
|
|
|
|
|
readTimer.Elapsed += ReadTimer_Elapsed;
|
|
|
|
|
|
readTimer.AutoReset = true;
|
|
|
|
|
|
readTimer.Start();
|
|
|
|
|
|
|
|
|
|
|
|
txtStatus.Text = "串口已打开,正在读取数据...";
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show($"串口打开失败: {ex.Message}\n请检查COM口号及连接。", "通信错误", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
|
|
|
|
txtStatus.Text = "串口打开失败";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//private void ReadTimer_Elapsed(object sender, ElapsedEventArgs e)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// // 发送读保持寄存器指令:功能码 0x03,起始地址 0x0005,读取 2 个寄存器
|
|
|
|
|
|
// byte[] request = new byte[8];
|
|
|
|
|
|
// request[0] = slaveId;
|
|
|
|
|
|
// request[1] = 0x03; // 功能码
|
|
|
|
|
|
// request[2] = 0x00; // 起始地址高字节
|
|
|
|
|
|
// request[3] = 0x05; // 起始地址低字节 (0x0005)
|
|
|
|
|
|
// request[4] = 0x00; // 数量高字节
|
|
|
|
|
|
// request[5] = 0x02; // 数量低字节 (2 个寄存器)
|
|
|
|
|
|
// byte[] crc = CalculateCRC16(request, 6);
|
|
|
|
|
|
// request[6] = crc[0];
|
|
|
|
|
|
// request[7] = crc[1];
|
|
|
|
|
|
|
|
|
|
|
|
// lock (portLock)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// if (serialPort != null && serialPort.IsOpen)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// try
|
|
|
|
|
|
// {
|
|
|
|
|
|
// serialPort.Write(request, 0, request.Length);
|
|
|
|
|
|
// }
|
|
|
|
|
|
// catch (Exception ex)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// Dispatcher.Invoke(() => txtStatus.Text = $"发送失败: {ex.Message}");
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
//}
|
|
|
|
|
|
// 替换原有的 ReadTimer_Elapsed 方法
|
|
|
|
|
|
private void ReadTimer_Elapsed(object sender, ElapsedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (portLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (serialPort == null || !serialPort.IsOpen) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 从 0x0000 开始读取 20 个寄存器
|
|
|
|
|
|
byte[] request = new byte[8];
|
|
|
|
|
|
request[0] = slaveId;
|
|
|
|
|
|
request[1] = 0x03;
|
|
|
|
|
|
request[2] = 0x00;
|
|
|
|
|
|
request[3] = 0x00; // 起始地址 0x0000
|
|
|
|
|
|
request[4] = 0x00;
|
|
|
|
|
|
request[5] = 0x14; // 读取 20 个寄存器 (0x0000 ~ 0x0013)
|
|
|
|
|
|
byte[] crc = CalculateCRC16(request, 6);
|
|
|
|
|
|
request[6] = crc[0];
|
|
|
|
|
|
request[7] = crc[1];
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
serialPort.Write(request, 0, request.Length);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch { }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
System.Threading.Thread.Sleep(50);
|
|
|
|
|
|
byte[] buffer = new byte[serialPort.BytesToRead];
|
|
|
|
|
|
serialPort.Read(buffer, 0, buffer.Length);
|
|
|
|
|
|
|
|
|
|
|
|
if (buffer.Length < 9 || buffer[0] != slaveId || buffer[1] != 0x03)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
// 解析温度 (0x0005)
|
|
|
|
|
|
int tempIndex = 3 + (0x0005 - 0x0000) * 2;
|
|
|
|
|
|
ushort rawTemp = (ushort)((buffer[tempIndex] << 8) | buffer[tempIndex + 1]);
|
|
|
|
|
|
double temp = rawTemp / 100.0;
|
|
|
|
|
|
|
|
|
|
|
|
// 解析运行时间 (0x0007) - 这才是您要的恒温时间!
|
|
|
|
|
|
int timeIndex = 3 + (0x0007 - 0x0000) * 2;
|
|
|
|
|
|
ushort rawRunTime = (ushort)((buffer[timeIndex] << 8) | buffer[timeIndex + 1]);
|
|
|
|
|
|
|
|
|
|
|
|
// 根据 0x000F 的值判断单位(0=分钟,1=小时)
|
|
|
|
|
|
int unitIndex = 3 + (0x000F - 0x0000) * 2;
|
|
|
|
|
|
ushort timeUnit = (ushort)((buffer[unitIndex] << 8) | buffer[unitIndex + 1]);
|
|
|
|
|
|
|
|
|
|
|
|
int displayTime = rawRunTime;
|
|
|
|
|
|
string unit = "分钟";
|
|
|
|
|
|
if (timeUnit == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
displayTime = rawRunTime; // 小时
|
|
|
|
|
|
unit = "小时";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
txtTestTemp.Text = temp.ToString("F2");
|
|
|
|
|
|
|
|
|
|
|
|
// 只有当运行时间 >0 时才显示,否则显示等待状态
|
|
|
|
|
|
if (rawRunTime > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
txtThermostatTime.Text = displayTime.ToString();
|
|
|
|
|
|
txtStatus.Text = $"温度: {temp:F2}℃ 已运行: {displayTime} {unit} 时间单位寄存器: {timeUnit}";
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
txtThermostatTime.Text = "0";
|
|
|
|
|
|
txtStatus.Text = $"温度: {temp:F2}℃ 等待启动... (运行时间=0)";
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Dispatcher.Invoke(() => txtStatus.Text = $"接收错误: {ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// try
|
|
|
|
|
|
// {
|
|
|
|
|
|
// System.Threading.Thread.Sleep(50); // 等待数据收完
|
|
|
|
|
|
// byte[] buffer = new byte[serialPort.BytesToRead];
|
|
|
|
|
|
// serialPort.Read(buffer, 0, buffer.Length);
|
|
|
|
|
|
|
|
|
|
|
|
// // 最小有效响应长度:地址(1) + 功能码(1) + 字节数(1) + 数据(2*2) + CRC(2) = 9
|
|
|
|
|
|
// if (buffer.Length < 9 || buffer[0] != slaveId || buffer[1] != 0x03)
|
|
|
|
|
|
// return;
|
|
|
|
|
|
|
|
|
|
|
|
// // 提取数据:温度寄存器 (0x0005) 和 运行时间寄存器 (0x0007)
|
|
|
|
|
|
// ushort rawTemp = (ushort)((buffer[3] << 8) | buffer[4]);
|
|
|
|
|
|
// ushort rawRunTime = (ushort)((buffer[5] << 8) | buffer[6]);
|
|
|
|
|
|
|
|
|
|
|
|
// double temp = rawTemp / 100.0; // 协议规定小数点2位
|
|
|
|
|
|
// int runTimeMinutes = rawRunTime; // 假设单位是分钟,直接显示
|
|
|
|
|
|
|
|
|
|
|
|
// // 更新 UI
|
|
|
|
|
|
// Dispatcher.Invoke(() =>
|
|
|
|
|
|
// {
|
|
|
|
|
|
// // 更新试验温度(如果希望保留手动输入能力,可添加判断条件,这里直接覆盖)
|
|
|
|
|
|
// txtTestTemp.Text = temp.ToString("F2");
|
|
|
|
|
|
|
|
|
|
|
|
// // 更新恒温时间
|
|
|
|
|
|
// txtThermostatTime.Text = runTimeMinutes.ToString();
|
|
|
|
|
|
|
|
|
|
|
|
// txtStatus.Text = $"实时温度: {temp:F2}℃ 运行时间: {runTimeMinutes} min";
|
|
|
|
|
|
// });
|
|
|
|
|
|
// }
|
|
|
|
|
|
// catch (Exception ex)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// Dispatcher.Invoke(() => txtStatus.Text = $"接收错误: {ex.Message}");
|
|
|
|
|
|
// }
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private byte[] CalculateCRC16(byte[] data, int length)
|
|
|
|
|
|
{
|
|
|
|
|
|
ushort crc = 0xFFFF;
|
|
|
|
|
|
for (int pos = 0; pos < length; pos++)
|
|
|
|
|
|
{
|
|
|
|
|
|
crc ^= data[pos];
|
|
|
|
|
|
for (int i = 8; i != 0; i--)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ((crc & 0x0001) != 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
crc >>= 1;
|
|
|
|
|
|
crc ^= 0xA001;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
crc >>= 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return new byte[] { (byte)(crc & 0xFF), (byte)(crc >> 8) };
|
2026-02-12 17:03:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 15:09:57 +08:00
|
|
|
|
protected override void OnClosed(EventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
base.OnClosed(e);
|
|
|
|
|
|
readTimer?.Stop();
|
|
|
|
|
|
readTimer?.Dispose();
|
|
|
|
|
|
if (serialPort != null && serialPort.IsOpen)
|
|
|
|
|
|
{
|
|
|
|
|
|
serialPort.Close();
|
|
|
|
|
|
serialPort.Dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-12 17:03:23 +08:00
|
|
|
|
// 温度变化时自动更新恒温时间(表2)
|
|
|
|
|
|
private void TxtTestTemp_TextChanged(object sender, TextChangedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
UpdateThermostatTime();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void UpdateThermostatTime()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (double.TryParse(txtTestTemp.Text, out double temp))
|
|
|
|
|
|
{
|
|
|
|
|
|
int time = Models.Constants.GetThermostatTime(temp);
|
|
|
|
|
|
txtThermostatTime.Text = time.ToString();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
txtThermostatTime.Text = "";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载示例数据
|
|
|
|
|
|
private void BtnLoadExample_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
txtSampleID.Text = "S2026-001";
|
|
|
|
|
|
txtSampleName.Text = "润滑油";
|
|
|
|
|
|
txtTestTemp.Text = "50";
|
|
|
|
|
|
txtConstant.Text = "0.4780";
|
|
|
|
|
|
txtDensity.Text = "0.85";
|
|
|
|
|
|
txtOperator.Text = "张三";
|
|
|
|
|
|
txtRemark.Text = "示例数据";
|
|
|
|
|
|
|
|
|
|
|
|
txtTime1.Text = "322.4";
|
|
|
|
|
|
txtTime2.Text = "322.6";
|
|
|
|
|
|
txtTime3.Text = "321.0";
|
|
|
|
|
|
txtTime4.Text = "";
|
|
|
|
|
|
txtTime5.Text = "";
|
|
|
|
|
|
txtTime6.Text = "";
|
|
|
|
|
|
|
|
|
|
|
|
UpdateThermostatTime();
|
|
|
|
|
|
txtStatus.Text = "示例数据已加载";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清空所有输入
|
|
|
|
|
|
private void BtnClearAll_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
txtSampleID.Clear();
|
|
|
|
|
|
txtSampleName.Clear();
|
|
|
|
|
|
txtTestTemp.Clear();
|
|
|
|
|
|
txtConstant.Clear();
|
|
|
|
|
|
txtDensity.Clear();
|
|
|
|
|
|
txtOperator.Clear();
|
|
|
|
|
|
txtRemark.Clear();
|
|
|
|
|
|
txtTime1.Clear();
|
|
|
|
|
|
txtTime2.Clear();
|
|
|
|
|
|
txtTime3.Clear();
|
|
|
|
|
|
txtTime4.Clear();
|
|
|
|
|
|
txtTime5.Clear();
|
|
|
|
|
|
txtTime6.Clear();
|
|
|
|
|
|
txtAvgTime.Clear();
|
|
|
|
|
|
txtViscosity.Clear();
|
|
|
|
|
|
txtDynamicViscosity.Clear();
|
|
|
|
|
|
txtValidCount.Clear();
|
|
|
|
|
|
txtPrecisionJudge.Clear();
|
|
|
|
|
|
txtRepeatability.Clear();
|
|
|
|
|
|
txtReproducibility.Clear();
|
|
|
|
|
|
txtMeasuredRepeatability.Clear();
|
|
|
|
|
|
txtRepeatJudge.Clear();
|
|
|
|
|
|
lstRawTimes.ItemsSource = null;
|
|
|
|
|
|
rawTimes.Clear();
|
|
|
|
|
|
txtStatus.Text = "已清空";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清空流动时间
|
|
|
|
|
|
private void BtnClearTimes_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
txtTime1.Clear();
|
|
|
|
|
|
txtTime2.Clear();
|
|
|
|
|
|
txtTime3.Clear();
|
|
|
|
|
|
txtTime4.Clear();
|
|
|
|
|
|
txtTime5.Clear();
|
|
|
|
|
|
txtTime6.Clear();
|
|
|
|
|
|
rawTimes.Clear();
|
|
|
|
|
|
lstRawTimes.ItemsSource = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 增加一次测量(在界面中添加更多输入框,为简化,我们已预留6个)
|
|
|
|
|
|
private void BtnAddMeasurement_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 本版本固定6个输入框,无需动态增加
|
|
|
|
|
|
MessageBox.Show("已支持最多6次测量,请直接在对应框中输入。", "提示");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 核心计算:运动粘度、动力粘度、精密度
|
|
|
|
|
|
private void BtnCalcViscosity_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. 读取流动时间
|
|
|
|
|
|
rawTimes = GetTimeValues();
|
|
|
|
|
|
if (rawTimes.Count < 4)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show("至少需要输入4次流动时间!", "数据不足", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 读取常数、密度、温度
|
|
|
|
|
|
if (!double.TryParse(txtConstant.Text, out double constant))
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show("请输入有效的粘度计常数 C", "参数错误", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!double.TryParse(txtTestTemp.Text, out double temp))
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show("请输入试验温度", "参数错误", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
double density = 0;
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(txtDensity.Text))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!double.TryParse(txtDensity.Text, out density))
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show("密度格式错误,将不计算动力粘度", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 根据温度获取允许偏差(%)
|
|
|
|
|
|
double allowedDeviationPercent = Models.Constants.GetAllowedDeviation(temp);
|
|
|
|
|
|
double allowedDeviation = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 计算平均值、筛选有效数据
|
|
|
|
|
|
List<double> validTimes = new List<double>();
|
|
|
|
|
|
double avg = rawTimes.Average();
|
|
|
|
|
|
bool continueCalc = true;
|
|
|
|
|
|
int maxIterations = 10; // 防止死循环
|
|
|
|
|
|
while (continueCalc && maxIterations-- > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
validTimes = rawTimes.Where(t => Math.Abs(t - avg) <= avg * allowedDeviationPercent / 100.0).ToList();
|
|
|
|
|
|
if (validTimes.Count >= 3)
|
|
|
|
|
|
{
|
|
|
|
|
|
double newAvg = validTimes.Average();
|
|
|
|
|
|
if (Math.Abs(newAvg - avg) < 0.0001)
|
|
|
|
|
|
continueCalc = false;
|
|
|
|
|
|
else
|
|
|
|
|
|
avg = newAvg;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (validTimes.Count < 3)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show("有效测量次数不足3次,无法计算!请检查数据是否离散过大。", "精密度超差", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
double finalAvg = validTimes.Average();
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 计算运动粘度
|
|
|
|
|
|
double viscosity = constant * finalAvg;
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 计算动力粘度(如果有密度)
|
|
|
|
|
|
double dynamicViscosity = 0;
|
|
|
|
|
|
if (density > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
dynamicViscosity = viscosity * density;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 7. 计算本次测量的重复性(相对极差)
|
|
|
|
|
|
double measuredRepeatability = 0;
|
|
|
|
|
|
if (validTimes.Count > 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
measuredRepeatability = (validTimes.Max() - validTimes.Min()) / finalAvg * 100;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 8. 获取标准重复性限
|
|
|
|
|
|
double stdRepeatability = Models.Constants.GetRepeatabilityLimit(temp);
|
|
|
|
|
|
|
|
|
|
|
|
// 9. 更新UI
|
|
|
|
|
|
txtAvgTime.Text = finalAvg.ToString("F3");
|
2026-02-24 16:30:52 +08:00
|
|
|
|
txtViscosity.Text = viscosity.ToString("F4"); // 四位有效数字
|
2026-02-12 17:03:23 +08:00
|
|
|
|
if (density > 0)
|
2026-02-24 16:30:52 +08:00
|
|
|
|
txtDynamicViscosity.Text = dynamicViscosity.ToString("F4");
|
2026-02-12 17:03:23 +08:00
|
|
|
|
else
|
|
|
|
|
|
txtDynamicViscosity.Text = "未计算";
|
|
|
|
|
|
txtValidCount.Text = $"{validTimes.Count} / {rawTimes.Count}";
|
|
|
|
|
|
txtMeasuredRepeatability.Text = measuredRepeatability.ToString("F2") + "%";
|
|
|
|
|
|
txtRepeatability.Text = stdRepeatability.ToString("F1") + "%";
|
|
|
|
|
|
txtReproducibility.Text = Models.Constants.GetReproducibilityLimit(temp).ToString("F1") + "%";
|
|
|
|
|
|
|
|
|
|
|
|
bool repeatOK = measuredRepeatability <= stdRepeatability;
|
|
|
|
|
|
txtRepeatJudge.Text = repeatOK ? "合格" : $"超差 (允许 ≤ {stdRepeatability:F1}%)";
|
|
|
|
|
|
txtRepeatJudge.Foreground = repeatOK ? System.Windows.Media.Brushes.Green : System.Windows.Media.Brushes.Red;
|
|
|
|
|
|
|
|
|
|
|
|
txtPrecisionJudge.Text = repeatOK ? "精密度符合要求" : "精密度不符合要求,建议重新测试";
|
|
|
|
|
|
|
|
|
|
|
|
// 10. 显示原始数据
|
|
|
|
|
|
lstRawTimes.ItemsSource = rawTimes.Select((t, i) => new { Display = $"第{i + 1}次: {t:F3}s" }).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 11. 保存当前数据到模型
|
|
|
|
|
|
currentData.SampleID = txtSampleID.Text;
|
|
|
|
|
|
currentData.SampleName = txtSampleName.Text;
|
|
|
|
|
|
currentData.TestTemp = temp;
|
|
|
|
|
|
currentData.Constant = constant;
|
|
|
|
|
|
currentData.Density = density > 0 ? density : (double?)null;
|
|
|
|
|
|
currentData.RawTimes = rawTimes;
|
|
|
|
|
|
currentData.ValidTimes = validTimes;
|
|
|
|
|
|
currentData.AvgTime = finalAvg;
|
|
|
|
|
|
currentData.Viscosity = viscosity;
|
|
|
|
|
|
currentData.DynamicViscosity = dynamicViscosity;
|
|
|
|
|
|
currentData.Operator = txtOperator.Text;
|
|
|
|
|
|
currentData.TestDate = dpTestDate.SelectedDate ?? DateTime.Now;
|
|
|
|
|
|
currentData.Remark = txtRemark.Text;
|
|
|
|
|
|
currentData.Repeatability = measuredRepeatability;
|
|
|
|
|
|
currentData.IsRepeatabilityOK = repeatOK;
|
|
|
|
|
|
|
|
|
|
|
|
txtStatus.Text = $"计算完成,有效次数 {validTimes.Count},运动粘度 {viscosity:G4} mm²/s";
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show($"计算错误: {ex.Message}", "异常", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从文本框获取流动时间列表
|
|
|
|
|
|
private List<double> GetTimeValues()
|
|
|
|
|
|
{
|
|
|
|
|
|
var list = new List<double>();
|
|
|
|
|
|
AddIfValid(txtTime1, list);
|
|
|
|
|
|
AddIfValid(txtTime2, list);
|
|
|
|
|
|
AddIfValid(txtTime3, list);
|
|
|
|
|
|
AddIfValid(txtTime4, list);
|
|
|
|
|
|
AddIfValid(txtTime5, list);
|
|
|
|
|
|
AddIfValid(txtTime6, list);
|
|
|
|
|
|
return list;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void AddIfValid(TextBox tb, List<double> list)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(tb.Text) && double.TryParse(tb.Text, out double val))
|
|
|
|
|
|
{
|
|
|
|
|
|
list.Add(val);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成原始记录
|
|
|
|
|
|
private void BtnGenRecord_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (currentData.Viscosity <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show("请先计算运动粘度!", "无数据", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var record = new TestRecordControl();
|
|
|
|
|
|
record.LoadData(currentData);
|
|
|
|
|
|
ReportContent.Content = record;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成测试报告
|
|
|
|
|
|
private void BtnGenReport_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (currentData.Viscosity <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show("请先计算运动粘度!", "无数据", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var report = new TestReportControl();
|
|
|
|
|
|
report.LoadData(currentData);
|
|
|
|
|
|
ReportContent.Content = report;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 打印预览
|
|
|
|
|
|
private void BtnPrintPreview_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ReportContent.Content == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show("请先生成记录或报告", "无报表", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (ReportContent.Content is FrameworkElement visual)
|
|
|
|
|
|
{
|
|
|
|
|
|
PrintDialog printDialog = new PrintDialog();
|
|
|
|
|
|
if (printDialog.ShowDialog() == true)
|
|
|
|
|
|
{
|
|
|
|
|
|
printDialog.PrintVisual(visual, "粘度测试报表");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-24 15:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 第1次
|
|
|
|
|
|
private void BtnStart1_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
stopwatch1.Reset();
|
|
|
|
|
|
stopwatch1.Start();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BtnStop1_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
stopwatch1.Stop();
|
|
|
|
|
|
txtTime1.Text = (stopwatch1.Elapsed.TotalSeconds).ToString("F3");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第2次
|
|
|
|
|
|
private void BtnStart2_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
stopwatch2.Reset();
|
|
|
|
|
|
stopwatch2.Start();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BtnStop2_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
stopwatch2.Stop();
|
|
|
|
|
|
txtTime2.Text = (stopwatch2.Elapsed.TotalSeconds).ToString("F3");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第3次
|
|
|
|
|
|
private void BtnStart3_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
stopwatch3.Reset();
|
|
|
|
|
|
stopwatch3.Start();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BtnStop3_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
stopwatch3.Stop();
|
|
|
|
|
|
txtTime3.Text = (stopwatch3.Elapsed.TotalSeconds).ToString("F3");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第4次
|
|
|
|
|
|
private void BtnStart4_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
stopwatch4.Reset();
|
|
|
|
|
|
stopwatch4.Start();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BtnStop4_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
stopwatch4.Stop();
|
|
|
|
|
|
txtTime4.Text = (stopwatch4.Elapsed.TotalSeconds).ToString("F3");
|
|
|
|
|
|
}
|
2026-02-12 17:03:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|