using Microsoft.Win32; using System; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using TabletTester2025.Helpers; using TabletTester2025.Models; using TabletTester2025.Services; namespace 片剂四用仪.Views { public partial class ShowData : Window { private readonly IPlcService _plc; private readonly PlcParamMapping[] _paramMappings; private CancellationTokenSource? _cts; public ShowData(IPlcService plc, PlcConfiguration plcConfig) { InitializeComponent(); _plc = plc; _paramMappings = BuildParamMappings(plcConfig); Loaded += ShowData_Loaded; Closing += ShowData_Closing; } private async void ShowData_Loaded(object sender, RoutedEventArgs e) { var cts = new CancellationTokenSource(); _cts = cts; 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 = float.IsFinite(value) ? value.ToString("0.###") : string.Empty); } } // 正常循环的Delay,同样处理取消异常 try { await Task.Delay(1000, token); } catch (OperationCanceledException) { break; } } catch (OperationCanceledException) { // 窗口关闭时的取消异常,正常退出循环 break; } catch { try { await Task.Delay(1000, token); } 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) && double.IsFinite(val)) { textBox.Text = val.ToString("0.###"); await _plc.WriteFloatAsync((ushort)mapping.Address, (float)val); } } } catch { } } // ====================== 地址映射表 ====================== private static PlcParamMapping[] BuildParamMappings(PlcConfiguration plcConfig) { return new[] { new PlcParamMapping("txt_HardnessSpeed", AddressOrDefault(plcConfig.HardnessSudu, 300), PlcParamType.Float), new PlcParamMapping("txt_HardnessDisplacement", AddressOrDefault(plcConfig.HardnessWeiyi, 310), PlcParamType.Float), new PlcParamMapping("txt_HardnessMotorLimit", AddressOrDefault(plcConfig.HardnessLimit, 298), PlcParamType.Float), new PlcParamMapping("txt_HardnessDamageThreshold", AddressOrDefault(plcConfig.HardnessPoSun, 400), PlcParamType.Float), new PlcParamMapping("txt_FriabilityRounds", ResolveFriabilityRoundsRegister(plcConfig), PlcParamType.Int), new PlcParamMapping("txt_PreBrittlenessMass", ResolveWeightRegister(plcConfig.FriabilityWeightBefore, plcConfig.WeightBefore, 412), PlcParamType.Float), new PlcParamMapping("txt_PostBrittlenessMass", ResolveWeightRegister(plcConfig.FriabilityWeightAfter, plcConfig.WeightAfter, 414), PlcParamType.Float), new PlcParamMapping("txt_WeightLossRate", 416, PlcParamType.Int), new PlcParamMapping("txt_DisintegrationSpeed", AddressOrDefault(plcConfig.DisintegrationSpeed, 330), PlcParamType.Float), new PlcParamMapping("txt_DisintegrationTime", AddressOrDefault(plcConfig.DisintegrationTime, 420), PlcParamType.Int), new PlcParamMapping("txt_Dissolution1Speed", AddressOrDefault(plcConfig.Dissolution1Speed, 340), PlcParamType.Float), new PlcParamMapping("txt_Dissolution2Speed", AddressOrDefault(plcConfig.Dissolution2Speed, 350), PlcParamType.Float), new PlcParamMapping("txt_DissolutionTime", AddressOrDefault(plcConfig.Dissolution1Time, 430), PlcParamType.Int), new PlcParamMapping("txt_Dissolution2Time", AddressOrDefault(plcConfig.Dissolution2Time, 440), PlcParamType.Int), new PlcParamMapping("txt_Dissolution1SamplingInterval", AddressOrDefault(plcConfig.Dissolution1SampleInterval, 432), PlcParamType.Float), new PlcParamMapping("txt_Dissolution2SamplingInterval", AddressOrDefault(plcConfig.Dissolution2SampleInterval, 442), PlcParamType.Float), new PlcParamMapping("txt_ForceCoefficient", 1320, PlcParamType.Float), new PlcParamMapping("txt_ForceProtection", 1322, PlcParamType.Float), new PlcParamMapping("txt_TemperatureDisplay", AddressOrDefault(plcConfig.DisintegrationTemp, 1430), PlcParamType.Float), new PlcParamMapping("txt_MaxForceCollect", AddressOrDefault(plcConfig.HardnessMax, 72), PlcParamType.Float), new PlcParamMapping("txt_ForceDisplay", AddressOrDefault(plcConfig.HardnessShishilizhi, 1314), PlcParamType.Float), new PlcParamMapping("txt_TemperatureCoefficient", 1428, PlcParamType.Float), }; } private static ushort AddressOrDefault(ushort configuredAddress, ushort fallbackAddress) { return configuredAddress != 0 ? configuredAddress : fallbackAddress; } private static ushort ResolveFriabilityRoundsRegister(PlcConfiguration plcConfig) { if (plcConfig.FriabilityRounds != 0) return plcConfig.FriabilityRounds; if (plcConfig.FriabilityRoundsBox != 0) return plcConfig.FriabilityRoundsBox; return AddressOrDefault(plcConfig.FriabilityTestTime, 410); } private static ushort ResolveWeightRegister(ushort primaryAddress, ushort compatibleAddress, ushort fallbackAddress) { if (primaryAddress != 0) return primaryAddress; return AddressOrDefault(compatibleAddress, fallbackAddress); } } 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 } }