更新20260602版本

This commit is contained in:
GukSang.Jin
2026-06-02 19:16:50 +08:00
parent 34f03d0ed0
commit ed0a26e76b
4 changed files with 234 additions and 98 deletions

View File

@@ -7,6 +7,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using A = DocumentFormat.OpenXml.Drawing;
using C = DocumentFormat.OpenXml.Drawing.Charts;
using Xdr = DocumentFormat.OpenXml.Drawing.Spreadsheet;
@@ -18,6 +19,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
private const string InfoSheetName = "试验信息";
private const string ResultSheetName = "结果汇总";
private const string DataSheetName = "实时数据";
private const int StandardTrialCount = 3;
public string Export(SlipReportExport report)
{
@@ -100,12 +102,26 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
{
result.Index,
result.Time,
result.StaticCoefficientValue,
result.DynamicCoefficientValue,
result.StaticCoefficient,
result.DynamicCoefficient,
result.Verdict
});
}
var latest = results.Take(StandardTrialCount).ToList();
if (latest.Count == StandardTrialCount)
{
rows.Add(new object?[] { string.Empty, string.Empty, string.Empty, string.Empty, string.Empty });
rows.Add(new object?[]
{
"近3次平均",
string.Empty,
latest.Average(result => result.StaticCoefficientValue).ToString("F2", CultureInfo.InvariantCulture),
latest.Average(result => result.DynamicCoefficientValue).ToString("F2", CultureInfo.InvariantCulture),
"GB/T 3903.6-2024 8.2"
});
}
AddWorksheet(workbookPart, sheets, sheetId, ResultSheetName, rows, "A", "E");
}
@@ -264,23 +280,30 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
{
const uint xAxisId = 48650112U;
const uint yAxisId = 48672768U;
const uint coefficientAxisId = 48689152U;
var scatterChart = new C.ScatterChart(
var forceChart = new C.ScatterChart(
new C.ScatterStyle { Val = C.ScatterStyleValues.LineMarker },
CreateSeries(0, "正压力(N)", 2, lastRow),
CreateSeries(1, "摩擦力(N)", 3, lastRow),
CreateSeries(2, "位移量(mm)", 4, lastRow),
CreateSeries(3, "摩擦系数", 5, lastRow),
new C.AxisId { Val = xAxisId },
new C.AxisId { Val = yAxisId });
var coefficientChart = new C.ScatterChart(
new C.ScatterStyle { Val = C.ScatterStyleValues.LineMarker },
CreateSeries(3, "摩擦系数", 5, lastRow),
new C.AxisId { Val = xAxisId },
new C.AxisId { Val = coefficientAxisId });
var chart = new C.Chart(
CreateTitle("防滑性能实时曲线"),
new C.PlotArea(
new C.Layout(),
scatterChart,
forceChart,
coefficientChart,
CreateValueAxis(xAxisId, yAxisId, C.AxisPositionValues.Bottom, "时间(s)"),
CreateValueAxis(yAxisId, xAxisId, C.AxisPositionValues.Left, "载荷 / 摩擦力 / 位移 / 系数")),
CreateValueAxis(yAxisId, xAxisId, C.AxisPositionValues.Left, "载荷 / 摩擦力 / 位移"),
CreateValueAxis(coefficientAxisId, xAxisId, C.AxisPositionValues.Right, "摩擦系数")),
new C.Legend(
new C.LegendPosition { Val = C.LegendPositionValues.Bottom },
new C.Layout()),

View File

@@ -27,6 +27,8 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
private const ushort ManualDisplacementRegister = 320;
private readonly object sync = new();
private readonly object plcIoLock = new();
private readonly object adcIoLock = new();
private readonly ModbusFactory modbusFactory = new();
private CancellationTokenSource? cancellationTokenSource;
@@ -50,8 +52,10 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
private bool hasAdcRawValues;
private bool isTestRunning;
private bool isResetting;
private bool isConnected;
private string lastError = string.Empty;
private bool isAdcConnected;
private bool isPlcConnected;
private string adcLastError = string.Empty;
private string plcLastError = string.Empty;
public SlipDeviceSnapshot CurrentSnapshot
{
@@ -90,7 +94,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
SetConnected(true, string.Empty);
SetAllDisconnected(string.Empty);
adcTask = Task.Run(() => PollAdc(token), token);
plcTask = Task.Run(() => PollPlc(token), token);
Log.Information("防滑测试设备连接成功ADC/PLC 轮询已启动");
@@ -103,7 +107,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
settings.PlcPortName,
settings.AdcPortName,
settings.BaudRate);
SetConnected(false, ex.Message);
SetAllDisconnected(ex.Message);
Stop();
}
}
@@ -197,10 +201,17 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
public Task<PlcDeviceParameters> ReadDeviceParametersAsync() =>
Task.Run(() =>
{
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
var manualSpeedWords = master.ReadHoldingRegisters(SlaveId, ManualSpeedRegister, 2);
var manualDisplacementWords = master.ReadHoldingRegisters(SlaveId, ManualDisplacementRegister, 2);
var testSpeedWords = master.ReadHoldingRegisters(SlaveId, TestSpeedRegister, 2);
ushort[] manualSpeedWords;
ushort[] manualDisplacementWords;
ushort[] testSpeedWords;
lock (plcIoLock)
{
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
manualSpeedWords = master.ReadHoldingRegisters(SlaveId, ManualSpeedRegister, 2);
manualDisplacementWords = master.ReadHoldingRegisters(SlaveId, ManualDisplacementRegister, 2);
testSpeedWords = master.ReadHoldingRegisters(SlaveId, TestSpeedRegister, 2);
}
var parameters = new PlcDeviceParameters(
UshortToFloat(manualSpeedWords[1], manualSpeedWords[0]),
UshortToFloat(manualDisplacementWords[1], manualDisplacementWords[0]),
@@ -263,6 +274,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
ClosePort(adcPort);
plcPort = null;
adcPort = null;
SetAllDisconnected(string.Empty);
}
public void Dispose()
@@ -304,13 +316,18 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
{
try
{
var master = adcMaster;
if (master is null)
ushort[] data;
lock (adcIoLock)
{
return;
var master = adcMaster;
if (master is null)
{
return;
}
data = master.ReadHoldingRegisters(SlaveId, 0, 8);
}
var data = master.ReadHoldingRegisters(SlaveId, 0, 8);
var pressureRaw = UshortToInt(data[0], data[1]);
var friction1Raw = UshortToInt(data[6], data[7]);
var friction2Raw = UshortToInt(data[2], data[3]);
@@ -331,8 +348,8 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
hasAdcRawValues = true;
verticalLoadN = pressure;
horizontalFrictionN = friction;
lastError = string.Empty;
isConnected = true;
adcLastError = string.Empty;
isAdcConnected = true;
RefreshSnapshotLocked();
}
@@ -341,7 +358,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
catch (Exception ex)
{
LogPollingError("ADC", ex, ref lastAdcErrorLoggedAt);
SetConnected(false, ex.Message);
SetAdcConnected(false, ex.Message);
Thread.Sleep(250);
}
}
@@ -353,22 +370,27 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
{
try
{
var master = plcMaster;
if (master is null)
ushort[] displacementWords;
bool[] coils;
lock (plcIoLock)
{
return;
}
var master = plcMaster;
if (master is null)
{
return;
}
var displacementWords = master.ReadHoldingRegisters(SlaveId, PlcDisplacementRegister, 2);
var coils = master.ReadCoils(SlaveId, PlcStateCoilStart, 10);
displacementWords = master.ReadHoldingRegisters(SlaveId, PlcDisplacementRegister, 2);
coils = master.ReadCoils(SlaveId, PlcStateCoilStart, 10);
}
lock (sync)
{
displacementMm = UshortToFloat(displacementWords[1], displacementWords[0]);
isTestRunning = coils.Length > 0 && coils[0];
isResetting = coils.Length > 9 && coils[9];
lastError = string.Empty;
isConnected = true;
plcLastError = string.Empty;
isPlcConnected = true;
RefreshSnapshotLocked();
}
@@ -377,7 +399,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
catch (Exception ex)
{
LogPollingError("PLC", ex, ref lastPlcErrorLoggedAt);
SetConnected(false, ex.Message);
SetPlcConnected(false, ex.Message);
Thread.Sleep(250);
}
}
@@ -394,26 +416,35 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
private Task ToggleCoilAsync(ushort coil) =>
Task.Run(() =>
{
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
var current = master.ReadCoils(SlaveId, coil, 1)[0];
master.WriteSingleCoil(SlaveId, coil, !current);
Log.Information("切换 PLC 线圈M{Coil}, Before={Before}, After={After}", coil, current, !current);
lock (plcIoLock)
{
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
var current = master.ReadCoils(SlaveId, coil, 1)[0];
master.WriteSingleCoil(SlaveId, coil, !current);
Log.Information("切换 PLC 线圈M{Coil}, Before={Before}, After={After}", coil, current, !current);
}
});
private Task WriteCoilAsync(ushort coil, bool value) =>
Task.Run(() =>
{
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
master.WriteSingleCoil(SlaveId, coil, value);
Log.Debug("写入 PLC 线圈M{Coil}={Value}", coil, value);
lock (plcIoLock)
{
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
master.WriteSingleCoil(SlaveId, coil, value);
Log.Debug("写入 PLC 线圈M{Coil}={Value}", coil, value);
}
});
private Task WriteFloatRegisterAsync(ushort register, double value) =>
Task.Run(() =>
{
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
master.WriteMultipleRegisters(SlaveId, register, SplitFloatToUShortArray((float)value));
Log.Information("写入 PLC 浮点寄存器D{Register}={Value:F3}", register, value);
lock (plcIoLock)
{
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
master.WriteMultipleRegisters(SlaveId, register, SplitFloatToUShortArray((float)value));
Log.Information("写入 PLC 浮点寄存器D{Register}={Value:F3}", register, value);
}
});
private void LogPollingError(string source, Exception exception, ref DateTime lastLoggedAt)
@@ -434,18 +465,62 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
settings.BaudRate);
}
private void SetConnected(bool connected, string error)
private void SetAdcConnected(bool connected, string error)
{
lock (sync)
{
isConnected = connected;
lastError = error;
isAdcConnected = connected;
adcLastError = error;
RefreshSnapshotLocked();
}
}
private void SetPlcConnected(bool connected, string error)
{
lock (sync)
{
isPlcConnected = connected;
plcLastError = error;
if (!connected)
{
isTestRunning = false;
isResetting = false;
}
RefreshSnapshotLocked();
}
}
private void SetAllDisconnected(string error)
{
lock (sync)
{
isAdcConnected = false;
isPlcConnected = false;
adcLastError = error;
plcLastError = error;
isTestRunning = false;
isResetting = false;
RefreshSnapshotLocked();
}
}
private void RefreshSnapshotLocked()
{
var connected = isAdcConnected && isPlcConnected;
var error = string.Empty;
if (!isAdcConnected && !string.IsNullOrWhiteSpace(adcLastError))
{
error = "ADC: " + adcLastError;
}
if (!isPlcConnected && !string.IsNullOrWhiteSpace(plcLastError))
{
error = string.IsNullOrWhiteSpace(error)
? "PLC: " + plcLastError
: error + "; PLC: " + plcLastError;
}
snapshot = new SlipDeviceSnapshot(
DateTime.Now,
verticalLoadN,
@@ -453,8 +528,8 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
displacementMm,
isTestRunning,
isResetting,
isConnected,
lastError);
connected,
error);
}
private static double ConvertAdc(int rawValue, string zeroText, string coefficientText)

