48 Commits
rain ... master

Author SHA1 Message Date
GukSang.Jin
df36b65b30 更新202680 2026-05-21 08:57:19 +08:00
GukSang.Jin
b2da230e24 更新 2026-05-20 17:45:03 +08:00
GukSang.Jin
f9554447ec 更新2026 2026-05-20 17:03:41 +08:00
GukSang.Jin
9a6adab2a3 更新 2026-05-20 16:53:08 +08:00
GukSang.Jin
4030c18dc8 更新122 2026-05-20 16:39:42 +08:00
GukSang.Jin
7c7339d72c 更新 2026-05-20 16:24:21 +08:00
GukSang.Jin
9991a82d19 更新12 2026-05-20 16:12:56 +08:00
GukSang.Jin
164e4db95b 更新12 2026-05-20 16:10:32 +08:00
GukSang.Jin
49f1cc0cf7 更新 2026-05-20 15:44:00 +08:00
GukSang.Jin
65815607bd 更新290 2026-05-20 15:26:04 +08:00
GukSang.Jin
b79c34bba7 更新2026 2026-05-20 15:21:03 +08:00
GukSang.Jin
0cba5198f2 更新 2026-05-20 14:51:00 +08:00
GukSang.Jin
263fffbcf5 更新 2026-05-20 14:37:20 +08:00
GukSang.Jin
df5c7566fb 更新 2026-05-20 14:27:16 +08:00
GukSang.Jin
17d9904898 更新202612 2026-05-20 13:33:15 +08:00
GukSang.Jin
43893e5da6 更新2026 2026-05-20 11:38:23 +08:00
GukSang.Jin
a4a95e6cf3 更新2026 2026-05-20 11:29:20 +08:00
GukSang.Jin
9da775aa37 更新 2026-05-20 11:19:43 +08:00
GukSang.Jin
954afaaf39 更新2026 2026-05-20 11:13:12 +08:00
GukSang.Jin
61420da42e 更新 2026-05-20 10:16:02 +08:00
GukSang.Jin
070463ae8e 更新 2026-05-20 09:45:34 +08:00
8f625a7a41 出去介质温度 2026-05-20 07:11:00 +08:00
81b257cbdc 崩解最长时间 2026-05-20 07:05:45 +08:00
73bcebf598 delete 2026-05-20 06:44:20 +08:00
a9587a90b3 删除不必要的配置 2026-05-20 06:43:07 +08:00
GukSang.Jin
18d317623d 更新 2026-05-19 21:17:29 +08:00
GukSang.Jin
ce62b19960 更新 2026-05-19 21:11:24 +08:00
GukSang.Jin
527169bffb 更新2026 2026-05-19 21:00:08 +08:00
GukSang.Jin
7b25ef07cc 更新 2026-05-19 20:41:02 +08:00
GukSang.Jin
1b6624c262 223 2026-05-19 20:33:41 +08:00
GukSang.Jin
69557bc108 更新122 2026-05-19 20:33:16 +08:00
c850099e2c 删除 2026-05-19 20:20:51 +08:00
GukSang.Jin
eb5fa4a948 更新 2026-05-19 20:14:10 +08:00
GukSang.Jin
0966f1f4f6 更新 2026-05-19 20:06:51 +08:00
GukSang.Jin
7cd88de040 更新 2026-05-19 19:52:32 +08:00
GukSang.Jin
4af7a825db Merge branch 'master' of http://101.132.182.216:3000/csi_team/CSI-Z420-Tablet-Multi-Function-Tester 2026-05-19 19:37:23 +08:00
GukSang.Jin
b2286cd514 提交 2026-05-19 19:37:13 +08:00
dc4562e9ae 添加默认值 2026-05-19 19:26:36 +08:00
GukSang.Jin
1e1f1e930e Merge branch 'master' of http://101.132.182.216:3000/csi_team/CSI-Z420-Tablet-Multi-Function-Tester 2026-05-19 19:01:45 +08:00
GukSang.Jin
375399607c 更新 2026-05-19 19:01:28 +08:00
4a435db5c3 页面参数 2026-05-19 18:56:05 +08:00
GukSang.Jin
8ed011f91e 更新2025 2026-05-19 18:44:56 +08:00
GukSang.Jin
2f4388723c 更新20260518 2026-05-19 18:22:00 +08:00
GukSang.Jin
00c224ceff 更新20260519 2026-05-19 17:27:12 +08:00
GukSang.Jin
8d504d91e5 更新201 2026-05-19 17:19:54 +08:00
GukSang.Jin
72fac0e41b 更新 2026-05-19 17:14:29 +08:00
GukSang.Jin
b80edaea78 更新20260519 2026-05-19 16:55:00 +08:00
57ccef3f5b 修改代码 2026-05-19 16:42:23 +08:00
24 changed files with 2185 additions and 970 deletions

View File

@@ -1,6 +1,19 @@
<Application x:Class="TabletTester2025.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Application.Resources>
<sys:Double x:Key="SmallTextFontSize">14</sys:Double>
<sys:Double x:Key="TableFontSize">14</sys:Double>
<sys:Double x:Key="BaseFontSize">16</sys:Double>
<sys:Double x:Key="ButtonFontSize">16</sys:Double>
<sys:Double x:Key="TabFontSize">17</sys:Double>
<sys:Double x:Key="ParamValueFontSize">19</sys:Double>
<sys:Double x:Key="LargeInputFontSize">24</sys:Double>
<sys:Double x:Key="TitleFontSize">24</sys:Double>
<sys:Double x:Key="MetricValueFontSize">28</sys:Double>
<sys:Double x:Key="KeypadButtonFontSize">22</sys:Double>
<sys:Double x:Key="KeypadCommandFontSize">18</sys:Double>
<sys:Double x:Key="KeypadValueFontSize">28</sys:Double>
</Application.Resources>
</Application>

View File

@@ -56,6 +56,7 @@ namespace TabletTester2025
-- 硬度
HardnessAvg REAL,
HardnessAverageDeviation REAL,
HardnessRSD REAL,
HardnessMax REAL,
HardnessMin REAL,
@@ -106,6 +107,20 @@ namespace TabletTester2025
";
cmd.ExecuteNonQuery();
var hardnessSampleCmd = connection.CreateCommand();
hardnessSampleCmd.CommandText = @"
CREATE TABLE IF NOT EXISTS HardnessSamplePoints (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
TestBatchId INTEGER NOT NULL,
SequenceNo INTEGER NOT NULL,
Value REAL NOT NULL,
DeviationFromAverage REAL NOT NULL,
RecordedAt TEXT NOT NULL,
FOREIGN KEY(TestBatchId) REFERENCES TestBatches(Id) ON DELETE CASCADE
);
";
hardnessSampleCmd.ExecuteNonQuery();
var sampleCmd = connection.CreateCommand();
sampleCmd.CommandText = @"
CREATE TABLE IF NOT EXISTS DissolutionSamplePoints (
@@ -180,6 +195,7 @@ CREATE TABLE IF NOT EXISTS DissolutionSamplePoints (
{
("TestBatches", "HardnessMax", "REAL", "0"),
("TestBatches", "HardnessMin", "REAL", "0"),
("TestBatches", "HardnessAverageDeviation", "REAL", "0"),
("TestBatches", "HardnessTestCount", "INTEGER", "6"),
("TestBatches", "HardnessInternalMin", "REAL", "40"),
("TestBatches", "HardnessInternalMax", "REAL", "60"),

View File

@@ -18,12 +18,20 @@ namespace TabletTester2025.Data
}
public DbSet<TestBatch> TestBatches { get; set; }
public DbSet<HardnessSamplePoint> HardnessSamplePoints { get; set; }
public DbSet<DissolutionSamplePoint> DissolutionSamplePoints { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TestBatch>().ToTable("TestBatches");
modelBuilder.Entity<TestBatch>().HasKey(t => t.Id);
modelBuilder.Entity<HardnessSamplePoint>().ToTable("HardnessSamplePoints");
modelBuilder.Entity<HardnessSamplePoint>().HasKey(t => t.Id);
modelBuilder.Entity<HardnessSamplePoint>()
.HasOne<TestBatch>()
.WithMany(t => t.HardnessSamples)
.HasForeignKey(t => t.TestBatchId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<DissolutionSamplePoint>().ToTable("DissolutionSamplePoints");
modelBuilder.Entity<DissolutionSamplePoint>().HasKey(t => t.Id);
modelBuilder.Entity<DissolutionSamplePoint>()

View File

@@ -23,12 +23,31 @@ namespace TabletTester2025.Services
db.SaveChanges();
}
public void InsertBatch(TestBatch batch, IEnumerable<DissolutionSamplePoint> dissolutionSamples)
public void InsertBatch(
TestBatch batch,
IEnumerable<DissolutionSamplePoint> dissolutionSamples,
IEnumerable<HardnessSamplePoint>? hardnessSamples = null)
{
using var db = new AppDbContext(_connectionString);
db.TestBatches.Add(batch);
db.SaveChanges();
var hardnessDetails = (hardnessSamples ?? Enumerable.Empty<HardnessSamplePoint>())
.Where(s => double.IsFinite(s.Value) && s.Value > 0)
.OrderBy(s => s.SequenceNo)
.Select(s => new HardnessSamplePoint
{
TestBatchId = batch.Id,
SequenceNo = s.SequenceNo,
Value = s.Value,
DeviationFromAverage = s.DeviationFromAverage,
RecordedAt = s.RecordedAt
})
.ToList();
if (hardnessDetails.Count > 0)
db.HardnessSamplePoints.AddRange(hardnessDetails);
var samples = dissolutionSamples
.Where(s => s.Percent.HasValue)
.Select(s => new DissolutionSamplePoint
@@ -42,22 +61,39 @@ namespace TabletTester2025.Services
})
.ToList();
if (samples.Count == 0)
if (samples.Count > 0)
db.DissolutionSamplePoints.AddRange(samples);
if (hardnessDetails.Count == 0 && samples.Count == 0)
return;
db.DissolutionSamplePoints.AddRange(samples);
db.SaveChanges();
batch.HardnessSamples = hardnessDetails;
batch.DissolutionSamples = samples;
}
public List<TestBatch> GetBatches(int? stationId = null, int limit = 100)
{
using var db = new AppDbContext(_connectionString);
var query = db.TestBatches.Include(b => b.DissolutionSamples).AsQueryable();
var query = db.TestBatches
.Include(b => b.HardnessSamples)
.Include(b => b.DissolutionSamples)
.AsQueryable();
if (stationId.HasValue)
query = query.Where(b => b.StationId == stationId.Value);
query = query.OrderByDescending(b => b.TestTime).Take(limit);
return query.ToList();
}
public void DeleteBatch(int id)
{
using var db = new AppDbContext(_connectionString);
var batch = db.TestBatches.Find(id);
if (batch != null)
{
db.TestBatches.Remove(batch);
db.SaveChanges();
}
}
}
}

View File

@@ -0,0 +1,51 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
namespace TabletTester2025.Models
{
public class HardnessSamplePoint : ObservableObject
{
private int _id;
private int _testBatchId;
private int _sequenceNo;
private double _value;
private double _deviationFromAverage;
private DateTime _recordedAt;
public int Id
{
get => _id;
set => SetProperty(ref _id, value);
}
public int TestBatchId
{
get => _testBatchId;
set => SetProperty(ref _testBatchId, value);
}
public int SequenceNo
{
get => _sequenceNo;
set => SetProperty(ref _sequenceNo, value);
}
public double Value
{
get => _value;
set => SetProperty(ref _value, value);
}
public double DeviationFromAverage
{
get => _deviationFromAverage;
set => SetProperty(ref _deviationFromAverage, value);
}
public DateTime RecordedAt
{
get => _recordedAt;
set => SetProperty(ref _recordedAt, value);
}
}
}

View File

@@ -5,6 +5,7 @@
public string IpAddress { get; set; }
public int Port { get; set; }
public byte SlaveId { get; set; }
public PlcFloatWordOrder FloatWordOrder { get; set; } = PlcFloatWordOrder.LowWordFirst;
// 硬度
public ushort HardnessMax { get; set; }
@@ -14,6 +15,7 @@
public ushort HardnessSudu { get; set; }
public ushort HardnessWeiyi { get; set; }
public ushort HardnessLimit { get; set; }
public ushort HardnessForward { get; set; }
public ushort HardnessBack { get; set; }
@@ -22,12 +24,19 @@
// 兼容旧代码:硬度完成线圈与溶出或硬度实时值寄存器地址
public ushort HardnessOver { get; set; }
public ushort HardnessCompleteCoil { get; set; }
public ushort HardnessStartOver { get; set; }
public ushort HardnessPoSun { get; set; }
public ushort HardnessPressure { get; set; }
// 脆碎度
public ushort FriabilityStartCoil { get; set; }
public ushort FriabilityRpm { get; set; }
public ushort FriabilityRounds { get; set; }
public ushort FriabilityRoundsBox { get; set; } // 兼容旧配置字段
public ushort FriabilityRealtimeRounds { get; set; }
public ushort FriabilityTestTime { get; set; }
public ushort FriabilityWeightBefore { get; set; }
public ushort FriabilityWeightAfter { get; set; }
public ushort FriabilityLossPercent { get; set; }
public ushort WeightBefore { get; set; } // 天平重量寄存器(可选)
public ushort WeightAfter { get; set; }
public ushort FriabilityStartCoil2 { get; set; }
@@ -49,6 +58,8 @@
// 溶出
public ushort DissolutionRpm { get; set; }
public ushort Dissolution1Speed { get; set; }
public ushort Dissolution2Speed { get; set; }
public ushort DissolutionPercent { get; set; }
public ushort Dissolution1Percent { get; set; }
public ushort Dissolution2Percent { get; set; }
@@ -66,4 +77,10 @@
public ushort Dissolution1SampleInterval { get; set; }
public ushort Dissolution2SampleInterval { get; set; }
}
public enum PlcFloatWordOrder
{
LowWordFirst,
HighWordFirst
}
}

View File

@@ -15,12 +15,21 @@ namespace TabletTester2025.Models
// 硬度
public double HardnessAvg { get; set; }
public double HardnessAverageDeviation { get; set; }
public double HardnessRSD { get; set; }
public double HardnessMax { get; set; }
public double HardnessMin { get; set; }
public int HardnessTestCount { get; set; }
public double HardnessInternalMin { get; set; }
public double HardnessInternalMax { get; set; }
public List<HardnessSamplePoint> HardnessSamples { get; set; } = new();
[NotMapped]
public string HardnessSampleSummary => HardnessSamples == null || HardnessSamples.Count == 0
? ""
: string.Join("", HardnessSamples
.OrderBy(s => s.SequenceNo)
.Select(s => $"{s.SequenceNo}:{s.Value:0.0}"));
// 脆碎度
public double FriabilityLoss { get; set; }

View File

@@ -60,7 +60,7 @@ namespace TabletTester2025.Services
{
var data = batches.ToList();
var sheet = package.Workbook.Worksheets.Add("硬度报表");
WriteHeader(sheet, "检测时间", "样品名称", "平均值(N)", "RSD(%)", "最大值(N)", "最值(N)", "测试次数", "内控下限(N)", "内控上限(N)", "判定");
WriteHeader(sheet, "检测时间", "样品名称", "平均值(N)", "平均偏差(N)", "RSD", "最值(N)", "测试次数", "单次数据(N)", "判定");
if (data.Count == 0)
{
@@ -75,13 +75,37 @@ namespace TabletTester2025.Services
sheet.Cells[row, 1].Value = b.TestTime.ToString("yyyy-MM-dd HH:mm:ss");
sheet.Cells[row, 2].Value = b.SampleName;
sheet.Cells[row, 3].Value = b.HardnessAvg;
sheet.Cells[row, 4].Value = b.HardnessRSD;
sheet.Cells[row, 5].Value = b.HardnessMax;
sheet.Cells[row, 6].Value = b.HardnessMin;
sheet.Cells[row, 4].Value = b.HardnessAverageDeviation;
sheet.Cells[row, 5].Value = b.HardnessRSD;
sheet.Cells[row, 6].Value = b.HardnessMax;
sheet.Cells[row, 7].Value = b.HardnessTestCount;
sheet.Cells[row, 8].Value = b.HardnessInternalMin;
sheet.Cells[row, 9].Value = b.HardnessInternalMax;
sheet.Cells[row, 10].Value = b.HardnessPassText;
sheet.Cells[row, 8].Value = b.HardnessSampleSummary;
sheet.Cells[row, 9].Value = b.HardnessPassText;
row++;
}
sheet.Cells.AutoFitColumns();
AddHardnessSamplesSheet(package, data);
}
private static void AddHardnessSamplesSheet(ExcelPackage package, IEnumerable<TestBatch> batches)
{
var samples = GetHardnessSampleRows(batches).ToList();
if (samples.Count == 0)
return;
var sheet = package.Workbook.Worksheets.Add("硬度单次明细");
WriteHeader(sheet, "检测时间", "样品名称", "序号", "硬度值(N)", "与平均值偏差(N)", "记录时间");
int row = 2;
foreach (var item in samples)
{
sheet.Cells[row, 1].Value = item.Batch.TestTime.ToString("yyyy-MM-dd HH:mm:ss");
sheet.Cells[row, 2].Value = item.Batch.SampleName;
sheet.Cells[row, 3].Value = item.Sample.SequenceNo;
sheet.Cells[row, 4].Value = item.Sample.Value;
sheet.Cells[row, 5].Value = item.Sample.DeviationFromAverage;
sheet.Cells[row, 6].Value = item.Sample.RecordedAt.ToString("yyyy-MM-dd HH:mm:ss");
row++;
}
@@ -123,7 +147,7 @@ namespace TabletTester2025.Services
{
var data = batches.ToList();
var sheet = package.Workbook.Worksheets.Add("崩解报表");
WriteHeader(sheet, "检测时间", "样品名称", "剂型规格", "时限(秒)", "崩解时间(秒)", "剩余未崩解管", "水浴温度(℃)", "判定");
WriteHeader(sheet, "检测时间", "样品名称", "崩解时间(秒)", "水浴温度(℃)", "判定");
if (data.Count == 0)
{
@@ -137,12 +161,9 @@ namespace TabletTester2025.Services
{
sheet.Cells[row, 1].Value = b.TestTime.ToString("yyyy-MM-dd HH:mm:ss");
sheet.Cells[row, 2].Value = b.SampleName;
sheet.Cells[row, 3].Value = b.DisintegrationDosageForm;
sheet.Cells[row, 4].Value = b.DisintegrationLimitSeconds;
sheet.Cells[row, 5].Value = b.DisintegrationTimeSec;
sheet.Cells[row, 6].Value = b.RemainingTubesAtEnd;
sheet.Cells[row, 7].Value = b.DisintegrationTemp;
sheet.Cells[row, 8].Value = b.DisintegrationPassText;
sheet.Cells[row, 3].Value = b.DisintegrationTimeSec;
sheet.Cells[row, 4].Value = b.DisintegrationTemp;
sheet.Cells[row, 5].Value = b.DisintegrationPassText;
row++;
}
@@ -297,6 +318,15 @@ namespace TabletTester2025.Services
.ThenBy(x => x.Sample.ScheduledTimeMin);
}
private static IEnumerable<(TestBatch Batch, HardnessSamplePoint Sample)> GetHardnessSampleRows(IEnumerable<TestBatch> batches)
{
return batches
.SelectMany(batch => (batch.HardnessSamples ?? Enumerable.Empty<HardnessSamplePoint>())
.Select(sample => (Batch: batch, Sample: sample)))
.OrderBy(x => x.Batch.TestTime)
.ThenBy(x => x.Sample.SequenceNo);
}
private static void WriteHeader(ExcelWorksheet sheet, params string[] headers)
{
for (int i = 0; i < headers.Length; i++)

View File

@@ -9,7 +9,7 @@ namespace TabletTester2025.Services
Task<bool> CheckConnectionAsync();
//从 PLC 的指定起始地址,读取 1 个 32 位浮点型float数据。
Task<float> ReadFloatAsync(ushort startAddress);
//从 PLC 的指定起始地址,读取 1 个 32 位整型int数据。
//从 PLC 的指定地址,读取 1 个 16 位整型数据。
Task<int> ReadIntAsync(ushort startAddress);
//向 PLC 的指定线圈地址,写入一个布尔值(开关量)。
Task WriteCoilAsync(ushort coilAddress, bool value);

View File

@@ -15,6 +15,7 @@ namespace TabletTester2025.Services
private readonly PlcConfiguration _config;
private readonly SemaphoreSlim _connectLock = new(1, 1);
private readonly SemaphoreSlim _ioLock = new(1, 1);
private TcpClient? _tcpClient;
private IModbusMaster? _master;
@@ -95,7 +96,7 @@ namespace TabletTester2025.Services
public async Task<float> ReadFloatAsync(ushort startAddress)
{
var registers = await ReadHoldingRegistersAsync(startAddress, 2);
return UshortToFloat(registers[1], registers[0]);
return RegistersToFloat(registers[0], registers[1]);
}
public async Task<int> ReadIntAsync(ushort startAddress)
@@ -122,13 +123,10 @@ namespace TabletTester2025.Services
public Task WriteFloatAsync(ushort startAddress, float value)
{
byte[] bytes = BitConverter.GetBytes(value);
ushort[] registers =
{
(ushort)((bytes[2] << 8) | bytes[3]),
(ushort)((bytes[0] << 8) | bytes[1])
};
if (!float.IsFinite(value))
throw new ArgumentOutOfRangeException(nameof(value), "PLC浮点写入值不能是NaN或Infinity。");
ushort[] registers = FloatToRegisters(value);
return ExecuteAsync(master => master.WriteMultipleRegistersAsync(_config.SlaveId, startAddress, registers));
}
@@ -152,10 +150,11 @@ namespace TabletTester2025.Services
private async Task<T> ExecuteAsync<T>(Func<IModbusMaster, Task<T>> action)
{
await EnsureConnectedAsync();
await _ioLock.WaitAsync();
try
{
await EnsureConnectedAsync();
if (_master == null)
throw new InvalidOperationException("PLC连接未初始化");
@@ -166,18 +165,42 @@ namespace TabletTester2025.Services
CloseConnection();
throw;
}
finally
{
_ioLock.Release();
}
}
private static float UshortToFloat(ushort high, ushort low)
private float RegistersToFloat(ushort firstRegister, ushort secondRegister)
{
byte[] bytes = new byte[4];
bytes[0] = (byte)(high >> 8);
bytes[1] = (byte)(high & 0xFF);
bytes[2] = (byte)(low >> 8);
bytes[3] = (byte)(low & 0xFF);
return _config.FloatWordOrder == PlcFloatWordOrder.HighWordFirst
? WordsToFloat(firstRegister, secondRegister)
: WordsToFloat(secondRegister, firstRegister);
}
private static float WordsToFloat(ushort highWord, ushort lowWord)
{
byte[] bytes =
{
(byte)(lowWord & 0xFF),
(byte)(lowWord >> 8),
(byte)(highWord & 0xFF),
(byte)(highWord >> 8)
};
return BitConverter.ToSingle(bytes, 0);
}
private ushort[] FloatToRegisters(float value)
{
byte[] bytes = BitConverter.GetBytes(value);
ushort highWord = (ushort)((bytes[3] << 8) | bytes[2]);
ushort lowWord = (ushort)((bytes[1] << 8) | bytes[0]);
return _config.FloatWordOrder == PlcFloatWordOrder.HighWordFirst
? new[] { highWord, lowWord }
: new[] { lowWord, highWord };
}
private void CloseConnection()
{
try { _master?.Dispose(); } catch { }
@@ -191,6 +214,7 @@ namespace TabletTester2025.Services
{
CloseConnection();
_connectLock.Dispose();
_ioLock.Dispose();
}
}

View File

@@ -17,11 +17,18 @@ namespace TabletTester2025.Services
// 模拟不同地址的数据
float value = startAddress switch
{
72 => 40 + (float)_rand.NextDouble() * 20, // 硬度最大采集力
100 => 40 + (float)_rand.NextDouble() * 20, // 硬度 40~60N
410 => 4.0f, // 脆碎试验时间(min)
1314 => 40 + (float)_rand.NextDouble() * 20, // 硬度实时力显示
82 => _rand.Next(0, 101), // 脆碎实时圈数
410 => 100f, // 脆碎圈数
412 => 5.0f + (float)_rand.NextDouble() * 2, // 脆碎度前重
414 => 4.9f + (float)_rand.NextDouble() * 2, // 后重
300 => 37.0f, // 温度
416 => 1.0f, // 失重率%
300 => 100.0f, // 硬度加压速度(mm/min)
330 => 31.0f, // 崩解升降频次(次/min)
340 => 50f, // 溶出速度1(r/min)
350 => 50f, // 溶出速度2(r/min)
400 => 50 + (float)_rand.NextDouble() * 30, // 转速
402 => 70 + (float)_rand.NextDouble() * 30, // 溶出度%
404 => 70 + (float)_rand.NextDouble() * 30, // 溶出2溶出度%
@@ -34,8 +41,14 @@ namespace TabletTester2025.Services
// 👇 新增 ReadIntAsync 的模拟实现
public Task<int> ReadIntAsync(ushort startAddress)
{
// 模拟整数返回比如返回0-1000之间的随机数
return Task.FromResult(_rand.Next(0, 1000));
int value = startAddress switch
{
410 => 100, // 脆碎圈数
430 => 30, // 溶出1时间(min)
440 => 30, // 溶出2时间(min)
_ => _rand.Next(0, 1000)
};
return Task.FromResult(value);
}
public Task WriteCoilAsync(ushort coilAddress, bool value) => Task.CompletedTask;

View File

@@ -7,6 +7,7 @@ namespace TabletTester2025.Services
{
public readonly record struct HardnessStatistics(
double Average,
double AverageDeviation,
double RsdPercent,
double Maximum,
double Minimum,
@@ -26,9 +27,10 @@ namespace TabletTester2025.Services
.ToList();
if (validValues.Count == 0)
return new HardnessStatistics(0, 0, 0, 0, 0, false);
return new HardnessStatistics(0, 0, 0, 0, 0, 0, false);
double average = validValues.Average();
double averageDeviation = validValues.Average(value => Math.Abs(value - average));
double standardDeviation = CalculateSampleStandardDeviation(validValues, average);
double rsd = average == 0 ? 0 : standardDeviation / average * 100;
bool countMet = validValues.Count >= Math.Max(1, requiredCount);
@@ -36,6 +38,7 @@ namespace TabletTester2025.Services
return new HardnessStatistics(
average,
averageDeviation,
rsd,
validValues.Max(),
validValues.Min(),

View File

@@ -0,0 +1,79 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
namespace TabletTester2025.ViewModels
{
public class HardnessDisplaySamplePoint : ObservableObject
{
private int _groupNo;
private int _cumulativeNo;
private int _sequenceNo;
private double _value;
private double _deviationFromAverage;
private double _groupAverage;
private double _groupAverageDeviation;
private double _groupRSD;
private DateTime _recordedAt;
private bool _isSummaryRow;
public bool IsSummaryRow
{
get => _isSummaryRow;
set => SetProperty(ref _isSummaryRow, value);
}
public int GroupNo
{
get => _groupNo;
set => SetProperty(ref _groupNo, value);
}
public int CumulativeNo
{
get => _cumulativeNo;
set => SetProperty(ref _cumulativeNo, value);
}
public int SequenceNo
{
get => _sequenceNo;
set => SetProperty(ref _sequenceNo, value);
}
public double Value
{
get => _value;
set => SetProperty(ref _value, value);
}
public double DeviationFromAverage
{
get => _deviationFromAverage;
set => SetProperty(ref _deviationFromAverage, value);
}
public double GroupAverage
{
get => _groupAverage;
set => SetProperty(ref _groupAverage, value);
}
public double GroupAverageDeviation
{
get => _groupAverageDeviation;
set => SetProperty(ref _groupAverageDeviation, value);
}
public double GroupRSD
{
get => _groupRSD;
set => SetProperty(ref _groupRSD, value);
}
public DateTime RecordedAt
{
get => _recordedAt;
set => SetProperty(ref _recordedAt, value);
}
}
}

View File

@@ -6,7 +6,6 @@ using System.Windows;
using System.Windows.Threading;
using TabletTester2025.Models;
using TabletTester2025.Services;
using .Views;
namespace TabletTester2025.ViewModels
{
@@ -34,7 +33,6 @@ namespace TabletTester2025.ViewModels
public IAsyncRelayCommand OpenSettingsCommand { get; }
public IAsyncRelayCommand OpenHistoryCommand { get; }
public IAsyncRelayCommand OpenCalibrationCommand { get; }
public IAsyncRelayCommand ShowDataCommand { get; }
public MainViewModel(IPlcService plc, DatabaseService db, ExcelExportService excel, AlarmService alarm, PlcConfiguration plcConfig)
{
@@ -61,20 +59,12 @@ namespace TabletTester2025.ViewModels
OpenSettingsCommand = new AsyncRelayCommand(() =>
{
var window = new SettingsWindow();
window.Owner = Application.Current.MainWindow;
if (window.ShowDialog() == true)
Tester.ApplyPharmaDefaults();
return Task.CompletedTask;
});
OpenHistoryCommand = new AsyncRelayCommand(() => { new HistoryWindow().ShowDialog(); return Task.CompletedTask; });
// 跳转到 PlcDataPage 页面
ShowDataCommand = new AsyncRelayCommand(async () =>
{
// 用你项目里已有的PLC实例假设叫 _plcClient
var window = new ShowData(_plc);
window.ShowDialog();
});
OpenHistoryCommand = new AsyncRelayCommand(() => { new HistoryWindow(_db).ShowDialog(); return Task.CompletedTask; });
}
private async Task ConnectToPlc()

File diff suppressed because it is too large Load Diff

View File

@@ -15,15 +15,17 @@
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="RowBackground" Value="White"/>
<Setter Property="AlternatingRowBackground" Value="#F8FAFC"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="FontSize" Value="{StaticResource TableFontSize}"/>
<Setter Property="HeadersVisibility" Value="Column"/>
<Setter Property="AutoGenerateColumns" Value="False"/>
<Setter Property="CanUserResizeColumns" Value="True"/>
<Setter Property="CanUserSortColumns" Value="True"/>
<Setter Property="GridLinesVisibility" Value="Horizontal"/>
<Setter Property="RowHeight" Value="32"/>
<Setter Property="RowHeight" Value="38"/>
<Setter Property="SelectionMode" Value="Single"/>
<Setter Property="SelectionUnit" Value="FullRow"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
</Style>
<!-- 表头样式 -->
@@ -31,8 +33,8 @@
<Setter Property="Background" Value="#EFF3F6"/>
<Setter Property="Foreground" Value="#1E293B"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Height" Value="36"/>
<Setter Property="FontSize" Value="{StaticResource TableFontSize}"/>
<Setter Property="Height" Value="40"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="BorderBrush" Value="#E2E8F0"/>
<Setter Property="BorderThickness" Value="0,0,1,1"/>
@@ -41,7 +43,7 @@
<!-- 按钮样式 -->
<Style TargetType="Button" x:Key="ActionButton">
<Setter Property="MinWidth" Value="80"/>
<Setter Property="Height" Value="34"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Margin" Value="6,0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Background" Value="#FFFFFF"/>
@@ -49,7 +51,7 @@
<Setter Property="BorderBrush" Value="#CBD5E1"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="FontSize" Value="{StaticResource TableFontSize}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
@@ -94,15 +96,37 @@
</Setter>
</Style>
<Style TargetType="Button" x:Key="DangerButton" BasedOn="{StaticResource ActionButton}">
<Setter Property="Background" Value="#DC2626"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#DC2626"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}" CornerRadius="6"
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1"
Padding="12,0">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#B91C1C"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- TabControl 样式 -->
<Style TargetType="TabControl">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
<Style TargetType="TabItem">
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontSize" Value="{StaticResource BaseFontSize}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Padding" Value="16,8"/>
<Setter Property="Padding" Value="18,10"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="#475569"/>
<Setter Property="Template">
@@ -143,7 +167,7 @@
<!-- 标题栏 -->
<Border Grid.Row="0" Background="#F8FAFC" CornerRadius="16,16,0,0" Padding="20,16">
<TextBlock Text="📋 历史检测记录" FontSize="20" FontWeight="Bold"
<TextBlock Text="📋 历史检测记录" FontSize="{StaticResource TitleFontSize}" FontWeight="Bold"
Foreground="#0F172A" HorizontalAlignment="Center"/>
</Border>
@@ -163,19 +187,22 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="📎 导出硬度报表" Style="{StaticResource ExportButton}"
Width="140" HorizontalAlignment="Right" Margin="0,0,0,12" Click="ExportHardness_Click"/>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,0,12">
<Button Content="🗑️ 删除选中" Style="{StaticResource DangerButton}"
Width="120" Margin="0,0,8,0" Click="DeleteHardness_Click"/>
<Button Content="📎 导出硬度报表" Style="{StaticResource ExportButton}"
Width="140" Click="ExportHardness_Click"/>
</StackPanel>
<DataGrid x:Name="HardnessGrid" Grid.Row="1">
<DataGrid.Columns>
<DataGridTextColumn Header="检测时间" Binding="{Binding TestTime, StringFormat=yyyy-MM-dd HH:mm:ss}" Width="160"/>
<DataGridTextColumn Header="样品名称" Binding="{Binding SampleName}" Width="120"/>
<DataGridTextColumn Header="平均值(N)" Binding="{Binding HardnessAvg, StringFormat=F1}" Width="90"/>
<DataGridTextColumn Header="RSD(%)" Binding="{Binding HardnessRSD, StringFormat=F1}" Width="80"/>
<DataGridTextColumn Header="平均偏差(N)" Binding="{Binding HardnessAverageDeviation, StringFormat=F2}" Width="110"/>
<DataGridTextColumn Header="RSD" Binding="{Binding HardnessRSD, StringFormat=F1}" Width="80"/>
<DataGridTextColumn Header="最大值(N)" Binding="{Binding HardnessMax, StringFormat=F1}" Width="90"/>
<DataGridTextColumn Header="最小值(N)" Binding="{Binding HardnessMin, StringFormat=F1}" Width="90"/>
<DataGridTextColumn Header="测试次数" Binding="{Binding HardnessTestCount}" Width="70"/>
<DataGridTextColumn Header="内控下限(N)" Binding="{Binding HardnessInternalMin, StringFormat=F1}" Width="100"/>
<DataGridTextColumn Header="内控上限(N)" Binding="{Binding HardnessInternalMax, StringFormat=F1}" Width="100"/>
<DataGridTextColumn Header="单次数据(N)" Binding="{Binding HardnessSampleSummary}" Width="220"/>
<DataGridTextColumn Header="判定" Binding="{Binding HardnessPassText}" Width="70"/>
</DataGrid.Columns>
</DataGrid>
@@ -189,8 +216,12 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="📎 导出脆碎度报表" Style="{StaticResource ExportButton}"
Width="150" HorizontalAlignment="Right" Margin="0,0,0,12" Click="ExportFriability_Click"/>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,0,12">
<Button Content="🗑️ 删除选中" Style="{StaticResource DangerButton}"
Width="120" Margin="0,0,8,0" Click="DeleteFriability_Click"/>
<Button Content="📎 导出脆碎度报表" Style="{StaticResource ExportButton}"
Width="150" Click="ExportFriability_Click"/>
</StackPanel>
<DataGrid x:Name="FriabilityGrid" Grid.Row="1">
<DataGrid.Columns>
<DataGridTextColumn Header="检测时间" Binding="{Binding TestTime, StringFormat=yyyy-MM-dd HH:mm:ss}" Width="160"/>
@@ -214,16 +245,17 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="📎 导出崩解报表" Style="{StaticResource ExportButton}"
Width="150" HorizontalAlignment="Right" Margin="0,0,0,12" Click="ExportDisintegration_Click"/>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,0,12">
<Button Content="🗑️ 删除选中" Style="{StaticResource DangerButton}"
Width="120" Margin="0,0,8,0" Click="DeleteDisintegration_Click"/>
<Button Content="📎 导出崩解报表" Style="{StaticResource ExportButton}"
Width="150" Click="ExportDisintegration_Click"/>
</StackPanel>
<DataGrid x:Name="DisintegrationGrid" Grid.Row="1">
<DataGrid.Columns>
<DataGridTextColumn Header="检测时间" Binding="{Binding TestTime, StringFormat=yyyy-MM-dd HH:mm:ss}" Width="160"/>
<DataGridTextColumn Header="样品名称" Binding="{Binding SampleName}" Width="120"/>
<DataGridTextColumn Header="剂型规格" Binding="{Binding DisintegrationDosageForm}" Width="100"/>
<DataGridTextColumn Header="时限(秒)" Binding="{Binding DisintegrationLimitSeconds}" Width="80"/>
<DataGridTextColumn Header="崩解时间(秒)" Binding="{Binding DisintegrationTimeSec}" Width="100"/>
<DataGridTextColumn Header="剩余未崩解管" Binding="{Binding RemainingTubesAtEnd}" Width="110"/>
<DataGridTextColumn Header="水浴温度(℃)" Binding="{Binding DisintegrationTemp, StringFormat=F1}" Width="110"/>
<DataGridTextColumn Header="判定" Binding="{Binding DisintegrationPassText}" Width="70"/>
</DataGrid.Columns>
@@ -238,8 +270,12 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="📎 导出溶出报表" Style="{StaticResource ExportButton}"
Width="150" HorizontalAlignment="Right" Margin="0,0,0,12" Click="ExportDissolution_Click"/>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,0,12">
<Button Content="🗑️ 删除选中" Style="{StaticResource DangerButton}"
Width="120" Margin="0,0,8,0" Click="DeleteDissolution_Click"/>
<Button Content="📎 导出溶出报表" Style="{StaticResource ExportButton}"
Width="150" Click="ExportDissolution_Click"/>
</StackPanel>
<DataGrid x:Name="DissolutionGrid" Grid.Row="1">
<DataGrid.Columns>
<DataGridTextColumn Header="检测时间" Binding="{Binding TestTime, StringFormat=yyyy-MM-dd HH:mm:ss}" Width="160"/>

View File

@@ -13,11 +13,10 @@ namespace TabletTester2025
private readonly DatabaseService _dbService;
private List<TestBatch> _allData;
public HistoryWindow()
public HistoryWindow(DatabaseService dbService)
{
InitializeComponent();
var connectionString = "Data Source=TabletTests.db";
_dbService = new DatabaseService(connectionString);
_dbService = dbService ?? throw new ArgumentNullException(nameof(dbService));
LoadData();
}
@@ -43,6 +42,66 @@ namespace TabletTester2025
LoadData();
}
private void DeleteHardness_Click(object sender, RoutedEventArgs e)
{
if (HardnessGrid.SelectedItem is not TestBatch batch)
{
MessageBox.Show("请先选择一条记录", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var result = MessageBox.Show($"确定要删除该记录吗?\n检测时间{batch.TestTime:yyyy-MM-dd HH:mm:ss}",
"确认删除", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result != MessageBoxResult.Yes)
return;
_dbService.DeleteBatch(batch.Id);
LoadData();
}
private void DeleteFriability_Click(object sender, RoutedEventArgs e)
{
if (FriabilityGrid.SelectedItem is not TestBatch batch)
{
MessageBox.Show("请先选择一条记录", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var result = MessageBox.Show($"确定要删除该记录吗?\n检测时间{batch.TestTime:yyyy-MM-dd HH:mm:ss}",
"确认删除", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result != MessageBoxResult.Yes)
return;
_dbService.DeleteBatch(batch.Id);
LoadData();
}
private void DeleteDisintegration_Click(object sender, RoutedEventArgs e)
{
if (DisintegrationGrid.SelectedItem is not TestBatch batch)
{
MessageBox.Show("请先选择一条记录", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var result = MessageBox.Show($"确定要删除该记录吗?\n检测时间{batch.TestTime:yyyy-MM-dd HH:mm:ss}",
"确认删除", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result != MessageBoxResult.Yes)
return;
_dbService.DeleteBatch(batch.Id);
LoadData();
}
private void DeleteDissolution_Click(object sender, RoutedEventArgs e)
{
if (DissolutionGrid.SelectedItem is not TestBatch batch)
{
MessageBox.Show("请先选择一条记录", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var result = MessageBox.Show($"确定要删除该记录吗?\n检测时间{batch.TestTime:yyyy-MM-dd HH:mm:ss}",
"确认删除", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result != MessageBoxResult.Yes)
return;
_dbService.DeleteBatch(batch.Id);
LoadData();
}
private void ExportHardness_Click(object sender, RoutedEventArgs e)
{
var data = HardnessGrid.ItemsSource as IEnumerable<TestBatch>;

View File

@@ -23,15 +23,15 @@
<SolidColorBrush x:Key="ValueBrush" Color="#102A43"/>
<Style TargetType="Button" x:Key="ActionButton">
<Setter Property="MinWidth" Value="112"/>
<Setter Property="Height" Value="42"/>
<Setter Property="Margin" Value="6"/>
<Setter Property="Padding" Value="14,0"/>
<Setter Property="MinWidth" Value="116"/>
<Setter Property="Height" Value="44"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Padding" Value="16,0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Background" Value="{StaticResource PrimaryBrush}"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontSize" Value="{StaticResource ButtonFontSize}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Template">
<Setter.Value>
@@ -78,9 +78,9 @@
</Style>
<Style TargetType="TabItem">
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontSize" Value="{StaticResource TabFontSize}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Padding" Value="24,12"/>
<Setter Property="Padding" Value="24,13"/>
<Setter Property="Foreground" Value="#465A6E"/>
<Setter Property="Template">
<Setter.Value>
@@ -109,44 +109,56 @@
<Style TargetType="GroupBox">
<Setter Property="BorderBrush" Value="{StaticResource PanelBorderBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="14"/>
<Setter Property="Padding" Value="16"/>
<Setter Property="Margin" Value="0,0,0,12"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontSize" Value="{StaticResource BaseFontSize}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Background" Value="{StaticResource PanelBackgroundBrush}"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="Width" Value="130"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Height" Value="42"/>
<Setter Property="Padding" Value="8,2"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="BorderBrush" Value="#B7C4D2"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontSize" Value="{StaticResource BaseFontSize}"/>
<Setter Property="helpers:NumericInput.IsEnabled" Value="True"/>
</Style>
<Style TargetType="DataGrid">
<Setter Property="FontSize" Value="{StaticResource TableFontSize}"/>
<Setter Property="RowHeight" Value="36"/>
<Setter Property="ColumnHeaderHeight" Value="38"/>
</Style>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontSize" Value="{StaticResource TableFontSize}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
<Style TargetType="RadioButton">
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontSize" Value="{StaticResource BaseFontSize}"/>
<Setter Property="Margin" Value="0,0,22,0"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
<Style TargetType="TextBlock" x:Key="ParamLabel">
<Setter Property="Width" Value="250"/>
<Setter Property="Width" Value="260"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="{StaticResource LabelBrush}"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontSize" Value="{StaticResource BaseFontSize}"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
<Style TargetType="StackPanel" x:Key="ParamRow">
<Setter Property="Orientation" Value="Horizontal"/>
<Setter Property="Margin" Value="0,6,26,6"/>
<Setter Property="Margin" Value="0,7,26,7"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="MinWidth" Value="400"/>
<Setter Property="MinWidth" Value="430"/>
</Style>
<Style TargetType="Border" x:Key="MetricCard">
@@ -154,14 +166,14 @@
<Setter Property="BorderBrush" Value="#E0E7EF"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="6"/>
<Setter Property="Padding" Value="12,10"/>
<Setter Property="Padding" Value="14,12"/>
<Setter Property="Margin" Value="6"/>
<Setter Property="MinHeight" Value="86"/>
<Setter Property="MinHeight" Value="94"/>
</Style>
<Style TargetType="TextBlock" x:Key="MetricLabel">
<Setter Property="Foreground" Value="{StaticResource LabelBrush}"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontSize" Value="{StaticResource BaseFontSize}"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="TextAlignment" Value="Center"/>
@@ -170,7 +182,7 @@
<Style TargetType="TextBlock" x:Key="MetricValue">
<Setter Property="Foreground" Value="{StaticResource ValueBrush}"/>
<Setter Property="FontSize" Value="26"/>
<Setter Property="FontSize" Value="{StaticResource MetricValueFontSize}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="TextAlignment" Value="Center"/>
@@ -194,14 +206,14 @@
<Style TargetType="TextBlock" x:Key="FooterLabel">
<Setter Property="Foreground" Value="#647487"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontSize" Value="{StaticResource SmallTextFontSize}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style TargetType="TextBlock" x:Key="FooterValue">
<Setter Property="Foreground" Value="#102A43"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
@@ -220,7 +232,7 @@
</Grid.RowDefinitions>
<TextBlock Text="{Binding LocalAlarm}"
FontSize="15"
FontSize="{StaticResource BaseFontSize}"
FontWeight="SemiBold"
Margin="0,0,0,8"
HorizontalAlignment="Center">
@@ -235,22 +247,22 @@
<DataTrigger Binding="{Binding LocalAlarm}" Value="">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding LocalAlarm}" Value="硬度测试完成">
<DataTrigger Binding="{Binding LocalAlarm}" Value="硬度测试完成,已保存">
<Setter Property="Foreground" Value="#1565C0"/>
</DataTrigger>
<DataTrigger Binding="{Binding LocalAlarm}" Value="脆碎度测试完成">
<DataTrigger Binding="{Binding LocalAlarm}" Value="脆碎度测试完成,已保存">
<Setter Property="Foreground" Value="#1565C0"/>
</DataTrigger>
<DataTrigger Binding="{Binding LocalAlarm}" Value="崩解测试完成">
<DataTrigger Binding="{Binding LocalAlarm}" Value="崩解测试完成,已保存">
<Setter Property="Foreground" Value="#1565C0"/>
</DataTrigger>
<DataTrigger Binding="{Binding LocalAlarm}" Value="溶出测试完成">
<DataTrigger Binding="{Binding LocalAlarm}" Value="溶出测试完成,已保存">
<Setter Property="Foreground" Value="#1565C0"/>
</DataTrigger>
<DataTrigger Binding="{Binding LocalAlarm}" Value="溶出1测试完成">
<DataTrigger Binding="{Binding LocalAlarm}" Value="溶出1测试完成,已保存">
<Setter Property="Foreground" Value="#1565C0"/>
</DataTrigger>
<DataTrigger Binding="{Binding LocalAlarm}" Value="溶出2测试完成">
<DataTrigger Binding="{Binding LocalAlarm}" Value="溶出2测试完成,已保存">
<Setter Property="Foreground" Value="#1565C0"/>
</DataTrigger>
</Style.Triggers>
@@ -258,8 +270,10 @@
</TextBlock.Style>
</TextBlock>
<TabControl Grid.Row="1" FontSize="13" BorderThickness="0">
<TabControl Grid.Row="1" FontSize="{StaticResource BaseFontSize}" BorderThickness="0">
<TabItem Header="硬度">
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<Grid Margin="4,14,4,4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -269,39 +283,39 @@
<GroupBox Header="当前参数" Grid.Row="0" Margin="0,5">
<UniformGrid Columns="3" Margin="10">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,10,18,0">
<!--<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,10,18,0">
<TextBlock Text="内控下限(N)" Width="130" VerticalAlignment="Center"/>
<TextBlock Text="{Binding HardnessInternalMin, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding HardnessInternalMin, StringFormat=F1}" FontSize="{StaticResource ParamValueFontSize}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,10,18,0">
<TextBlock Text="内控上限(N)" Width="130" VerticalAlignment="Center"/>
<TextBlock Text="{Binding HardnessInternalMax, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<TextBlock Text="{Binding HardnessInternalMax, StringFormat=F1}" FontSize="{StaticResource ParamValueFontSize}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>-->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,10,0,0">
<TextBlock Text="测试次数:" Width="110" VerticalAlignment="Center"/>
<TextBlock Text="{Binding HardnessTestCount}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding HardnessTestCount}" FontSize="{StaticResource ParamValueFontSize}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
</UniformGrid>
</GroupBox>
<Grid Grid.Row="1">
<GroupBox Header="测试结果" Grid.Row="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<GroupBox Header="测试结果" Grid.Row="0">
<UniformGrid Columns="5">
<UniformGrid Grid.Row="0" Columns="4">
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="最大力值(N)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding HardnessMax, StringFormat=F1}" Foreground="#1565C0" Style="{StaticResource MetricValue}"/>
<TextBlock Text="实时力(N)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding HardnessShishilizhi, StringFormat=F1}" Foreground="#C62828" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="最力值(N)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding HardnessMin, StringFormat=F1}" Foreground="#2E7D32" Style="{StaticResource MetricValue}"/>
<TextBlock Text="最力值(N)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding HardnessMax, StringFormat=F1}" Foreground="#1565C0" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
<Border Style="{StaticResource MetricCard}">
@@ -312,26 +326,154 @@
</Border>
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="RSD(%)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="平均偏差(N)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding HardnessAverageDeviation, StringFormat=F2}" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="RSD" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding HardnessRSD, StringFormat=F2}" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="测试次数" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="本组次数" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding HardnessCurrentCount}" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="累计次数" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding HardnessTotalCount}" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
</UniformGrid>
</GroupBox>
<DataGrid Grid.Row="1"
ItemsSource="{Binding HardnessDisplaySamplePoints}"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
IsReadOnly="True"
HeadersVisibility="Column"
Margin="10,8,10,10"
MinHeight="160"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" BasedOn="{StaticResource {x:Type DataGridRow}}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSummaryRow}" Value="True">
<Setter Property="Background" Value="#DBEAFE"/>
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Header="累计次数" Width="85">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding CumulativeNo}" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSummaryRow}" Value="True">
<Setter Property="Text" Value=""/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="组号" Binding="{Binding GroupNo}" Width="65"/>
<DataGridTemplateColumn Header="组内序号" Width="80">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding SequenceNo}" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSummaryRow}" Value="True">
<Setter Property="Text" Value=""/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="硬度值(N)" Width="95">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value, StringFormat=F1}" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSummaryRow}" Value="True">
<Setter Property="Text" Value=""/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="与平均值偏差(N)" Width="125">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DeviationFromAverage, StringFormat=F2}" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSummaryRow}" Value="True">
<Setter Property="Text" Value=""/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="组平均值(N)" Binding="{Binding GroupAverage, StringFormat=F1}" Width="105"/>
<!--<DataGridTextColumn Header="组平均偏差(N)" Binding="{Binding GroupAverageDeviation, StringFormat=F2}" Width="120"/>-->
<DataGridTextColumn Header="组RSD" Binding="{Binding GroupRSD, StringFormat=F2}" Width="95"/>
<DataGridTemplateColumn Header="记录时间" Width="105">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding RecordedAt, StringFormat=HH:mm:ss}" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSummaryRow}" Value="True">
<Setter Property="Text" Value=""/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</GroupBox>
<WrapPanel Grid.Row="2" Style="{StaticResource CommandBar}">
<Button Command="{Binding StartHardnessCommand}" Content="开始" Style="{StaticResource StartButton}"/>
<Button Command="{Binding StopHardnessCommand}" Content="停止" Style="{StaticResource StopButton}"/>
<Button Command="{Binding HardnessResetCommand}" Content="复位" Style="{StaticResource ResetButton}"/>
<Button Command="{Binding HardnessResetCommand}" Content="{Binding HardnessResetButtonText}" Style="{StaticResource ResetButton}"/>
<Button Command="{Binding ClearHardnessRecordsCommand}" Content="清空记录" Style="{StaticResource SecondaryButton}"/>
</WrapPanel>
</Grid>
</ScrollViewer>
</TabItem>
<TabItem Header="脆碎度">
@@ -344,33 +486,35 @@
<GroupBox Header="当前参数" Grid.Row="0">
<WrapPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="试验时间(min)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding FriabilityTargetTimeMin, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="转速设置(r/min)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding FriabilityTargetRpm, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding FriabilityTargetRpm, StringFormat=F1}" FontSize="{StaticResource ParamValueFontSize}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="试验转数(转)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding FriabilityTargetRounds}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding FriabilityTargetRounds}" FontSize="{StaticResource ParamValueFontSize}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="失重率限度(%)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding FriabilityMaxLossPercent, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding FriabilityMaxLossPercent, StringFormat=F1}" FontSize="{StaticResource ParamValueFontSize}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
</WrapPanel>
</GroupBox>
<GroupBox Header="测试结果" Grid.Row="1">
<UniformGrid Columns="3">
<UniformGrid Columns="4">
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="实时圈数" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding FriabilityRealtimeRounds, StringFormat=F1}" Foreground="#1565C0" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="脆碎前质量(g)" Style="{StaticResource MetricLabel}"/>
<TextBox Text="{Binding WeightBefore, Mode=TwoWay, UpdateSourceTrigger=LostFocus, StringFormat=F3}"
Width="150"
FontSize="22"
FontSize="{StaticResource LargeInputFontSize}"
FontWeight="SemiBold"
HorizontalContentAlignment="Center"
Margin="0,8,0,0"/>
@@ -381,7 +525,7 @@
<TextBlock Text="脆碎后质量(g)" Style="{StaticResource MetricLabel}"/>
<TextBox Text="{Binding WeightAfter, Mode=TwoWay, UpdateSourceTrigger=LostFocus, StringFormat=F3}"
Width="150"
FontSize="22"
FontSize="{StaticResource LargeInputFontSize}"
FontWeight="SemiBold"
HorizontalContentAlignment="Center"
Margin="0,8,0,0"/>
@@ -389,7 +533,7 @@
</Border>
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="脆碎度(%)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="失重率(%)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding LossPercent, StringFormat=F2}" Foreground="#1565C0" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
@@ -399,6 +543,7 @@
<WrapPanel Grid.Row="2" Style="{StaticResource CommandBar}">
<Button Command="{Binding StartFriabilityCommand}" Content="开始" Style="{StaticResource StartButton}"/>
<Button Command="{Binding StopFriabilityCommand}" Content="停止" Style="{StaticResource StopButton}"/>
<Button Command="{Binding SaveFriabilityResultCommand}" Content="保存记录" Style="{StaticResource SecondaryButton}" IsEnabled="{Binding CanSaveFriabilityResult}"/>
<!--<Button Command="{Binding ResetFriabilityCommand}" Content="复位" Style="{StaticResource ResetButton}"/>-->
</WrapPanel>
</Grid>
@@ -418,34 +563,64 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Header="当前参数" Grid.Row="0">
<WrapPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出1时间(min)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding Dissolution1TimeMin}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Dissolution1TimeMin}" FontSize="{StaticResource ParamValueFontSize}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出2时间(min)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding Dissolution2TimeMin}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Dissolution2TimeMin}" FontSize="{StaticResource ParamValueFontSize}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出1间隔取样时间(min)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding Dissolution1SampleIntervalMin, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Dissolution1SampleIntervalMin, StringFormat=F0}" FontSize="{StaticResource ParamValueFontSize}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出2间隔取样时间(min)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding Dissolution2SampleIntervalMin, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Dissolution2SampleIntervalMin, StringFormat=F0}" FontSize="{StaticResource ParamValueFontSize}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="30min最低溶出度Q(%)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding DissolutionMinPercentAt30Min, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="介质温度(℃)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding DisintegrationTemp, StringFormat=F1}" FontSize="{StaticResource ParamValueFontSize}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
</WrapPanel>
</GroupBox>
<GroupBox Header="测试结果" Grid.Row="1">
<GroupBox Header="运行计时" Grid.Row="1">
<UniformGrid Columns="4">
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="溶出1累计(min)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding Dissolution1ElapsedTime, StringFormat=F1}" Foreground="#2E7D32" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="溶出1倒计时(min)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding Dissolution1Countdown, StringFormat=F1}" Foreground="#2E7D32" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="溶出2累计(min)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding Dissolution2ElapsedTime, StringFormat=F1}" Foreground="#1565C0" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="溶出2倒计时(min)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding Dissolution2Countdown, StringFormat=F1}" Foreground="#1565C0" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
</UniformGrid>
</GroupBox>
<GroupBox Header="测试结果" Grid.Row="2">
<UniformGrid Columns="4">
<Border Style="{StaticResource MetricCard}">
<StackPanel>
@@ -474,7 +649,7 @@
</UniformGrid>
</GroupBox>
<GroupBox Header="溶出双曲线和R²值" Grid.Row="2">
<GroupBox Header="溶出双曲线和R²值" Grid.Row="3">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="240"/>
@@ -484,12 +659,12 @@
<oxy:PlotView Grid.Row="0" Model="{Binding DissolutionPlotModel}" Margin="4"/>
<WrapPanel Grid.Row="1" Margin="4,8,4,2">
<TextBlock Text="{Binding Dissolution1RSquared, StringFormat='溶出1 R² = {0:F4}'}"
FontSize="15"
FontSize="{StaticResource BaseFontSize}"
FontWeight="SemiBold"
Foreground="#2E7D32"
Margin="0,0,28,0"/>
<TextBlock Text="{Binding Dissolution2RSquared, StringFormat='溶出2 R² = {0:F4}'}"
FontSize="15"
FontSize="{StaticResource BaseFontSize}"
FontWeight="SemiBold"
Foreground="#1565C0"/>
</WrapPanel>
@@ -502,7 +677,7 @@
</Grid>
</GroupBox>
<GroupBox Header="取样记录" Grid.Row="3">
<GroupBox Header="取样记录" Grid.Row="4">
<DataGrid ItemsSource="{Binding DissolutionSamplePoints}"
AutoGenerateColumns="False"
HeadersVisibility="Column"
@@ -511,8 +686,8 @@
IsReadOnly="True"
MinHeight="150"
MaxHeight="240"
RowHeight="34"
FontSize="14"
RowHeight="36"
FontSize="{StaticResource TableFontSize}"
GridLinesVisibility="Horizontal">
<DataGrid.Columns>
<DataGridTextColumn Header="通道" Binding="{Binding ChannelName}" Width="90"/>
@@ -548,47 +723,47 @@
<GroupBox Header="当前参数" Grid.Row="0">
<WrapPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="剂型规格:" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding DisintegrationDosageForm}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="崩解速度(r/min)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding DisintegrationSpeedRpm, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="崩解时间(min)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding DisintegrationTimeMin, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding DisintegrationTimeMin, StringFormat=F1}" FontSize="{StaticResource ParamValueFontSize}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="介质温度(℃)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding DisintegrationTemp, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding DisintegrationTemp, StringFormat=F1}" FontSize="{StaticResource ParamValueFontSize}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
</WrapPanel>
</GroupBox>
<GroupBox Header="测试结果" Grid.Row="1">
<UniformGrid Columns="2">
<UniformGrid Columns="1">
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="崩解时间(秒)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding DisintegrationSeconds}" Style="{StaticResource MetricValue}"/>
<TextBlock Text="实际崩解时间(秒)" Style="{StaticResource MetricLabel}"/>
<TextBox Text="{Binding DisintegrationActualSecondsText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="160"
Height="50"
Margin="0,8,0,0"
HorizontalAlignment="Center"
IsEnabled="{Binding CanSaveDisintegrationResult}"
TextAlignment="Center"
FontSize="{StaticResource MetricValueFontSize}"
FontWeight="SemiBold"
Foreground="{StaticResource ValueBrush}"/>
</StackPanel>
</Border>
<Border Style="{StaticResource MetricCard}">
<!--<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="剩余未崩解管数" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding RemainingTubes}" Foreground="#C62828" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
</Border>-->
</UniformGrid>
</GroupBox>
<WrapPanel Grid.Row="2" Style="{StaticResource CommandBar}">
<Button Command="{Binding StartDisintegrationCommand}" Content="开始" Style="{StaticResource StartButton}"/>
<Button Command="{Binding StopDisintegrationCommand}" Content="停止" Style="{StaticResource StopButton}"/>
<Button Command="{Binding SaveDisintegrationResultCommand}" Content="保存记录" Style="{StaticResource SecondaryButton}" />
<!--<Button Command="{Binding ResetDisintegrationCommand}" Content="复位" Style="{StaticResource ResetButton}"/>-->
</WrapPanel>
</Grid>
@@ -608,12 +783,18 @@
<Border Background="#0F3D68" CornerRadius="6" Margin="0,0,0,8" Padding="12,10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="片剂四用仪 硬度 · 脆碎度 · 溶出 · 崩解"
FontSize="22"
FontSize="{StaticResource TitleFontSize}"
FontWeight="Bold"
Foreground="White"
VerticalAlignment="Center"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"
Margin="0,0,12,0"/>
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
<Button Command="{Binding OpenSettingsCommand}" Content="参数设置" Style="{StaticResource ActionButton}"/>
<Button Command="{Binding OpenHistoryCommand}" Content="历史记录" Style="{StaticResource ActionButton}"/>
<Button Command="{Binding ExportAllCommand}" Content="导出报告" Style="{StaticResource ActionButton}"/>
@@ -673,21 +854,12 @@
<TextBlock Grid.Column="1"
Text="{Binding GlobalAlarm}"
Foreground="#C62828"
FontSize="14"
FontSize="{StaticResource SmallTextFontSize}"
FontWeight="SemiBold"
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
</Grid>
</Border>
<Button Grid.Column="3"
Background="Transparent"
BorderThickness="0"
Opacity="0"
Width="18"
Height="18"
Margin="8,0,0,0"
Command="{Binding ShowDataCommand}"/>
</Grid>
</Border>
</Grid>