View File

@@ -23,6 +23,12 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
{
public partial class MainWindowViewModel : ViewModelBase, IDisposable
{
private const double SampleIntervalSeconds = 1.0 / 50.0;
private const double DynamicWindowStartSeconds = 0.3;
private const double DynamicWindowEndSeconds = 0.6;
private const int MinimumDynamicWindowPointCount = 10;
private const int StandardTrialCount = 3;
private readonly SlipResistanceDeviceService deviceService = new();
private readonly SlipExcelExportService excelExportService = new();
private readonly DispatcherTimer refreshTimer;
@@ -131,10 +137,10 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
private string resultSummary = "等待 3 次有效试验";
[ObservableProperty]
private string staticCoefficient = "0.000";
private string staticCoefficient = "0.00";
[ObservableProperty]
private string dynamicCoefficient = "0.000";
private string dynamicCoefficient = "0.00";
[ObservableProperty]
private string verticalPressure = "0.0";
@@ -149,7 +155,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
private string distance = "0.0";
public string TestSpeedText => $"{TestSpeed} m/s";
public string SampleRateText { get; } = "30 Hz";
public string SampleRateText { get; } = "50 Hz";
public string BatchNumber { get; } = DateTime.Now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
public string UploadProgressText => $"{UploadProgress}%";
public string StandardReference { get; } = "GB/T 3903.6-2024 5.1.3、7.3.1.4、8.1-8.3:实时采集、静/动摩擦系数、三次平均与重测判定";
@@ -219,10 +225,10 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
Log.Information("初始化主页面 ViewModel");
Series =
[
CreateLineSeries("垂直压力(N)", verticalLoadPoints, "#DC2626", 0, 0.65),
CreateLineSeries("水平摩擦力(N)", horizontalFrictionPoints, "#16A34A", 0, 0.65),
CreateLineSeries("摩擦系数", frictionCoefficientPoints, "#C026D3", 1, 0.65),
CreateLineSeries("位移(mm)", displacementPoints, "#2563EB", 0, 0.35)
CreateLineSeries("垂直压力(N)", verticalLoadPoints, "#DC2626", 0, 0),
CreateLineSeries("水平摩擦力(N)", horizontalFrictionPoints, "#16A34A", 0, 0),
CreateLineSeries("摩擦系数", frictionCoefficientPoints, "#C026D3", 1, 0),
CreateLineSeries("位移(mm)", displacementPoints, "#2563EB", 0, 0)
];
LoadDeviceSettings();
@@ -230,7 +236,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
deviceService.Start(CurrentSettings());
_ = LoadPlcParametersAsync();
refreshTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(33) };
refreshTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(20) };
refreshTimer.Tick += (_, _) => RefreshFromDevice();
refreshTimer.Start();
}
@@ -461,22 +467,30 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
CurrentStatus = $"设备离线:{device.LastError}";
}
if (!wasRunning && device.IsTestRunning)
var isRecording = device.IsConnected && device.IsTestRunning;
if (!wasRunning && isRecording)
{
BeginRun();
}
if (device.IsTestRunning)
if (isRecording)
{
RecordPoint(device);
}
if (wasRunning && !device.IsTestRunning)
if (wasRunning && !isRecording)
{
CompleteRun();
if (device.IsConnected)
{
CompleteRun();
}
else
{
AbortRun("设备离线,本次采集已停止,未生成试验结果");
}
}
wasRunning = device.IsTestRunning;
wasRunning = isRecording;
}
private void BeginRun()
@@ -495,7 +509,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
private void RecordPoint(SlipDeviceSnapshot device)
{
var time = runStopwatch.Elapsed.TotalSeconds;
if (currentRun.Count > 0 && time - currentRun[^1].TimeSeconds < 1.0 / 30.0)
if (currentRun.Count > 0 && time - currentRun[^1].TimeSeconds < SampleIntervalSeconds)
{
return;
}
@@ -530,11 +544,17 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
lastCompletedRun = currentRun.ToList();
var peak = FindStaticPeak(currentRun);
var dynamicWindow = currentRun
.Where(point => point.TimeSeconds >= 0.3 && point.TimeSeconds <= 0.6)
.Where(point => point.TimeSeconds >= DynamicWindowStartSeconds && point.TimeSeconds <= DynamicWindowEndSeconds)
.ToList();
if (dynamicWindow.Count == 0)
if (dynamicWindow.Count < MinimumDynamicWindowPointCount)
{
dynamicWindow = currentRun.ToList();
Log.Warning(
"测试停止但动摩擦窗口采样点不足TestNumber={TestNumber}, PointCount={PointCount}, DynamicWindowPointCount={DynamicWindowPointCount}",
TestNumber,
currentRun.Count,
dynamicWindow.Count);
CurrentStatus = "测试已停止0.3 s~0.6 s 有效采样点不足,未生成结果";
return;
}
var staticCoefficientValue = CalculateCoefficient(peak.HorizontalFrictionN, peak.VerticalLoadN);
@@ -547,14 +567,14 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
Samples.Insert(0, new TestSample(
nextIndex,
DateTime.Now.ToString("HH:mm:ss", CultureInfo.InvariantCulture),
staticCoefficientValue.ToString("F3", CultureInfo.InvariantCulture),
dynamicCoefficientValue.ToString("F3", CultureInfo.InvariantCulture),
staticCoefficientValue.ToString("F2", CultureInfo.InvariantCulture),
dynamicCoefficientValue.ToString("F2", CultureInfo.InvariantCulture),
verdict,
staticCoefficientValue,
dynamicCoefficientValue));
StaticCoefficient = staticCoefficientValue.ToString("F3", CultureInfo.InvariantCulture);
DynamicCoefficient = dynamicCoefficientValue.ToString("F3", CultureInfo.InvariantCulture);
StaticCoefficient = staticCoefficientValue.ToString("F2", CultureInfo.InvariantCulture);
DynamicCoefficient = dynamicCoefficientValue.ToString("F2", CultureInfo.InvariantCulture);
UpdateResultSummary();
UploadProgress = 100;
CurrentStatus = verdict == "有效"
@@ -569,21 +589,39 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
verdict);
}
private void AbortRun(string message)
{
runStopwatch.Stop();
Log.Warning(
"测试采集中止TestNumber={TestNumber}, PointCount={PointCount}, Message={Message}",
TestNumber,
currentRun.Count,
message);
CurrentStatus = message;
}
private bool NeedsRetest(double staticCoefficientValue, double dynamicCoefficientValue)
{
var latest = Samples.Take(2).ToList();
if (latest.Count < 2)
var previous = Samples.Take(StandardTrialCount - 1).Reverse().ToList();
if (previous.Count < StandardTrialCount - 1)
{
return false;
}
var staticValues = latest.Select(sample => sample.StaticCoefficientValue).Append(staticCoefficientValue).ToArray();
var dynamicValues = latest.Select(sample => sample.DynamicCoefficientValue).Append(dynamicCoefficientValue).ToArray();
return ExceedsTenPercent(staticValues) || ExceedsTenPercent(dynamicValues);
var staticValues = previous.Select(sample => sample.StaticCoefficientValue).Append(staticCoefficientValue).ToArray();
var dynamicValues = previous.Select(sample => sample.DynamicCoefficientValue).Append(dynamicCoefficientValue).ToArray();
return HasSystematicTrendExceedingTenPercent(staticValues) || HasSystematicTrendExceedingTenPercent(dynamicValues);
}
private static bool ExceedsTenPercent(double[] values)
private static bool HasSystematicTrendExceedingTenPercent(double[] values)
{
var isIncreasing = values.Zip(values.Skip(1), (left, right) => right > left).All(result => result);
var isDecreasing = values.Zip(values.Skip(1), (left, right) => right < left).All(result => result);
if (!isIncreasing && !isDecreasing)
{
return false;
}
var average = values.Average();
if (Math.Abs(average) < 0.0001)
{
@@ -595,32 +633,32 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
private void UpdateResultSummary()
{
var latest = Samples.Take(3).ToList();
var latest = Samples.Take(StandardTrialCount).ToList();
if (latest.Count == 0)
{
ResultSummary = "等待 3 次有效试验";
return;
}
if (latest.Count < StandardTrialCount)
{
ResultSummary = $"已完成 {latest.Count}/3 次有效试验";
return;
}
var staticAverage = latest.Average(sample => sample.StaticCoefficientValue);
var dynamicAverage = latest.Average(sample => sample.DynamicCoefficientValue);
ResultSummary = $"近 {latest.Count} 次平均 静 {staticAverage:F3} / 动 {dynamicAverage:F3}";
ResultSummary = $"近 3 次平均 静 {staticAverage:F2} / 动 {dynamicAverage:F2}";
}
private static SlipDataPoint FindStaticPeak(IReadOnlyList<SlipDataPoint> points)
{
for (var index = 1; index < points.Count - 1; index++)
{
var previous = points[index - 1].HorizontalFrictionN;
var current = points[index].HorizontalFrictionN;
var next = points[index + 1].HorizontalFrictionN;
if (current >= previous && current >= next && current > 0)
{
return points[index];
}
}
return points.MaxBy(point => point.HorizontalFrictionN) ?? points[0];
var preDynamicWindow = points
.Where(point => point.TimeSeconds <= DynamicWindowStartSeconds)
.ToList();
return (preDynamicWindow.Count > 0 ? preDynamicWindow : points)
.MaxBy(point => point.HorizontalFrictionN)
?? points[0];
}
private static double CalculateCoefficient(double frictionForce, double verticalLoad) =>

View File

@@ -430,47 +430,47 @@
RowSpacing="10"
ColumnSpacing="12">
<Label Content="手动速度" Classes="setting-label" VerticalAlignment="Center"/>
<TextBox Grid.Column="1" Classes="setting-value" Text="{Binding ManualSpeed, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Column="1" Classes="setting-value" Text="{Binding ManualSpeed, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
<TextBlock Grid.Column="2" Text="m/s" Classes="setting-unit" VerticalAlignment="Center"/>
<Label Grid.Row="1" Content="手动位移" Classes="setting-label" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1" Classes="setting-value" Text="{Binding ManualDisplacement, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="1" Grid.Column="1" Classes="setting-value" Text="{Binding ManualDisplacement, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="m" Classes="setting-unit" VerticalAlignment="Center"/>
<Label Grid.Row="2" Content="测试速度" Classes="setting-label" VerticalAlignment="Center"/>
<TextBox Grid.Row="2" Grid.Column="1" Classes="setting-value" Text="{Binding TestSpeed, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="2" Grid.Column="1" Classes="setting-value" Text="{Binding TestSpeed, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
<TextBlock Grid.Row="2" Grid.Column="2" Text="m/s" Classes="setting-unit" VerticalAlignment="Center"/>
<Label Grid.Row="3" Content="PLC串口" Classes="setting-label" VerticalAlignment="Center"/>
<TextBox Grid.Row="3" Grid.Column="1" Classes="setting-value" Text="{Binding PlcPortName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="3" Grid.Column="1" Classes="setting-value" Text="{Binding PlcPortName, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
<Label Grid.Row="3" Grid.Column="4" Content="ADC串口" Classes="setting-label" VerticalAlignment="Center"/>
<TextBox Grid.Row="3" Grid.Column="5" Classes="setting-value" Text="{Binding AdcPortName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="3" Grid.Column="5" Classes="setting-value" Text="{Binding AdcPortName, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
<Label Grid.Row="4" Content="波特率" Classes="setting-label" VerticalAlignment="Center"/>
<TextBox Grid.Row="4" Grid.Column="1" Classes="setting-value" Text="{Binding BaudRate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="4" Grid.Column="1" Classes="setting-value" Text="{Binding BaudRate, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
<Border Grid.Row="5" Grid.ColumnSpan="6" Height="1" Background="{StaticResource LineBrush}" Margin="0,4"/>
<Label Grid.Row="6" Content="正压力零点" Classes="setting-label" VerticalAlignment="Center"/>
<TextBox Grid.Row="6" Grid.Column="1" Classes="setting-value" Text="{Binding NormalPressureZero, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="6" Grid.Column="1" Classes="setting-value" Text="{Binding NormalPressureZero, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
<Button Grid.Row="6" Grid.Column="2" Content="采零" Classes="compact" Command="{Binding CalibrateNormalPressureZeroCommand}"/>
<Label Grid.Row="6" Grid.Column="4" Content="正压力系数" Classes="setting-label" VerticalAlignment="Center"/>
<TextBox Grid.Row="6" Grid.Column="5" Classes="setting-value" Text="{Binding NormalPressureCoefficient, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="6" Grid.Column="5" Classes="setting-value" Text="{Binding NormalPressureCoefficient, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
<Label Grid.Row="7" Content="摩擦1零点" Classes="setting-label" VerticalAlignment="Center"/>
<TextBox Grid.Row="7" Grid.Column="1" Classes="setting-value" Text="{Binding FrictionZero1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="7" Grid.Column="1" Classes="setting-value" Text="{Binding FrictionZero1, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
<Button Grid.Row="7" Grid.Column="2" Content="摩擦采零" Classes="compact" Command="{Binding CalibrateFrictionZeroCommand}"/>
<Label Grid.Row="7" Grid.Column="4" Content="摩擦1系数" Classes="setting-label" VerticalAlignment="Center"/>
<TextBox Grid.Row="7" Grid.Column="5" Classes="setting-value" Text="{Binding FrictionCoefficient1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="7" Grid.Column="5" Classes="setting-value" Text="{Binding FrictionCoefficient1, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
<Label Grid.Row="8" Content="摩擦2零点" Classes="setting-label" VerticalAlignment="Center"/>
<TextBox Grid.Row="8" Grid.Column="1" Classes="setting-value" Text="{Binding FrictionZero2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="8" Grid.Column="1" Classes="setting-value" Text="{Binding FrictionZero2, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
<Label Grid.Row="8" Grid.Column="4" Content="摩擦2系数" Classes="setting-label" VerticalAlignment="Center"/>
<TextBox Grid.Row="8" Grid.Column="5" Classes="setting-value" Text="{Binding FrictionCoefficient2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="8" Grid.Column="5" Classes="setting-value" Text="{Binding FrictionCoefficient2, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
</Grid>
<Grid Grid.Row="2"