View File

@@ -13,14 +13,14 @@
<Setter Property="MinWidth" Value="72"/>
<Setter Property="MinHeight" Value="58"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="FontSize" Value="22"/>
<Setter Property="FontSize" Value="{StaticResource KeypadButtonFontSize}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="#C7D2DE"/>
<Setter Property="BorderThickness" Value="1"/>
</Style>
<Style TargetType="Button" x:Key="CommandButton" BasedOn="{StaticResource KeyButton}">
<Setter Property="FontSize" Value="18"/>
<Setter Property="FontSize" Value="{StaticResource KeypadCommandFontSize}"/>
<Setter Property="Background" Value="#E9F1F8"/>
</Style>
</Window.Resources>
@@ -36,7 +36,7 @@
Grid.Row="0"
Height="58"
Padding="12,0"
FontSize="28"
FontSize="{StaticResource KeypadValueFontSize}"
FontWeight="SemiBold"
HorizontalContentAlignment="Right"
VerticalContentAlignment="Center"
@@ -74,7 +74,7 @@
<TextBlock Grid.Row="2"
Text="点击数字输入,确定后写入当前输入框"
Foreground="#526273"
FontSize="13"
FontSize="{StaticResource SmallTextFontSize}"
HorizontalAlignment="Center"/>
</Grid>
</Window>

View File

@@ -4,9 +4,10 @@
xmlns:helpers="clr-namespace:TabletTester2025.Helpers"
Title="参数设置 - 中国药典2025"
Width="1024"
Height="768"
MinWidth="900"
MinHeight="768"
WindowState="Maximized"
WindowStartupLocation="CenterScreen"
WindowStartupLocation="CenterOwner"
ResizeMode="CanResize"
Background="#EDF1F5">
<Window.Resources>
@@ -15,40 +16,47 @@
<Style TargetType="GroupBox">
<Setter Property="Margin" Value="0,0,0,14"/>
<Setter Property="Padding" Value="14"/>
<Setter Property="Padding" Value="16"/>
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="{StaticResource PanelBorderBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontSize" Value="{StaticResource BaseFontSize}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</Style>
<Style TargetType="TextBlock" x:Key="ParamLabel">
<Setter Property="Width" Value="220"/>
<Setter Property="Width" Value="230"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="#526273"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontSize" Value="{StaticResource BaseFontSize}"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="Width" Value="180"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Height" Value="42"/>
<Setter Property="Padding" Value="8,2"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontSize" Value="{StaticResource BaseFontSize}"/>
<Setter Property="helpers:NumericInput.IsEnabled" Value="True"/>
</Style>
<Style TargetType="Button">
<Setter Property="FontSize" Value="{StaticResource ButtonFontSize}"/>
<Setter Property="MinHeight" Value="44"/>
<Setter Property="Padding" Value="16,0"/>
</Style>
<Style TargetType="StackPanel" x:Key="ParamRow">
<Setter Property="Orientation" Value="Horizontal"/>
<Setter Property="Margin" Value="0,6,32,6"/>
<Setter Property="Margin" Value="0,7,32,7"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style TargetType="TextBlock" x:Key="StandardNote">
<Setter Property="Foreground" Value="#6A7888"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontSize" Value="{StaticResource SmallTextFontSize}"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="Margin" Value="0,10,0,0"/>
@@ -63,29 +71,33 @@
</Grid.RowDefinitions>
<Border Background="#0F3D68" CornerRadius="6" Padding="14,10" Margin="0,0,0,14">
<TextBlock Text="药典与内控参数设置"
<TextBlock Text="参数设置"
Foreground="White"
FontSize="22"
FontSize="{StaticResource TitleFontSize}"
FontWeight="Bold"/>
</Border>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<StackPanel>
<GroupBox Header="硬度内控">
<GroupBox Header="硬度">
<StackPanel>
<WrapPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="内控下限(N):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="HardnessMinBox"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="内控上限(N):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="HardnessMaxBox"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="测试次数:" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="HardnessCountBox" helpers:NumericInput.AllowDecimal="False"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="加压压力:" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="HardnessPressureBox"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="加压速度(mm/min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="HardnessSpeedBox"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="硬度破损判定(N):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="HardnessDamageThresholdBox" helpers:NumericInput.AllowDecimal="True"/>
</StackPanel>
</WrapPanel>
<TextBlock Text="硬度没有统一药典数值限度,此处作为企业内控范围和本机测试次数。"
Style="{StaticResource StandardNote}"/>
@@ -97,27 +109,23 @@
<WrapPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="转速(r/min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="FriabilityRpmBox" TextChanged="FriabilityCalculationBox_TextChanged"/>
<TextBox x:Name="FriabilityRpmBox"/>
</StackPanel>
<!--<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="试验时间(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="FriabilityTimeBox"/>
</StackPanel>-->
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="试验次数(次):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="FriabilityTimeBox" TextChanged="FriabilityCalculationBox_TextChanged"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="试验转数(自动计算):" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="脆碎圈数:" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="FriabilityRoundsBox"
helpers:NumericInput.AllowDecimal="False"
helpers:NumericInput.IsEnabled="False"
IsReadOnly="True"
Background="#F3F7FA"
Foreground="#526273"/>
helpers:NumericInput.AllowDecimal="False"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="最大失重率(%):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="FriabilityMaxLossBox"/>
</StackPanel>
</WrapPanel>
<TextBlock Text="默认转速25±1 r/min试验时间4min自动计算100转减失重量不得过1.0%,且不得检出断裂、龟裂或粉碎的片。"
<TextBlock Text="默认转速25±1 r/min试验时间4min脆碎圈数100转减失重量不得过1.0%,且不得检出断裂、龟裂或粉碎的片。"
Style="{StaticResource StandardNote}"/>
</StackPanel>
</GroupBox>
@@ -126,32 +134,15 @@
<StackPanel>
<WrapPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="剂型规格:" Style="{StaticResource ParamLabel}"/>
<ComboBox x:Name="DisintegrationDosageFormBox"
Width="180"
Height="40"
FontSize="15"
SelectionChanged="DisintegrationDosageFormBox_SelectionChanged">
<ComboBoxItem Content="普通片" Tag="900"/>
<ComboBoxItem Content="薄膜衣片" Tag="1800"/>
<ComboBoxItem Content="糖衣片" Tag="3600"/>
<ComboBoxItem Content="胶囊" Tag="1800"/>
</ComboBox>
<TextBlock Text="崩解时间(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="DisintegrationTimeMinBox"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="最长崩解时间(秒):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="DisintegrationMaxSecBox" helpers:NumericInput.AllowDecimal="False"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="升降频率(次/min):" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="升降频次(次/min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="DisintegrationSpeedBox"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="介质温度(℃):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="DisintegrationTempBox"/>
</StackPanel>
</WrapPanel>
<TextBlock Text="默认升降频率30-32次/min介质温度37±1℃。不同剂型按药典或品种正文规定时限执行。"
<TextBlock Text="崩解时间按照样品测试设置,保存后立即写入设备。"
Style="{StaticResource StandardNote}"/>
</StackPanel>
</GroupBox>
@@ -159,38 +150,38 @@
<GroupBox Header="溶出度">
<StackPanel>
<WrapPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="30分钟最低溶出度Q(%):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="DissolutionMinPercentBox"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<!--<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出介质温度(℃):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="DissolutionTempBox"/>
</StackPanel>-->
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出速度1(r/min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="Dissolution1SpeedBox"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出1运行时间(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="Dissolution1TimeBox" helpers:NumericInput.AllowDecimal="False"/>
<TextBlock Text="溶出速度2(r/min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="Dissolution2SpeedBox"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出2运行时间(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="Dissolution2TimeBox" helpers:NumericInput.AllowDecimal="False"/>
<TextBlock Text="溶出1时间(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="Dissolution1TimeBox"
helpers:NumericInput.AllowDecimal="False"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出2时间(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="Dissolution2TimeBox"
helpers:NumericInput.AllowDecimal="False"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出1取样间隔(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="Dissolution1IntervalBox"/>
<TextBox x:Name="Dissolution1IntervalBox" helpers:NumericInput.AllowDecimal="True"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出2取样间隔(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="Dissolution2IntervalBox"/>
<TextBox x:Name="Dissolution2IntervalBox" helpers:NumericInput.AllowDecimal="True"/>
</StackPanel>
</WrapPanel>
<StackPanel Style="{StaticResource ParamRow}" Margin="0,8,0,0">
<TextBlock Text="取样时间点(分钟,逗号分隔):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="SampleTimesBox"
Width="430"
helpers:NumericInput.IsEnabled="False"/>
</StackPanel>
<TextBlock Text="默认普通制剂通常取6片溶出介质温度37±0.5℃Q值、介质、转速和取样点应按具体品种正文或企业批准标准录入。"
<TextBlock Text="默认普通制剂通常取6片溶出介质温度37±0.5℃;转速和取样间隔应按样品种类录入。"
Style="{StaticResource StandardNote}"/>
</StackPanel>
</GroupBox>
@@ -200,8 +191,8 @@
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,16,0,0">
<Button x:Name="SaveButton"
Content="保存"
Width="110"
Height="40"
Width="116"
Height="44"
Margin="6"
Click="SaveButton_Click"
Background="{StaticResource PrimaryBrush}"
@@ -210,8 +201,8 @@
FontWeight="SemiBold"/>
<Button x:Name="CancelButton"
Content="取消"
Width="110"
Height="40"
Width="116"
Height="44"
Margin="6"
Click="CancelButton_Click"
Background="#687789"

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using TabletTester2025.Models;
@@ -12,61 +13,105 @@ namespace TabletTester2025
{
InitializeComponent();
LoadSettings();
Loaded += SettingsWindow_Loaded;
}
private async void SettingsWindow_Loaded(object sender, RoutedEventArgs e)
{
await LoadHardnessPressureAsync();
await LoadHardnessSpeedAsync();
await LoadHardnessDamageThresholdAsync();
// 脆碎度
await LoadPlcFloatToTextBoxAsync(ResolveFriabilityRpmRegister(), FriabilityRpmBox);
await LoadPlcIntToTextBoxAsync(ResolveFriabilityRoundsRegister(), FriabilityRoundsBox);
// 崩解
await LoadPlcFloatToTextBoxAsync(ResolveDisintegrationSpeedRegister(), DisintegrationSpeedBox);
// 溶出度
await LoadPlcFloatToTextBoxAsync(ResolveDissolution1SpeedRegister(), Dissolution1SpeedBox);
await LoadPlcFloatToTextBoxAsync(ResolveDissolution2SpeedRegister(), Dissolution2SpeedBox);
await LoadPlcIntToTextBoxAsync(ResolveDissolution1TimeRegister(), Dissolution1TimeBox);
await LoadPlcIntToTextBoxAsync(ResolveDissolution2TimeRegister(), Dissolution2TimeBox);
await LoadPlcFloatToTextBoxAsync(ResolveDissolution1IntervalRegister(), Dissolution1IntervalBox);
await LoadPlcFloatToTextBoxAsync(ResolveDissolution2IntervalRegister(), Dissolution2IntervalBox);
}
private void LoadSettings()
{
var p = App.CurrentPharmaParams;
HardnessMinBox.Text = p.HardnessMin_N.ToString();
HardnessMaxBox.Text = p.HardnessMax_N.ToString();
HardnessCountBox.Text = p.HardnessTestCount.ToString();
FriabilityRpmBox.Text = p.FriabilityTargetRpm.ToString();
FriabilityTimeBox.Text = ResolveFriabilityTargetTimeMin(p).ToString("0.###");
FriabilityRoundsBox.Text = CalculateFriabilityRounds(
ResolveFriabilityTargetTimeMin(p),
p.FriabilityTargetRpm > 0 ? p.FriabilityTargetRpm : 25).ToString();
//FriabilityTimeBox.Text = ResolveFriabilityTargetTimeMin(p).ToString("0.###");
FriabilityRoundsBox.Text = ResolveFriabilityTargetRounds(p).ToString();
FriabilityMaxLossBox.Text = p.FriabilityMaxLossPercent.ToString();
SelectDisintegrationDosageForm(p.DisintegrationDosageForm);
DisintegrationMaxSecBox.Text = p.DisintegrationMaxSeconds.ToString();
DisintegrationSpeedBox.Text = p.DisintegrationSpeedRpm.ToString();
DisintegrationTempBox.Text = p.DisintegrationTemperatureC.ToString();
DissolutionMinPercentBox.Text = p.DissolutionMinPercentAt30min.ToString();
DissolutionTempBox.Text = p.DissolutionTemperatureC.ToString();
DisintegrationTimeMinBox.Text = ResolveDisintegrationTimeMin(p).ToString("0.###");
DisintegrationSpeedBox.Text = p.DisintegrationSpeedRpm.ToString("0.###");
//DisintegrationTempBox.Text = p.DisintegrationTemperatureC.ToString();
//DissolutionTempBox.Text = p.DissolutionTemperatureC.ToString();
Dissolution1TimeBox.Text = p.Dissolution1TimeMin.ToString();
Dissolution2TimeBox.Text = p.Dissolution2TimeMin.ToString();
Dissolution1IntervalBox.Text = p.Dissolution1SampleIntervalMin.ToString();
Dissolution2IntervalBox.Text = p.Dissolution2SampleIntervalMin.ToString();
SampleTimesBox.Text = string.Join(",", p.DissolutionSampleTimes);
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
private async void SaveButton_Click(object sender, RoutedEventArgs e)
{
try
{
var p = App.CurrentPharmaParams;
p.HardnessMin_N = double.Parse(HardnessMinBox.Text);
p.HardnessMax_N = double.Parse(HardnessMaxBox.Text);
var current = App.CurrentPharmaParams;
var p = new PharmaParameters
{
StandardVersion = current.StandardVersion,
HardnessMin_N = current.HardnessMin_N,
HardnessMax_N = current.HardnessMax_N,
FriabilityTargetTimeMin = current.FriabilityTargetTimeMin,
DissolutionMinPercentAt30min = current.DissolutionMinPercentAt30min,
DisintegrationDosageForm = current.DisintegrationDosageForm,
DisintegrationSpeedRpm = current.DisintegrationSpeedRpm,
DisintegrationTemperatureC = current.DisintegrationTemperatureC,
DissolutionTemperatureC = current.DissolutionTemperatureC,
Dissolution1TimeMin = current.Dissolution1TimeMin,
Dissolution2TimeMin = current.Dissolution2TimeMin,
DissolutionSampleTimes = current.DissolutionSampleTimes?.ToArray() ?? Array.Empty<int>()
};
p.HardnessTestCount = int.Parse(HardnessCountBox.Text);
p.FriabilityTargetRpm = double.Parse(FriabilityRpmBox.Text);
p.FriabilityTargetTimeMin = int.Parse(FriabilityTimeBox.Text);
p.FriabilityTargetRounds = CalculateFriabilityRounds(p.FriabilityTargetTimeMin, p.FriabilityTargetRpm);
p.FriabilityMaxLossPercent = double.Parse(FriabilityMaxLossBox.Text);
p.DisintegrationDosageForm = GetSelectedDisintegrationDosageForm();
p.DisintegrationMaxSeconds = int.Parse(DisintegrationMaxSecBox.Text);
p.DisintegrationSpeedRpm = double.Parse(DisintegrationSpeedBox.Text);
p.DisintegrationTemperatureC = double.Parse(DisintegrationTempBox.Text);
p.DissolutionMinPercentAt30min = double.Parse(DissolutionMinPercentBox.Text);
p.DissolutionTemperatureC = double.Parse(DissolutionTempBox.Text);
p.Dissolution1TimeMin = int.Parse(Dissolution1TimeBox.Text);
p.Dissolution2TimeMin = int.Parse(Dissolution2TimeBox.Text);
p.Dissolution1SampleIntervalMin = double.Parse(Dissolution1IntervalBox.Text);
p.Dissolution2SampleIntervalMin = double.Parse(Dissolution2IntervalBox.Text);
p.DissolutionSampleTimes = SampleTimesBox.Text
.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(s => int.Parse(s.Trim()))
.ToArray();
double hardnessPressure = ParseFiniteDouble(HardnessPressureBox.Text, "加压压力");
double hardnessSpeed = ParsePositiveDouble(HardnessSpeedBox.Text, "加压速度");
double hardnessDamageThreshold = ParsePositiveDouble(HardnessDamageThresholdBox.Text, "硬度破损判定");
double friabilityRpm = ParseFiniteDouble(FriabilityRpmBox.Text, "脆碎度转速");
p.FriabilityTargetRpm = friabilityRpm;
//p.FriabilityTargetTimeMin = ParseFiniteDouble(FriabilityTimeBox.Text, "脆碎度试验时间");
p.FriabilityTargetRounds = ParsePositiveInt(FriabilityRoundsBox.Text, "脆碎圈数");
p.FriabilityMaxLossPercent = ParseFiniteDouble(FriabilityMaxLossBox.Text, "最大失重率");
double disintegrationTimeMin = ParsePositiveDouble(DisintegrationTimeMinBox.Text, "崩解时间");
double disintegrationSpeed = ParsePositiveDouble(DisintegrationSpeedBox.Text, "升降频次");
p.DisintegrationSpeedRpm = disintegrationSpeed;
p.DisintegrationMaxSeconds = ToDisintegrationSeconds(disintegrationTimeMin);
//p.DisintegrationTemperatureC = ParseFiniteDouble(DisintegrationTempBox.Text, "崩解介质温度");
//p.DissolutionTemperatureC = ParseFiniteDouble(DissolutionTempBox.Text, "溶出介质温度");
double dissolution1Speed = ParsePositiveDouble(Dissolution1SpeedBox.Text, "溶出速度1");
double dissolution2Speed = ParsePositiveDouble(Dissolution2SpeedBox.Text, "溶出速度2");
p.Dissolution1TimeMin = ParsePositiveInt(Dissolution1TimeBox.Text, "溶出1时间");
p.Dissolution2TimeMin = ParsePositiveInt(Dissolution2TimeBox.Text, "溶出2时间");
p.Dissolution1SampleIntervalMin = ParsePositiveDouble(Dissolution1IntervalBox.Text, "溶出1取样间隔");
p.Dissolution2SampleIntervalMin = ParsePositiveDouble(Dissolution2IntervalBox.Text, "溶出2取样间隔");
ValidateParameters(p);
await WriteHardnessPressureAsync(hardnessPressure);
await WriteHardnessSpeedAsync(hardnessSpeed);
await WriteHardnessDamageThresholdAsync(hardnessDamageThreshold);
await WriteFriabilityRpmAsync(friabilityRpm);
await WriteDisintegrationTimeAsync(disintegrationTimeMin);
await WriteDisintegrationSpeedAsync(disintegrationSpeed);
await WriteDissolution1SpeedAsync(dissolution1Speed);
await WriteDissolution2SpeedAsync(dissolution2Speed);
await WriteDissolution1TimeAsync(p.Dissolution1TimeMin);
await WriteDissolution2TimeAsync(p.Dissolution2TimeMin);
await WriteDissolution1IntervalAsync(p.Dissolution1SampleIntervalMin);
await WriteDissolution2IntervalAsync(p.Dissolution2SampleIntervalMin);
App.CurrentPharmaParams = p;
App.SaveCurrentPharmaParameters();
MessageBox.Show("参数已保存并立即生效", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
@@ -85,61 +130,25 @@ namespace TabletTester2025
Close();
}
private void DisintegrationDosageFormBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (DisintegrationDosageFormBox.SelectedItem is ComboBoxItem item && item.Tag is string seconds)
DisintegrationMaxSecBox.Text = seconds;
}
private void FriabilityCalculationBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (FriabilityRoundsBox == null)
return;
if (int.TryParse(FriabilityTimeBox?.Text, out int timeMin)
&& double.TryParse(FriabilityRpmBox?.Text, out double rpm)
&& timeMin > 0
&& rpm > 0)
{
FriabilityRoundsBox.Text = CalculateFriabilityRounds(timeMin, rpm).ToString();
}
else
{
FriabilityRoundsBox.Text = "";
}
}
private void SelectDisintegrationDosageForm(string dosageForm)
{
foreach (ComboBoxItem item in DisintegrationDosageFormBox.Items)
{
if (string.Equals(item.Content?.ToString(), dosageForm, StringComparison.OrdinalIgnoreCase))
{
DisintegrationDosageFormBox.SelectedItem = item;
return;
}
}
DisintegrationDosageFormBox.SelectedIndex = 0;
}
private string GetSelectedDisintegrationDosageForm()
{
return DisintegrationDosageFormBox.SelectedItem is ComboBoxItem item
? item.Content?.ToString() ?? "普通片"
: "普通片";
}
private static void ValidateParameters(PharmaParameters p)
{
if (!double.IsFinite(p.HardnessMin_N) || !double.IsFinite(p.HardnessMax_N))
throw new InvalidOperationException("硬度参数必须为有效数字。");
if (p.HardnessMin_N < 0 || p.HardnessMax_N <= p.HardnessMin_N)
throw new InvalidOperationException("硬度内控上限必须大于下限。");
throw new InvalidOperationException("硬度上限必须大于下限。");
if (p.HardnessTestCount <= 0)
throw new InvalidOperationException("硬度测试次数必须大于0。");
if (!double.IsFinite(p.FriabilityTargetRpm) || !double.IsFinite(p.FriabilityTargetTimeMin) || !double.IsFinite(p.FriabilityMaxLossPercent))
throw new InvalidOperationException("脆碎度参数必须为有效数字。");
if (p.FriabilityTargetRpm <= 0 || p.FriabilityTargetTimeMin <= 0 || p.FriabilityTargetRounds <= 0 || p.FriabilityMaxLossPercent <= 0)
throw new InvalidOperationException("脆碎度参数必须大于0。");
if (!double.IsFinite(p.DisintegrationSpeedRpm) || !double.IsFinite(p.DisintegrationTemperatureC))
throw new InvalidOperationException("崩解参数必须为有效数字。");
if (p.DisintegrationMaxSeconds <= 0 || p.DisintegrationSpeedRpm <= 0 || p.DisintegrationTemperatureC <= 0)
throw new InvalidOperationException("崩解参数必须大于0。");
if (!double.IsFinite(p.DissolutionMinPercentAt30min) || !double.IsFinite(p.DissolutionTemperatureC)
|| !double.IsFinite(p.Dissolution1SampleIntervalMin) || !double.IsFinite(p.Dissolution2SampleIntervalMin))
throw new InvalidOperationException("溶出度参数必须为有效数字。");
if (p.DissolutionMinPercentAt30min < 0 || p.DissolutionMinPercentAt30min > 150)
throw new InvalidOperationException("溶出度Q值应在0到150之间。");
if (p.DissolutionTemperatureC <= 0 || p.Dissolution1TimeMin <= 0 || p.Dissolution2TimeMin <= 0)
@@ -147,7 +156,294 @@ namespace TabletTester2025
if (p.Dissolution1SampleIntervalMin <= 0 || p.Dissolution2SampleIntervalMin <= 0)
throw new InvalidOperationException("溶出取样间隔必须大于0。");
if (p.DissolutionSampleTimes == null || p.DissolutionSampleTimes.Length == 0 || p.DissolutionSampleTimes.Any(t => t <= 0))
throw new InvalidOperationException("溶出取样时间点必须为大于0的分钟。");
throw new InvalidOperationException("溶出取样配置必须为有效的正数分钟。");
}
private static double ParseFiniteDouble(string text, string fieldName)
{
if (!double.TryParse(text, out double value) || !double.IsFinite(value))
throw new InvalidOperationException($"{fieldName}必须为有效数字。");
if (value < 0)
throw new InvalidOperationException($"{fieldName}不能小于0。");
return value;
}
private static int ParsePositiveInt(string text, string fieldName)
{
if (!int.TryParse(text, out int value))
throw new InvalidOperationException($"{fieldName}必须为整数。");
if (value <= 0)
throw new InvalidOperationException($"{fieldName}必须大于0。");
return value;
}
private static double ParsePositiveDouble(string text, string fieldName)
{
double value = ParseFiniteDouble(text, fieldName);
if (value <= 0)
throw new InvalidOperationException($"{fieldName}必须大于0。");
return value;
}
private async Task LoadHardnessPressureAsync()
{
ushort registerAddress = ResolveHardnessPressureRegister();
if (registerAddress == 0)
return;
try
{
float value = await App.PlcService.ReadFloatAsync(registerAddress);
if (float.IsFinite(value))
HardnessPressureBox.Text = value.ToString("0.###");
}
catch
{
HardnessPressureBox.Text = "";
}
}
private static async Task WriteHardnessPressureAsync(double value)
{
ushort registerAddress = ResolveHardnessPressureRegister();
if (registerAddress == 0)
throw new InvalidOperationException("加压压力PLC寄存器地址未配置。");
await App.PlcService.WriteFloatAsync(registerAddress, (float)value);
}
private static ushort ResolveHardnessPressureRegister()
{
return App.PlcConfig.HardnessPressure != 0 ? App.PlcConfig.HardnessPressure : (ushort)1480;
}
private async Task LoadHardnessSpeedAsync()
{
ushort registerAddress = ResolveHardnessSpeedRegister();
if (registerAddress == 0)
return;
try
{
float value = await App.PlcService.ReadFloatAsync(registerAddress);
if (float.IsFinite(value) && value > 0)
HardnessSpeedBox.Text = value.ToString("0.###");
}
catch
{
HardnessSpeedBox.Text = "";
}
}
private static async Task WriteHardnessSpeedAsync(double value)
{
ushort registerAddress = ResolveHardnessSpeedRegister();
if (registerAddress == 0)
throw new InvalidOperationException("加压速度PLC寄存器地址未配置。");
await App.PlcService.WriteFloatAsync(registerAddress, (float)value);
}
private static ushort ResolveHardnessSpeedRegister()
{
return App.PlcConfig.HardnessSudu != 0 ? App.PlcConfig.HardnessSudu : (ushort)300;
}
private async Task LoadHardnessDamageThresholdAsync()
{
ushort registerAddress = ResolveHardnessDamageThresholdRegister();
if (registerAddress == 0)
return;
try
{
float value = await App.PlcService.ReadFloatAsync(registerAddress);
if (float.IsFinite(value) && value >= 0)
HardnessDamageThresholdBox.Text = value.ToString("0.###");
}
catch
{
HardnessDamageThresholdBox.Text = "";
}
}
private static async Task WriteHardnessDamageThresholdAsync(double value)
{
ushort registerAddress = ResolveHardnessDamageThresholdRegister();
if (registerAddress == 0)
throw new InvalidOperationException("硬度破损判定PLC寄存器地址未配置。");
await App.PlcService.WriteFloatAsync(registerAddress, (float)value);
}
private static ushort ResolveHardnessDamageThresholdRegister()
{
return App.PlcConfig.HardnessPoSun != 0 ? App.PlcConfig.HardnessPoSun : (ushort)400;
}
private async Task LoadPlcFloatToTextBoxAsync(ushort address, TextBox textBox)
{
if (address == 0) return;
try
{
float value = await App.PlcService.ReadFloatAsync(address);
if (float.IsFinite(value))
textBox.Text = value.ToString("0.###");
}
catch { textBox.Text = ""; }
}
private async Task LoadPlcIntToTextBoxAsync(ushort address, TextBox textBox)
{
if (address == 0) return;
try
{
int value = await App.PlcService.ReadIntAsync(address);
if (value >= 0)
textBox.Text = value.ToString();
}
catch { textBox.Text = ""; }
}
private static async Task WriteFriabilityRpmAsync(double value)
{
ushort registerAddress = ResolveFriabilityRpmRegister();
if (registerAddress == 0)
throw new InvalidOperationException("脆碎度转速PLC寄存器地址未配置。");
await App.PlcService.WriteFloatAsync(registerAddress, (float)value);
}
private static ushort ResolveFriabilityRpmRegister()
{
return App.PlcConfig.FriabilityRpm != 0 ? App.PlcConfig.FriabilityRpm : (ushort)320;
}
private static ushort ResolveFriabilityRoundsRegister()
{
if (App.PlcConfig.FriabilityRounds != 0)
return App.PlcConfig.FriabilityRounds;
if (App.PlcConfig.FriabilityRoundsBox != 0)
return App.PlcConfig.FriabilityRoundsBox;
return App.PlcConfig.FriabilityTestTime != 0 ? App.PlcConfig.FriabilityTestTime : (ushort)410;
}
private static ushort ResolveDisintegrationTimeRegister()
{
return App.PlcConfig.DisintegrationTime != 0 ? App.PlcConfig.DisintegrationTime : (ushort)420;
}
private static ushort ResolveDisintegrationSpeedRegister()
{
return App.PlcConfig.DisintegrationSpeed != 0 ? App.PlcConfig.DisintegrationSpeed : (ushort)330;
}
private static async Task WriteDisintegrationTimeAsync(double value)
{
ushort registerAddress = ResolveDisintegrationTimeRegister();
if (registerAddress == 0)
throw new InvalidOperationException("崩解时间PLC寄存器地址未配置。");
await App.PlcService.WriteRegisterAsync(registerAddress, (ushort)Math.Clamp(
(int)Math.Round(value, MidpointRounding.AwayFromZero),
1,
ushort.MaxValue));
}
private static async Task WriteDisintegrationSpeedAsync(double value)
{
ushort registerAddress = ResolveDisintegrationSpeedRegister();
if (registerAddress == 0)
throw new InvalidOperationException("崩解升降频次PLC寄存器地址未配置。");
await App.PlcService.WriteFloatAsync(registerAddress, (float)value);
}
private static async Task WriteDissolution1SpeedAsync(double value)
{
ushort registerAddress = ResolveDissolution1SpeedRegister();
if (registerAddress == 0)
throw new InvalidOperationException("溶出速度1 PLC寄存器地址未配置。");
await App.PlcService.WriteFloatAsync(registerAddress, (float)value);
}
private static async Task WriteDissolution2SpeedAsync(double value)
{
ushort registerAddress = ResolveDissolution2SpeedRegister();
if (registerAddress == 0)
throw new InvalidOperationException("溶出速度2 PLC寄存器地址未配置。");
await App.PlcService.WriteFloatAsync(registerAddress, (float)value);
}
private static async Task WriteDissolution1TimeAsync(int value)
{
ushort registerAddress = ResolveDissolution1TimeRegister();
if (registerAddress == 0)
throw new InvalidOperationException("溶出1时间PLC寄存器地址未配置。");
await App.PlcService.WriteRegisterAsync(registerAddress, (ushort)Math.Clamp(value, 1, ushort.MaxValue));
}
private static async Task WriteDissolution2TimeAsync(int value)
{
ushort registerAddress = ResolveDissolution2TimeRegister();
if (registerAddress == 0)
throw new InvalidOperationException("溶出2时间PLC寄存器地址未配置。");
await App.PlcService.WriteRegisterAsync(registerAddress, (ushort)Math.Clamp(value, 1, ushort.MaxValue));
}
private static async Task WriteDissolution1IntervalAsync(double value)
{
ushort registerAddress = ResolveDissolution1IntervalRegister();
if (registerAddress == 0)
throw new InvalidOperationException("溶出1取样间隔PLC寄存器地址未配置。");
await App.PlcService.WriteFloatAsync(registerAddress, (float)value);
}
private static async Task WriteDissolution2IntervalAsync(double value)
{
ushort registerAddress = ResolveDissolution2IntervalRegister();
if (registerAddress == 0)
throw new InvalidOperationException("溶出2取样间隔PLC寄存器地址未配置。");
await App.PlcService.WriteFloatAsync(registerAddress, (float)value);
}
private static ushort ResolveDissolution1SpeedRegister()
{
return App.PlcConfig.Dissolution1Speed != 0 ? App.PlcConfig.Dissolution1Speed : (ushort)340;
}
private static ushort ResolveDissolution2SpeedRegister()
{
return App.PlcConfig.Dissolution2Speed != 0 ? App.PlcConfig.Dissolution2Speed : (ushort)350;
}
private static ushort ResolveDissolution1TimeRegister()
{
return App.PlcConfig.Dissolution1Time != 0 ? App.PlcConfig.Dissolution1Time : (ushort)430;
}
private static ushort ResolveDissolution2TimeRegister()
{
return App.PlcConfig.Dissolution2Time != 0 ? App.PlcConfig.Dissolution2Time : (ushort)440;
}
private static ushort ResolveDissolution1IntervalRegister()
{
return App.PlcConfig.Dissolution1SampleInterval != 0 ? App.PlcConfig.Dissolution1SampleInterval : (ushort)432;
}
private static ushort ResolveDissolution2IntervalRegister()
{
return App.PlcConfig.Dissolution2SampleInterval != 0 ? App.PlcConfig.Dissolution2SampleInterval : (ushort)442;
}
private static double ResolveFriabilityTargetTimeMin(PharmaParameters p)
@@ -162,12 +458,24 @@ namespace TabletTester2025
return 4.0;
}
private static int CalculateFriabilityRounds(double timeMin, double rpm)
private static int ResolveFriabilityTargetRounds(PharmaParameters p)
{
if (timeMin <= 0 || rpm <= 0)
return 0;
if (p.FriabilityTargetRounds > 0)
return p.FriabilityTargetRounds;
return Math.Max(1, (int)Math.Round(timeMin * rpm, MidpointRounding.AwayFromZero));
return 100;
}
private static double ResolveDisintegrationTimeMin(PharmaParameters p)
{
return p.DisintegrationMaxSeconds > 0
? p.DisintegrationMaxSeconds / 60.0
: 15.0;
}
private static int ToDisintegrationSeconds(double minutes)
{
return Math.Max(1, (int)Math.Round(minutes * 60, MidpointRounding.AwayFromZero));
}
}
}

View File

@@ -1,171 +0,0 @@
<Window x:Class="片剂四用仪.Views.ShowData"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="药典参数设置" Height="768" Width="1024"
WindowStartupLocation="CenterScreen"
Background="#F0F2F5">
<Window.Resources>
<!-- 卡片样式 -->
<Style x:Key="CardStyle" TargetType="Border">
<Setter Property="Background" Value="White"/>
<Setter Property="CornerRadius" Value="10"/>
<Setter Property="Margin" Value="15,15,80,15"/>
<Setter Property="Padding" Value="15"/>
<Setter Property="MinWidth" Value="390"/>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect Color="#D0D7DE" BlurRadius="8" ShadowDepth="1"/>
</Setter.Value>
</Setter>
</Style>
<!-- 分组标题样式 -->
<Style x:Key="GroupTitle" TargetType="TextBlock">
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Foreground" Value="#1976D2"/>
<Setter Property="Margin" Value="0 0 0 10"/>
</Style>
<!-- 标签样式 -->
<Style x:Key="SettingLabel" TargetType="TextBlock">
<Setter Property="FontSize" Value="13"/>
<Setter Property="Width" Value="190"/>
<Setter Property="Foreground" Value="#333333"/>
<Setter Property="Margin" Value="0 6 0 6"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
<!-- 输入框样式 -->
<Style x:Key="SettingTextBox" TargetType="TextBox">
<Setter Property="Width" Value="150"/>
<Setter Property="Height" Value="40"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="Margin" Value="0 6 0 6"/>
<Setter Property="Padding" Value="8"/>
<Setter Property="Background" Value="#F8F9FA"/>
<Setter Property="BorderBrush" Value="#D1D5DB"/>
<Setter Property="BorderThickness" Value="1"/>
</Style>
</Window.Resources>
<ScrollViewer VerticalScrollBarVisibility="Auto" Margin="10">
<WrapPanel Orientation="Horizontal">
<!-- 模块1硬度测试参数 -->
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock Style="{StaticResource GroupTitle}" Text="⚙ 硬度测试参数"/>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="速度输入(mm/min)"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_HardnessSpeed"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="位移输入(mm)"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_HardnessDisplacement"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="电机极限输入"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_HardnessMotorLimit"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="破损判定输入(N)"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_HardnessDamageThreshold"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="最大力采集"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_MaxForceCollect" IsReadOnly="True"/>
</StackPanel>
</StackPanel>
</Border>
<!-- 模块2脆碎度测试参数 -->
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock Style="{StaticResource GroupTitle}" Text="⚙ 脆碎度测试参数"/>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="试验时间设置"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_BrittlenessTestTime"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="脆碎前质量输入"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_PreBrittlenessMass"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="脆碎后质量输入"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_PostBrittlenessMass"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="失重率(%)"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_WeightLossRate"/>
</StackPanel>
</StackPanel>
</Border>
<!-- 模块3崩解测试参数 -->
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock Style="{StaticResource GroupTitle}" Text="⚙ 崩解测试参数"/>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="崩解速度(r/min)"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_DisintegrationSpeed"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="崩解时间设置(min)"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_DisintegrationTime"/>
</StackPanel>
</StackPanel>
</Border>
<!-- 模块4溶出测试参数 -->
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock Style="{StaticResource GroupTitle}" Text="⚙ 溶出测试参数"/>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="溶出时间设置(min)"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_DissolutionTime"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="溶出1间隔取样时间设置"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_Dissolution1SamplingInterval"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="溶出2间隔取样时间设置"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_Dissolution2SamplingInterval"/>
</StackPanel>
</StackPanel>
</Border>
<!-- 模块5力值与温度校准 -->
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock Style="{StaticResource GroupTitle}" Text="⚙ 力值与温度校准"/>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="力显示"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_ForceDisplay" IsReadOnly="True"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="力系数"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_ForceCoefficient"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="力保护"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_ForceProtection"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="温度系数"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_TemperatureCoefficient" IsReadOnly="True"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SettingLabel}" Text="温度显示"/>
<TextBox Style="{StaticResource SettingTextBox}" Name="txt_TemperatureDisplay"/>
</StackPanel>
</StackPanel>
</Border>
</WrapPanel>
</ScrollViewer>
</Window>

View File

@@ -1,179 +0,0 @@
using Microsoft.Win32;
using Sunny.UI;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using TabletTester2025.Helpers;
using TabletTester2025.Services;
using static OfficeOpenXml.ExcelErrorValue;
namespace .Views
{
public partial class ShowData : Window
{
private readonly IPlcService _plc;
private CancellationTokenSource _cts;
public ShowData(IPlcService plc)
{
InitializeComponent();
_plc = plc;
Loaded += ShowData_Loaded;
Closing += ShowData_Closing;
}
private async void ShowData_Loaded(object sender, RoutedEventArgs e)
{
_cts = new CancellationTokenSource();
BindAllTextBoxWriteEvents();
await StartPlcReadLoop(_cts.Token);
}
private void ShowData_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
_cts?.Cancel();
}
// ====================== 读取 PLC ======================
private async Task StartPlcReadLoop(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
foreach (var m in _paramMappings)
{
var tb = FindName(m.TextBoxName) as TextBox;
if (tb == null) continue;
if (NumericInput.GetIsKeypadOpen(tb) || tb.IsKeyboardFocusWithin)
continue;
if (m.Type == PlcParamType.Int)
{
int value = await _plc.ReadIntAsync((ushort)m.Address);
Dispatcher.Invoke(() => tb.Text = value.ToString());
}
else
{
float value = await _plc.ReadFloatAsync((ushort)m.Address);
Dispatcher.Invoke(() => tb.Text = value.ToString("F2"));
}
}
// 正常循环的Delay同样处理取消异常
try
{
await Task.Delay(1000, token);
}
catch (OperationCanceledException)
{
break;
}
}
catch (OperationCanceledException)
{
// 窗口关闭时的取消异常,正常退出循环
break;
}
//await Task.Delay(1000, token);
}
}
// ====================== 写入 PLC ======================
private void BindAllTextBoxWriteEvents()
{
foreach (var m in _paramMappings)
{
var tb = FindName(m.TextBoxName) as TextBox;
if (tb == null) continue;
if (tb.IsReadOnly)
continue;
NumericInput.SetIsEnabled(tb, true);
NumericInput.SetAllowDecimal(tb, m.Type != PlcParamType.Int);
NumericInput.SetAllowNegative(tb, false);
NumericInput.AddValueCommittedHandler(tb, async (_, _) => await WritePlcValueAsync(m, tb));
}
}
private async Task WritePlcValueAsync(PlcParamMapping mapping, TextBox textBox)
{
try
{
if (mapping.Type == PlcParamType.Int)
{
if (int.TryParse(textBox.Text, out int val))
await _plc.WriteRegisterAsync((ushort)mapping.Address, (ushort)Math.Clamp(val, 0, ushort.MaxValue));
}
else
{
if (double.TryParse(textBox.Text, out double val))
{
textBox.Text = val.ToString("F3");
await _plc.WriteFloatAsync((ushort)mapping.Address, (float)val);
}
}
}
catch { }
}
// ====================== 地址映射表 ======================
private readonly PlcParamMapping[] _paramMappings = new[]
{
new PlcParamMapping("txt_HardnessSpeed", 300, PlcParamType.Float),
new PlcParamMapping("txt_HardnessDisplacement", 310, PlcParamType.Float),
new PlcParamMapping("txt_HardnessMotorLimit", 298, PlcParamType.Float),
new PlcParamMapping("txt_HardnessDamageThreshold", 400, PlcParamType.Float),
new PlcParamMapping("txt_BrittlenessTestTime", 410, PlcParamType.Int),
new PlcParamMapping("txt_PreBrittlenessMass", 412, PlcParamType.Float),
new PlcParamMapping("txt_PostBrittlenessMass", 414, PlcParamType.Float),
new PlcParamMapping("txt_WeightLossRate", 416, PlcParamType.Int),
new PlcParamMapping("txt_DisintegrationSpeed", 330, PlcParamType.Float),
new PlcParamMapping("txt_DisintegrationTime", 420, PlcParamType.Float),
new PlcParamMapping("txt_DissolutionTime", 430, PlcParamType.Int),
new PlcParamMapping("txt_Dissolution1SamplingInterval", 432, PlcParamType.Float),
new PlcParamMapping("txt_Dissolution2SamplingInterval", 442, PlcParamType.Float),
new PlcParamMapping("txt_ForceCoefficient", 1320, PlcParamType.Float),
new PlcParamMapping("txt_ForceProtection", 1322, PlcParamType.Float),
new PlcParamMapping("txt_TemperatureDisplay", 1430, PlcParamType.Float),
new PlcParamMapping("txt_MaxForceCollect", 72, PlcParamType.Float),//读取
new PlcParamMapping("txt_ForceDisplay", 1314, PlcParamType.Float),//读取
new PlcParamMapping("txt_TemperatureCoefficient", 1428, PlcParamType.Float),//读取
};
}
internal class PlcParamMapping
{
public string TextBoxName { get; }
public int Address { get; }
public PlcParamType Type { get; }
public PlcParamMapping(string textBoxName, int address, PlcParamType type)
{
TextBoxName = textBoxName;
Address = address;
Type = type;
}
}
internal enum PlcParamType
{
Int,
Float
}
}

View File

@@ -7,6 +7,7 @@
"IpAddress": "192.168.1.10",
"Port": 502,
"SlaveId": 1,
"FloatWordOrder": "LowWordFirst", // 三菱D寄存器REAL低字在前避免参数写入后PLC显示NaN
//"HardnessValue": 72,
"HardnessStartCoil": 70, //硬度工位1启动测试M70
@@ -21,10 +22,15 @@
"HardnessSudu": 300, // 硬度速度输入mm/min
"HardnessWeiyi": 310, // 硬度位移输入mm/min
"HardnessPoSun": 400, // 硬度破损判定输入N
"HardnessPressure": 1480, // 加压压力
"HardnessMax": 72, //最大力采集
"HardnessShishilizhi": 1314, //力显示
"FriabilityRpm": 320, // 脆碎度转速 r/min
"FriabilityRounds": 410, // 脆碎圈数
"FriabilityRoundsBox": 410, // 兼容旧字段:脆碎圈数
"FriabilityRealtimeRounds": 82, // 脆碎实时圈数32位浮点
"DisintegrationSeconds": 420, //崩解时间
@@ -35,19 +41,23 @@
"FriabilityStartCoilReset": 95, // 脆碎复位启动
"FriabilityTestTime": 410, // 试验次数(次)
"FriabilityTestTime": 0, // 脆碎试验时间由药典参数计算D410用于脆碎圈数
"FriabilityWeightBefore": 412, // 脆碎前质量(g)
"FriabilityWeightAfter": 414, // 脆碎后质量(g)
"FriabilityLossPercent": 416, // 失重率(%)
"WeightBefore": 412,
"WeightAfter": 414,
"DisintegrationTemp": 1430,
"DisintegrationTemp": 1430, // 所有温度显示D1430float类型
"DisintegrationMovingUpCoil": 30,
"DisintegrationStartCoil": 50,
"DisintegrationStopCoil": 53,
"DisintegrationResetCoil": 100, // 崩解复位M100
"DisintegrationSpeed": 330,
"DisintegrationTime": 420,
"DisintegrationCompleteCoils": [ 200, 201, 202, 203, 204, 205 ],
"DissolutionRpm": 400,
"Dissolution1Speed": 340,
"Dissolution2Speed": 350,
"DissolutionPercent": 402,
"Dissolution1Percent": 402,
"Dissolution2Percent": 0,
@@ -59,7 +69,9 @@
"Dissolution2StopCoil": 33,
"Dissolution2SampleAckCoil": 34,
"Dissolution1Time": 430,
"Dissolution2Time": 440
"Dissolution2Time": 440,
"Dissolution1SampleInterval": 432, // 溶出1取样间隔float类型
"Dissolution2SampleInterval": 442 // 溶出2取样间隔float类型
},
"PharmaStandard": {
"StandardVersion": "中国药典2025",