更新UI布局

This commit is contained in:
GukSang.Jin
2026-01-19 11:06:05 +08:00
parent e121308ea3
commit e6c5f01ee3
16 changed files with 753 additions and 494 deletions

View File

@@ -90,7 +90,7 @@ namespace COFTester.Models
{
try
{
string directory = Path.GetDirectoryName(filePath);
string? directory = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(directory))
{
Directory.CreateDirectory(directory);

View File

@@ -234,6 +234,6 @@ namespace COFTester.Models
{
void SaveTestResult(TestResult result);
List<TestResult> LoadAllTestResults();
TestResult LoadTestResult(Guid testId);
TestResult? LoadTestResult(Guid testId);
}
}

View File

@@ -22,6 +22,12 @@ namespace COFTester.Resources
["AppTitle"] = "CSI-H238M 高精度摩擦系数测试仪",
["LanguageButton"] = "中/EN",
// 导航栏
["NavTest"] = "测试",
["NavHistory"] = "历史",
["NavSettings"] = "设置",
["NavCalibration"] = "标定",
// 左侧面板 - 实时监控
["RealTimeMonitor"] = "实时监控",
["Force"] = "力值 (N)",
@@ -120,6 +126,12 @@ namespace COFTester.Resources
["AppTitle"] = "CSI-H238M High-Precision Friction Tester",
["LanguageButton"] = "EN/中",
// Navigation
["NavTest"] = "Test",
["NavHistory"] = "History",
["NavSettings"] = "Settings",
["NavCalibration"] = "Calibration",
// Left Panel - Real-time Monitor
["RealTimeMonitor"] = "Real-time Monitor",
["Force"] = "Force (N)",
@@ -230,6 +242,13 @@ namespace COFTester.Resources
// 所有文本属性
public string AppTitle => GetString("AppTitle");
public string LanguageButton => GetString("LanguageButton");
// 导航栏
public string NavTest => GetString("NavTest");
public string NavHistory => GetString("NavHistory");
public string NavSettings => GetString("NavSettings");
public string NavCalibration => GetString("NavCalibration");
public string RealTimeMonitor => GetString("RealTimeMonitor");
public string Force => GetString("Force");
public string Displacement => GetString("Displacement");
@@ -326,6 +345,13 @@ namespace COFTester.Resources
// 通知所有属性已更改
OnPropertyChanged(nameof(AppTitle));
OnPropertyChanged(nameof(LanguageButton));
// 导航栏
OnPropertyChanged(nameof(NavTest));
OnPropertyChanged(nameof(NavHistory));
OnPropertyChanged(nameof(NavSettings));
OnPropertyChanged(nameof(NavCalibration));
OnPropertyChanged(nameof(RealTimeMonitor));
OnPropertyChanged(nameof(Force));
OnPropertyChanged(nameof(Displacement));

View File

@@ -120,12 +120,12 @@ namespace COFTester.Services
/// <summary>
/// 根據 TestId 加載特定測試結果
/// </summary>
public TestResult LoadTestResult(Guid testId)
public TestResult? LoadTestResult(Guid testId)
{
try
{
var index = LoadIndex();
if (index.TryGetValue(testId, out string fileName))
if (index.TryGetValue(testId, out string? fileName) && !string.IsNullOrEmpty(fileName))
{
string filePath = Path.Combine(_dataDirectory, fileName);
if (File.Exists(filePath))
@@ -225,7 +225,7 @@ namespace COFTester.Services
try
{
var index = LoadIndex();
if (index.TryGetValue(testId, out string fileName))
if (index.TryGetValue(testId, out string? fileName) && !string.IsNullOrEmpty(fileName))
{
string filePath = Path.Combine(_dataDirectory, fileName);
if (File.Exists(filePath))

View File

@@ -35,9 +35,9 @@ namespace COFTester.Services
/// <param name="data">原始數據點列表</param>
/// <param name="windowSize">濾波窗口大小(建議 3-10</param>
/// <returns>濾波後的數據點列表</returns>
public List<TestDataPoint> ApplyMovingAverageFilter(List<TestDataPoint> data, int windowSize)
public List<TestDataPoint> ApplyMovingAverageFilter(List<TestDataPoint>? data, int windowSize)
{
if (data == null || data.Count < windowSize) return data;
if (data == null || data.Count < windowSize) return data ?? new List<TestDataPoint>();
var filteredData = new List<TestDataPoint>();
@@ -60,9 +60,9 @@ namespace COFTester.Services
/// <summary>
/// 應用中值濾波器,對於脈衝噪聲有更好的抑制效果
/// </summary>
public List<TestDataPoint> ApplyMedianFilter(List<TestDataPoint> data, int windowSize)
public List<TestDataPoint> ApplyMedianFilter(List<TestDataPoint>? data, int windowSize)
{
if (data == null || data.Count < windowSize) return data;
if (data == null || data.Count < windowSize) return data ?? new List<TestDataPoint>();
var filteredData = new List<TestDataPoint>();
@@ -109,7 +109,7 @@ namespace COFTester.Services
/// <param name="rawData">原始或濾波後的數據</param>
/// <param name="parameters">測試參數(包含標準名稱和計算區間)</param>
/// <returns>計算結果</returns>
public TestResult CalculateResults(List<TestDataPoint> rawData, TestParameters parameters)
public TestResult? CalculateResults(List<TestDataPoint>? rawData, TestParameters parameters)
{
if (rawData == null || rawData.Count == 0) return null;
@@ -356,9 +356,9 @@ namespace COFTester.Services
/// <summary>
/// 檢測數據異常點(基於 3-sigma 原則)
/// </summary>
public List<TestDataPoint> RemoveOutliers(List<TestDataPoint> data)
public List<TestDataPoint> RemoveOutliers(List<TestDataPoint>? data)
{
if (data == null || data.Count < 10) return data;
if (data == null || data.Count < 10) return data ?? new List<TestDataPoint>();
var forces = data.Select(p => p.Force).ToList();
double mean = forces.Average();
@@ -383,14 +383,14 @@ namespace COFTester.Services
/// </summary>
public class SimulatedDataAcquisitionService : IDataAcquisitionService
{
private CancellationTokenSource _cts;
private CancellationTokenSource _cts = new CancellationTokenSource();
private bool _isAcquiring;
private bool _isConnected;
private readonly Random _random = new Random();
public event EventHandler<TestDataPoint> DataReceived;
public event EventHandler TestFinished;
public event EventHandler<string> ErrorOccurred;
public event EventHandler<TestDataPoint>? DataReceived;
public event EventHandler? TestFinished;
public event EventHandler<string>? ErrorOccurred;
public bool IsConnected => _isConnected;

View File

@@ -21,9 +21,9 @@ namespace COFTester.ViewModels
private readonly IDataAcquisitionService _daqService;
private readonly DataProcessingService _processingService;
private readonly DispatcherTimer _clockTimer;
private readonly WpfPlot _wpfPlot;
private WpfPlot? _wpfPlot;
private readonly AppConfig _config;
private ScottPlot.Plottables.Scatter _scatterPlot = null!;
private ScottPlot.Plottables.Scatter? _scatterPlot;
private readonly Dictionary<Guid, ScottPlot.Plottables.Scatter> _testCurves = new();
private double _currentForce;
@@ -38,11 +38,10 @@ namespace COFTester.ViewModels
private int _testCounter = 0;
private bool _disposed = false;
public MainViewModel(IDataAcquisitionService daqService, DataProcessingService processingService, WpfPlot wpfPlot, AppConfig config)
public MainViewModel(IDataAcquisitionService daqService, DataProcessingService processingService, AppConfig config)
{
_daqService = daqService ?? throw new ArgumentNullException(nameof(daqService));
_processingService = processingService ?? throw new ArgumentNullException(nameof(processingService));
_wpfPlot = wpfPlot ?? throw new ArgumentNullException(nameof(wpfPlot));
_config = config ?? throw new ArgumentNullException(nameof(config));
_realTimePoints = new List<TestDataPoint>();
@@ -63,8 +62,8 @@ namespace COFTester.ViewModels
ResetCommand = new RelayCommand(Reset, () => !_isTesting && IsConnected);
SwitchLanguageCommand = new RelayCommand(SwitchLanguage);
OpenConfigCommand = new RelayCommand(OpenConfig);
ClearAllTestsCommand = new RelayCommand(ClearAllTests, () => !_isTesting && TestRecords.Count > 0);
GenerateReportCommand = new RelayCommand(GenerateReport, () => TestRecords.Count > 0);
ClearAllTestsCommand = new RelayCommand(ClearAllTests, () => !_isTesting && TestRecords?.Count > 0);
GenerateReportCommand = new RelayCommand(GenerateReport, () => TestRecords?.Count > 0);
Parameters = _config.DefaultTestParameters ?? new TestParameters();
TestRecords = new ObservableCollection<TestResult>();
@@ -74,9 +73,6 @@ namespace COFTester.ViewModels
CommandManager.InvalidateRequerySuggested();
};
// 初始化 ScottPlot
InitializeScottPlot();
// 启动时钟更新
_clockTimer = new DispatcherTimer
{
@@ -89,6 +85,15 @@ namespace COFTester.ViewModels
AutoConnectOnStartup();
}
/// <summary>
/// 设置图表控件由TestPage调用
/// </summary>
public void SetPlot(WpfPlot wpfPlot)
{
_wpfPlot = wpfPlot ?? throw new ArgumentNullException(nameof(wpfPlot));
InitializeScottPlot();
}
/// <summary>
/// 啟動時根據配置自動連接
/// </summary>
@@ -124,6 +129,8 @@ namespace COFTester.ViewModels
/// </summary>
private void InitializeScottPlot()
{
if (_wpfPlot == null) return;
_wpfPlot.Plot.Clear();
// 设置支持中文的字体(必须在添加任何元素之前)
@@ -203,6 +210,8 @@ namespace COFTester.ViewModels
/// </summary>
private void UpdateScottPlot()
{
if (_wpfPlot == null || _scatterPlot == null) return;
// 移除旧的散点图
_wpfPlot.Plot.Remove(_scatterPlot);
@@ -424,6 +433,8 @@ namespace COFTester.ViewModels
var filteredData = _processingService.ApplyMovingAverageFilter(rawDataList, 5);
var result = _processingService.CalculateResults(filteredData, Parameters);
if (result == null) return;
// 保存原始数据用于再分析
result.RawData = rawDataList;
result.SampleNumber = $"#{++_testCounter}";
@@ -431,7 +442,7 @@ namespace COFTester.ViewModels
// 监听IsVisible属性变化
result.PropertyChanged += (s, args) =>
{
if (args.PropertyName == nameof(TestResult.IsVisible))
if (args?.PropertyName == nameof(TestResult.IsVisible))
{
UpdateAllCurvesVisibility();
}
@@ -596,7 +607,7 @@ namespace COFTester.ViewModels
/// </summary>
private void AddCurveToPlot(TestResult result)
{
if (result.RawData == null || result.RawData.Count == 0)
if (_wpfPlot == null || result.RawData == null || result.RawData.Count == 0)
return;
var xData = result.RawData.Select(p => p.Displacement).ToArray();
@@ -627,6 +638,8 @@ namespace COFTester.ViewModels
/// </summary>
private void UpdateAllCurvesVisibility()
{
if (_wpfPlot == null) return;
foreach (var record in TestRecords)
{
if (_testCurves.TryGetValue(record.TestId, out var curve))
@@ -644,8 +657,11 @@ namespace COFTester.ViewModels
{
TestRecords.Clear();
_testCurves.Clear();
_wpfPlot.Plot.Clear();
InitializeScottPlot();
if (_wpfPlot != null)
{
_wpfPlot.Plot.Clear();
InitializeScottPlot();
}
_testCounter = 0;
StatusMessage = "已清除所有测试记录";
}
@@ -732,9 +748,12 @@ namespace COFTester.ViewModels
SetChineseFont();
// 更新图表坐标轴标签
_wpfPlot.Plot.XLabel(Lang.DisplacementAxis);
_wpfPlot.Plot.YLabel(Lang.ForceAxis);
_wpfPlot.Refresh();
if (_wpfPlot != null)
{
_wpfPlot.Plot.XLabel(Lang.DisplacementAxis);
_wpfPlot.Plot.YLabel(Lang.ForceAxis);
_wpfPlot.Refresh();
}
// 更新当前状态消息
if (_statusMessage == "系统就绪" || _statusMessage == "System Ready")

View File

@@ -0,0 +1,76 @@
<UserControl x:Class="COFTester.Views.CalibrationPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="Transparent">
<UserControl.Resources>
<Style x:Key="CardStyle" TargetType="Border">
<Setter Property="Background" Value="White"/>
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Padding" Value="15"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="10" ShadowDepth="2" Opacity="0.1"/>
</Setter.Value>
</Setter>
</Style>
<SolidColorBrush x:Key="GrayBrush" Color="#7F8C8D"/>
<SolidColorBrush x:Key="AccentBrush" Color="#3498DB"/>
<SolidColorBrush x:Key="SuccessBrush" Color="#27AE60"/>
</UserControl.Resources>
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock Text="传感器标定" FontWeight="Bold" Margin="0,0,0,20" Foreground="{StaticResource GrayBrush}" FontSize="18"/>
<!-- 连接状态 -->
<Border Background="#F8F9FA" CornerRadius="4" Padding="15" Margin="0,0,0,20">
<StackPanel>
<TextBlock Text="设备连接" FontWeight="Bold" Margin="0,0,0,10" FontSize="14"/>
<TextBlock Text="{Binding ConnectionInfo}" FontSize="12" Foreground="{StaticResource GrayBrush}" Margin="0,0,0,10"/>
<Button Command="{Binding ToggleConnectionCommand}" Height="50" Foreground="White" FontSize="14">
<Button.Content>
<TextBlock Text="{Binding ConnectionButtonText}"/>
</Button.Content>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="#27AE60"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Background" Value="#E74C3C"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Border>
<!-- 标定操作 -->
<Border Background="#F8F9FA" CornerRadius="4" Padding="15">
<StackPanel>
<TextBlock Text="标定操作" FontWeight="Bold" Margin="0,0,0,10" FontSize="14"/>
<TextBlock Text="请确保设备已连接,然后执行以下标定步骤:" TextWrapping="Wrap" Margin="0,0,0,15" FontSize="12"/>
<Button Content="零点标定" Command="{Binding ResetCommand}" Height="50" Foreground="White" FontSize="14" Margin="0,0,0,10">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</Button.Style>
</Button>
<TextBlock Text="说明:在无负载状态下执行零点标定,将当前读数设为零点。"
TextWrapping="Wrap" FontSize="11" Foreground="{StaticResource GrayBrush}" Margin="0,5,0,0"/>
</StackPanel>
</Border>
</StackPanel>
</Border>
</UserControl>

View File

@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace COFTester.Views
{
public partial class CalibrationPage : UserControl
{
public CalibrationPage()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,95 @@
<UserControl x:Class="COFTester.Views.HistoryPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="Transparent">
<UserControl.Resources>
<Style x:Key="CardStyle" TargetType="Border">
<Setter Property="Background" Value="White"/>
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Padding" Value="15"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="10" ShadowDepth="2" Opacity="0.1"/>
</Setter.Value>
</Setter>
</Style>
<SolidColorBrush x:Key="GrayBrush" Color="#7F8C8D"/>
<SolidColorBrush x:Key="AccentBrush" Color="#3498DB"/>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="320"/>
</Grid.ColumnDefinitions>
<!-- 左侧:数据列表 -->
<Border Grid.Column="0" Style="{StaticResource CardStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Lang.TestList}" FontWeight="Bold" Margin="0,0,0,10" Foreground="{StaticResource GrayBrush}" FontSize="16"/>
<DataGrid Grid.Row="1" ItemsSource="{Binding TestRecords}" AutoGenerateColumns="False"
IsReadOnly="True" CanUserAddRows="False" CanUserDeleteRows="False"
GridLinesVisibility="Horizontal" HeadersVisibility="Column"
Background="White" BorderThickness="0">
<DataGrid.Columns>
<DataGridTextColumn Header="样本编号" Binding="{Binding SampleNumber}" Width="100"/>
<DataGridTextColumn Header="测试时间" Binding="{Binding TestDateTime, StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}" Width="150"/>
<DataGridTextColumn Header="静摩擦系数" Binding="{Binding StaticCOF, StringFormat={}{0:F4}}" Width="120"/>
<DataGridTextColumn Header="动摩擦系数" Binding="{Binding KineticCOF, StringFormat={}{0:F4}}" Width="120"/>
<DataGridTextColumn Header="最大力值(N)" Binding="{Binding MaxForce, StringFormat={}{0:F3}}" Width="120"/>
<DataGridTextColumn Header="平均力值(N)" Binding="{Binding AvgKineticForce, StringFormat={}{0:F3}}" Width="120"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Border>
<!-- 右侧:统计和操作 -->
<ScrollViewer Grid.Column="1" VerticalScrollBarVisibility="Auto">
<StackPanel>
<!-- 成组统计 -->
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock Text="{Binding Lang.GroupStatistics}" FontWeight="Bold" Margin="0,0,0,10" Foreground="{StaticResource GrayBrush}"/>
<TextBlock Text="{Binding GroupStatistics}" TextWrapping="Wrap" FontSize="12"/>
</StackPanel>
</Border>
<!-- 操作按钮 -->
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<Button Content="{Binding Lang.GenerateReport}" Command="{Binding GenerateReportCommand}"
Height="50" Foreground="White" FontSize="14" Margin="0,0,0,10">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</Button.Style>
</Button>
<Button Content="{Binding Lang.ClearAllTests}" Command="{Binding ClearAllTestsCommand}"
Height="50" Foreground="White" FontSize="14">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{StaticResource GrayBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace COFTester.Views
{
public partial class HistoryPage : UserControl
{
public HistoryPage()
{
InitializeComponent();
}
}
}

View File

@@ -1,475 +1,128 @@
<Window x:Class="COFTester.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:scottplot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
mc:Ignorable="d"
Title="CSI-H238M Precision Friction Tester" Height="800" Width="1200"
Title="CSI-H238M Precision Friction Tester" Height="800" Width="1024"
Background="#F0F3F5">
<Window.Resources>
<!-- 畫刷定義 -->
<SolidColorBrush x:Key="PrimaryBrush" Color="#2C3E50"/>
<SolidColorBrush x:Key="AccentBrush" Color="#3498DB"/>
<SolidColorBrush x:Key="SuccessBrush" Color="#27AE60"/>
<SolidColorBrush x:Key="DangerBrush" Color="#E74C3C"/>
<SolidColorBrush x:Key="WarningBrush" Color="#F39C12"/>
<SolidColorBrush x:Key="GrayBrush" Color="#7F8C8D"/>
<SolidColorBrush x:Key="LightGrayBrush" Color="#BDC3C7"/>
<!-- 卡片樣式 -->
<Style x:Key="CardStyle" TargetType="Border">
<Setter Property="Background" Value="White"/>
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Padding" Value="15"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Effect">
<Style x:Key="NavButtonStyle" TargetType="RadioButton">
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="100"/>
<Setter Property="Margin" Value="0,5"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="#7F8C8D"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<DropShadowEffect BlurRadius="10" ShadowDepth="2" Opacity="0.1"/>
<ControlTemplate TargetType="RadioButton">
<Border Background="{TemplateBinding Background}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{TemplateBinding Tag}" FontFamily="Segoe MDL2 Assets" FontSize="32" Foreground="{TemplateBinding Foreground}" HorizontalAlignment="Center" Margin="0,0,0,5"/>
<TextBlock Text="{TemplateBinding Content}" FontSize="12" Foreground="{TemplateBinding Foreground}" HorizontalAlignment="Center" FontWeight="Bold"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="#3498DB"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#ECF0F1"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 數字顯示樣式 -->
<Style x:Key="DigitalDisplay" TargetType="TextBlock">
<Setter Property="FontSize" Value="42"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="{StaticResource PrimaryBrush}"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</Window.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<!-- Header -->
<RowDefinition Height="*"/>
<!-- Main Content -->
<RowDefinition Height="Auto"/>
<!-- Status Bar -->
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 1. Header -->
<Border Grid.Row="0" Style="{StaticResource CardStyle}" Margin="5,5,5,10">
<Border.Background>
<SolidColorBrush Color="#2C3E50"/>
</Border.Background>
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left">
<Ellipse Width="15" Height="15" Margin="0,0,10,0">
<Ellipse.Fill>
<SolidColorBrush Color="#27AE60"/>
</Ellipse.Fill>
</Ellipse>
<TextBlock Text="{Binding Lang.AppTitle}" Foreground="White" FontSize="18" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Right" HorizontalAlignment="Right">
<Button Content="{Binding Lang.LanguageButton}" Command="{Binding SwitchLanguageCommand}"
Width="60" Height="30" Margin="0,0,10,0"
Background="#34495E" Foreground="White" BorderThickness="0"
Cursor="Hand" FontSize="12" ToolTip="{Binding Lang.LanguageTooltip}"/>
<TextBlock Text="{Binding CurrentDateTime, StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}" VerticalAlignment="Center">
<TextBlock.Foreground>
<SolidColorBrush Color="#BDC3C7"/>
</TextBlock.Foreground>
</TextBlock>
</StackPanel>
</DockPanel>
<Border Grid.Column="0" Background="#2C3E50">
<StackPanel>
<Border Height="60" Background="#34495E">
<Ellipse Width="40" Height="40" Fill="#27AE60"/>
</Border>
<RadioButton x:Name="NavTest" Content="{Binding Lang.NavTest}" Tag="&#xE8B7;" Style="{StaticResource NavButtonStyle}" IsChecked="True" Checked="NavButton_Checked"/>
<RadioButton x:Name="NavHistory" Content="{Binding Lang.NavHistory}" Tag="&#xE81C;" Style="{StaticResource NavButtonStyle}" Checked="NavButton_Checked"/>
<RadioButton x:Name="NavSettings" Content="{Binding Lang.NavSettings}" Tag="&#xE713;" Style="{StaticResource NavButtonStyle}" Checked="NavButton_Checked"/>
<RadioButton x:Name="NavCalibration" Content="{Binding Lang.NavCalibration}" Tag="&#xE9F9;" Style="{StaticResource NavButtonStyle}" Checked="NavButton_Checked"/>
</StackPanel>
</Border>
<!-- 2. Main Content -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="130"/>
<!-- Left: Test List -->
<ColumnDefinition Width="300"/>
<!-- Left-Center: Controls & Params -->
<ColumnDefinition Width="*"/>
<!-- Center: Chart -->
<ColumnDefinition Width="300"/>
<!-- Right: Results -->
</Grid.ColumnDefinitions>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 2.0 Test List Panel -->
<Border Grid.Column="0" Style="{StaticResource CardStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Lang.TestList}" FontWeight="Bold" Margin="0,0,0,10" Foreground="{StaticResource GrayBrush}"/>
<ListBox Grid.Row="1" ItemsSource="{Binding TestRecords}" SelectedItem="{Binding LatestResult}"
Background="Transparent" BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,5">
<CheckBox IsChecked="{Binding IsVisible}" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock Text="{Binding SampleNumber}" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Grid.Row="2" Content="{Binding Lang.ClearAllTests}" Command="{Binding ClearAllTestsCommand}"
Height="32" Foreground="White" FontSize="12" Margin="0,10,0,0">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{StaticResource GrayBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</Button.Style>
</Button>
</Grid>
<Border Grid.Row="0" Background="#2C3E50" Padding="15,0">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left" VerticalAlignment="Center">
<TextBlock Text="{Binding Lang.AppTitle}" Foreground="White" FontSize="16" FontWeight="Bold"/>
</StackPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Right" HorizontalAlignment="Right" VerticalAlignment="Center">
<Button Content="{Binding Lang.LanguageButton}" Command="{Binding SwitchLanguageCommand}" Width="60" Height="30" Margin="0,0,15,0" Background="#34495E" Foreground="White" BorderThickness="0" Cursor="Hand" FontSize="12"/>
<TextBlock Text="{Binding CurrentDateTime, StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}" Foreground="#BDC3C7" VerticalAlignment="Center" FontSize="12"/>
</StackPanel>
</DockPanel>
</Border>
<!-- 2.1 Left Panel: Controls & Parameters -->
<ScrollViewer Grid.Column="1" VerticalScrollBarVisibility="Auto">
<StackPanel>
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock Text="{Binding Lang.RealTimeMonitor}" FontWeight="Bold" Margin="0,0,0,10" Foreground="{StaticResource GrayBrush}"/>
<TextBlock Text="{Binding Lang.Force}" FontSize="12" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding CurrentForce, StringFormat={}{0:F4}}" Style="{StaticResource DigitalDisplay}" Foreground="{StaticResource DangerBrush}"/>
<Separator Margin="0,10"/>
<TextBlock Text="{Binding Lang.Displacement}" FontSize="12" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding CurrentDisp, StringFormat={}{0:F2}}" Style="{StaticResource DigitalDisplay}" FontSize="32"/>
</StackPanel>
</Border>
<ContentControl Grid.Row="1" x:Name="ContentArea" Margin="10"/>
<Border Style="{StaticResource CardStyle}" Margin="5,10,5,5">
<StackPanel>
<TextBlock Text="{Binding Lang.TestControl}" FontWeight="Bold" Margin="0,0,0,10" Foreground="{StaticResource GrayBrush}"/>
<!-- 連接控制 - 單一切換按鈕 -->
<Button Command="{Binding ToggleConnectionCommand}" Height="36" Foreground="White" FontSize="12" Margin="0,0,0,10">
<Button.Content>
<TextBlock Text="{Binding ConnectionButtonText}"/>
</Button.Content>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="#27AE60"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Background" Value="#E74C3C"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsConnecting}" Value="True">
<Setter Property="Background" Value="#F39C12"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<!-- 連接狀態顯示 -->
<Border Background="#F8F9FA" CornerRadius="4" Padding="8" Margin="0,0,0,10">
<StackPanel>
<TextBlock Text="{Binding ConnectionInfo}" FontSize="11" Foreground="{StaticResource GrayBrush}" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<Button Content="{Binding Lang.StartTest}" Command="{Binding StartCommand}" Height="50" Foreground="White" FontWeight="Bold" Margin="0,5" FontSize="16">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{StaticResource SuccessBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</Button.Style>
</Button>
<Button Content="{Binding Lang.Stop}" Command="{Binding StopCommand}" Height="40" Foreground="White" Margin="0,5" FontSize="14">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{StaticResource DangerBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</Button.Style>
</Button>
<Button Content="{Binding Lang.ResetSystem}" Command="{Binding ResetCommand}" Height="40" Foreground="White" Margin="0,5" FontSize="14">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Border>
<Border Style="{StaticResource CardStyle}" Margin="5,10,5,5">
<StackPanel>
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Lang.TestParameters}" FontWeight="Bold" Foreground="{StaticResource GrayBrush}" VerticalAlignment="Center"/>
<Button Grid.Column="1" Command="{Binding OpenConfigCommand}"
Width="32" Height="32"
Background="{StaticResource AccentBrush}"
BorderThickness="0"
Cursor="Hand"
ToolTip="{Binding Lang.DetailedConfig}"
Padding="0">
<TextBlock Text="&#xE713;" FontFamily="Segoe MDL2 Assets"
FontSize="16" Foreground="White"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
</Grid>
<TextBlock Text="{Binding Lang.Standard}" Margin="0,5,0,2"/>
<ComboBox SelectedValue="{Binding Parameters.Standard, Mode=TwoWay}"
SelectedValuePath="Content" Margin="0,0,0,8" Height="36"
VerticalContentAlignment="Center">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="10,8"/>
<Setter Property="MinHeight" Value="36"/>
</Style>
</ComboBox.ItemContainerStyle>
<ComboBoxItem Content="GB 10006"/>
<ComboBoxItem Content="ISO 8295"/>
<ComboBoxItem Content="ASTM D1894"/>
<ComboBoxItem Content="GB/T 22895"/>
<ComboBoxItem Content="TAPPI T816"/>
<ComboBoxItem Content="GB/T 2792"/>
<ComboBoxItem Content="GB/T 17200"/>
<ComboBoxItem Content="Custom"/>
</ComboBox>
<!-- 滑块质量/试样宽度 - 根据标准动态切换 -->
<TextBlock x:Name="MassOrWidthLabel" Margin="0,5,0,2">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Lang.SledMass}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Parameters.Standard}" Value="GB/T 2792">
<Setter Property="Text" Value="{Binding Lang.SampleWidth}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBox x:Name="MassOrWidthValue" Margin="0,0,0,8" IsReadOnly="True" Background="#F5F5F5">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Text" Value="{Binding Parameters.SledMass}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Parameters.Standard}" Value="GB/T 2792">
<Setter Property="Text" Value="{Binding Parameters.SampleWidth}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBlock Text="{Binding Lang.TestSpeed}" Margin="0,5,0,2"/>
<TextBox Text="{Binding Parameters.TestSpeed}" Margin="0,0,0,8" IsReadOnly="True" Background="#F5F5F5"/>
<TextBlock Text="{Binding Lang.TestDuration}" Margin="0,5,0,2"/>
<TextBox Text="{Binding Parameters.TestDuration}" Margin="0,0,0,8" IsReadOnly="True" Background="#F5F5F5"/>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
<!-- 2.2 Center Panel: Real-time Chart -->
<Border Grid.Column="2" Style="{StaticResource CardStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Lang.FrictionCurve}" FontWeight="Bold" Margin="0,0,0,10" Foreground="{StaticResource GrayBrush}" FontSize="14"/>
<!-- 显示全部曲线勾选框 -->
<CheckBox Grid.Row="1" Content="{Binding Lang.ShowAllCurves}" IsChecked="{Binding ShowAllCurves}"
Margin="0,0,0,10" FontSize="12"/>
<!-- ScottPlot 实时曲线 -->
<scottplot:WpfPlot Grid.Row="2" x:Name="FrictionPlot" />
</Grid>
</Border>
<!-- 2.3 Right Panel: Results -->
<ScrollViewer Grid.Column="3" VerticalScrollBarVisibility="Auto">
<StackPanel>
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock Text="{Binding Lang.TestResults}" FontWeight="Bold" Margin="0,0,0,10" Foreground="{StaticResource GrayBrush}"/>
<!-- 摩擦系数模式 - 默认显示 -->
<StackPanel x:Name="FrictionResultsPanel">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Parameters.Standard}" Value="GB/T 2792">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock Text="{Binding Lang.StaticCOF}" FontSize="12"/>
<TextBlock Text="{Binding LatestResult.StaticCOF, StringFormat={}{0:F4}, FallbackValue=--}" FontSize="28" FontWeight="Bold" Foreground="{StaticResource WarningBrush}"/>
<Separator Margin="0,10"/>
<TextBlock Text="{Binding Lang.KineticCOF}" FontSize="12"/>
<TextBlock Text="{Binding LatestResult.KineticCOF, StringFormat={}{0:F4}, FallbackValue=--}" FontSize="28" FontWeight="Bold" Foreground="{StaticResource WarningBrush}"/>
<Separator Margin="0,10"/>
<TextBlock Text="{Binding Lang.MaxForce}" FontSize="12"/>
<TextBlock Text="{Binding LatestResult.MaxForce, StringFormat={}{0:F3}, FallbackValue=--}" FontSize="20" FontWeight="Bold"/>
</StackPanel>
<!-- 剥离强度模式 - GB/T 2792 时显示 -->
<StackPanel x:Name="PeelResultsPanel">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Parameters.Standard}" Value="GB/T 2792">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock Text="{Binding Lang.PeelStrength}" FontSize="12"/>
<TextBlock Text="{Binding LatestResult.PeelStrength_N_25mm, StringFormat={}{0:F3}, FallbackValue=--}" FontSize="28" FontWeight="Bold" Foreground="{StaticResource SuccessBrush}"/>
<Separator Margin="0,10"/>
<TextBlock Text="{Binding Lang.AvgPeelForce}" FontSize="12"/>
<TextBlock Text="{Binding LatestResult.AvgKineticForce, StringFormat={}{0:F3}, FallbackValue=--}" FontSize="28" FontWeight="Bold" Foreground="{StaticResource AccentBrush}"/>
<Separator Margin="0,10"/>
<TextBlock Text="{Binding Lang.MaxForce}" FontSize="12"/>
<TextBlock Text="{Binding LatestResult.MaxForce, StringFormat={}{0:F3}, FallbackValue=--}" FontSize="20" FontWeight="Bold"/>
</StackPanel>
</StackPanel>
</Border>
<Border Style="{StaticResource CardStyle}" Margin="5,10,5,5">
<StackPanel>
<TextBlock Text="{Binding Lang.Statistics}" FontWeight="Bold" Margin="0,0,0,10" Foreground="{StaticResource GrayBrush}"/>
<!-- 标签页控件 -->
<TabControl Background="Transparent" BorderThickness="0">
<!-- 当前测试统计 -->
<TabItem Header="{Binding Lang.CurrentTest}">
<Grid Margin="0,10,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Lang.AvgForce}" Margin="0,5"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding LatestResult.AvgKineticForce, StringFormat={}{0:F3} N, FallbackValue=--}" FontWeight="Bold" Margin="5,5" HorizontalAlignment="Right"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding Lang.TestTime}" Margin="0,5"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding LatestResult.TestDateTime, StringFormat={}{0:HH:mm:ss}, FallbackValue=--}" FontWeight="Bold" Margin="5,5" HorizontalAlignment="Right"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding Lang.DataPoints}" Margin="0,5"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding DataPointsCount, FallbackValue=0}" FontWeight="Bold" Margin="5,5" HorizontalAlignment="Right"/>
</Grid>
</TabItem>
<!-- 成组统计 -->
<TabItem Header="{Binding Lang.GroupStatistics}">
<TextBlock Text="{Binding GroupStatistics}" Margin="0,10,0,0" TextWrapping="Wrap" FontSize="12"/>
</TabItem>
</TabControl>
<Button Content="{Binding Lang.GenerateReport}" Command="{Binding GenerateReportCommand}"
Margin="0,15,0,0" Height="40" Foreground="White" FontSize="14">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="#95A5A6"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
<StatusBar Grid.Row="2" Background="White" Padding="10,5">
<StatusBarItem>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Lang.Status}" Foreground="#7F8C8D" Margin="0,0,5,0"/>
<TextBlock Text="{Binding StatusMessage}" Foreground="#2C3E50" FontWeight="Bold"/>
</StackPanel>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<StackPanel Orientation="Horizontal">
<Ellipse Width="8" Height="8" Margin="0,0,5,0">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="#E74C3C"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Fill" Value="#27AE60"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Lang.NotConnected}"/>
<Setter Property="Foreground" Value="#E74C3C"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Text" Value="{Binding Lang.Connected}"/>
<Setter Property="Foreground" Value="#27AE60"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsConnecting}" Value="True">
<Setter Property="Text" Value="{Binding Lang.Connecting}"/>
<Setter Property="Foreground" Value="#F39C12"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock Text="{Binding CommunicationMode}" Foreground="#7F8C8D" FontSize="11"/>
</StatusBarItem>
</StatusBar>
</Grid>
<!-- 3. Status Bar -->
<StatusBar Grid.Row="2" Background="Transparent" Margin="5">
<StatusBarItem>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Lang.Status}" Foreground="{StaticResource GrayBrush}"/>
<TextBlock Text="{Binding StatusMessage}" Foreground="{StaticResource PrimaryBrush}" FontWeight="Bold"/>
</StackPanel>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<StackPanel Orientation="Horizontal">
<Ellipse Width="8" Height="8" Margin="0,0,5,0">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="#E74C3C"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Fill" Value="#27AE60"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Lang.NotConnected}"/>
<Setter Property="Foreground" Value="#E74C3C"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Text" Value="{Binding Lang.Connected}"/>
<Setter Property="Foreground" Value="#27AE60"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsConnecting}" Value="True">
<Setter Property="Text" Value="{Binding Lang.Connecting}"/>
<Setter Property="Foreground" Value="#F39C12"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock Text="{Binding CommunicationMode}" Foreground="{StaticResource GrayBrush}" FontSize="11"/>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock Foreground="{StaticResource GrayBrush}">
<Run Text="{Binding Lang.DataPointsCount, Mode=OneWay}"/>
<Run Text="{Binding DataPointsCount, Mode=OneWay}"/>
</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
</Window>

View File

@@ -2,37 +2,85 @@
using COFTester.Services;
using COFTester.Models;
using System.Windows;
using System.Windows.Controls;
namespace COFTester.Views
{
/// <summary>
/// MainWindow.xaml 的交互邏輯
/// </summary>
public partial class MainWindow : Window
{
private readonly MainViewModel _viewModel;
private readonly TestPage _testPage;
private readonly HistoryPage _historyPage;
private readonly SettingsPage _settingsPage;
private readonly CalibrationPage _calibrationPage;
public MainWindow()
{
InitializeComponent();
// 加配置
// 加配置
var config = AppConfig.Load(AppConfig.GetDefaultConfigPath());
// 根據配置創建數據採集服務(支持 Modbus TCP/RTU/ASCII 和模擬模式)
// 创建数据采集服务
IDataAcquisitionService daqService = ModbusServiceFactory.CreateService(config);
var processingService = new DataProcessingService();
// 設置 DataContext
_viewModel = new MainViewModel(daqService, processingService, FrictionPlot, config);
// 创建ViewModel
_viewModel = new MainViewModel(daqService, processingService, config);
this.DataContext = _viewModel;
// 创建所有页面
_testPage = new TestPage();
_historyPage = new HistoryPage();
_settingsPage = new SettingsPage();
_calibrationPage = new CalibrationPage();
// 设置页面的DataContext
_testPage.SetViewModel(_viewModel);
_historyPage.DataContext = _viewModel;
_settingsPage.DataContext = _viewModel;
_calibrationPage.DataContext = _viewModel;
// 默认显示测试页面(确保在 Loaded 事件后设置)
this.Loaded += (s, e) =>
{
if (ContentArea != null)
{
ContentArea.Content = _testPage;
}
};
}
private void NavButton_Checked(object sender, RoutedEventArgs e)
{
// 确保 ContentArea 已初始化
if (ContentArea == null) return;
if (sender is RadioButton radioButton)
{
if (radioButton == NavTest)
{
ContentArea.Content = _testPage;
}
else if (radioButton == NavHistory)
{
ContentArea.Content = _historyPage;
}
else if (radioButton == NavSettings)
{
ContentArea.Content = _settingsPage;
}
else if (radioButton == NavCalibration)
{
ContentArea.Content = _calibrationPage;
}
}
}
protected override void OnClosed(System.EventArgs e)
{
// 清理資源
_viewModel?.Dispose();
base.OnClosed(e);
}
}
}
}

View File

@@ -0,0 +1,75 @@
<UserControl x:Class="COFTester.Views.SettingsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="Transparent">
<UserControl.Resources>
<Style x:Key="CardStyle" TargetType="Border">
<Setter Property="Background" Value="White"/>
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Padding" Value="15"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="10" ShadowDepth="2" Opacity="0.1"/>
</Setter.Value>
</Setter>
</Style>
<SolidColorBrush x:Key="GrayBrush" Color="#7F8C8D"/>
<SolidColorBrush x:Key="AccentBrush" Color="#3498DB"/>
</UserControl.Resources>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel MaxWidth="800">
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<Grid Margin="0,0,0,15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Lang.TestParameters}" FontWeight="Bold" Foreground="{StaticResource GrayBrush}" VerticalAlignment="Center" FontSize="16"/>
<Button Grid.Column="1" Command="{Binding OpenConfigCommand}" Width="120" Height="40" Background="{StaticResource AccentBrush}" BorderThickness="0" Cursor="Hand" Foreground="White" FontSize="14">
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE713;" FontFamily="Segoe MDL2 Assets" FontSize="16" Margin="0,0,5,0" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Lang.DetailedConfig}" VerticalAlignment="Center"/>
</StackPanel>
</Button>
</Grid>
<TextBlock Text="{Binding Lang.Standard}" Margin="0,10,0,5" FontSize="13"/>
<ComboBox SelectedValue="{Binding Parameters.Standard, Mode=TwoWay}" SelectedValuePath="Content" Margin="0,0,0,15" Height="40" VerticalContentAlignment="Center" FontSize="13">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="10,8"/>
<Setter Property="MinHeight" Value="40"/>
</Style>
</ComboBox.ItemContainerStyle>
<ComboBoxItem Content="GB 10006"/>
<ComboBoxItem Content="ISO 8295"/>
<ComboBoxItem Content="ASTM D1894"/>
<ComboBoxItem Content="GB/T 22895"/>
<ComboBoxItem Content="TAPPI T816"/>
<ComboBoxItem Content="GB/T 2792"/>
<ComboBoxItem Content="GB/T 17200"/>
<ComboBoxItem Content="Custom"/>
</ComboBox>
<TextBlock Text="{Binding Lang.TestSpeed}" Margin="0,10,0,5" FontSize="13"/>
<TextBox Text="{Binding Parameters.TestSpeed, Mode=OneWay}" Margin="0,0,0,15" IsReadOnly="True" Background="#F5F5F5" Height="40" VerticalContentAlignment="Center" FontSize="13"/>
<TextBlock Text="{Binding Lang.TestDuration}" Margin="0,10,0,5" FontSize="13"/>
<TextBox Text="{Binding Parameters.TestDuration, Mode=OneWay}" Margin="0,0,0,15" IsReadOnly="True" Background="#F5F5F5" Height="40" VerticalContentAlignment="Center" FontSize="13"/>
</StackPanel>
</Border>
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock Text="系统设置" FontWeight="Bold" Margin="0,0,0,15" Foreground="{StaticResource GrayBrush}" FontSize="16"/>
<TextBlock Text="通信模式" Margin="0,10,0,5" FontSize="13"/>
<TextBox Text="{Binding CommunicationMode, Mode=OneWay}" IsReadOnly="True" Background="#F5F5F5" Height="40" VerticalContentAlignment="Center" FontSize="13" Margin="0,0,0,15"/>
<TextBlock Text="连接信息" Margin="0,10,0,5" FontSize="13"/>
<TextBox Text="{Binding ConnectionInfo, Mode=OneWay}" IsReadOnly="True" Background="#F5F5F5" Height="40" VerticalContentAlignment="Center" FontSize="13"/>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace COFTester.Views
{
public partial class SettingsPage : UserControl
{
public SettingsPage()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,212 @@
<UserControl x:Class="COFTester.Views.TestPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:scottplot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
Background="Transparent">
<UserControl.Resources>
<!-- 卡片样式 -->
<Style x:Key="CardStyle" TargetType="Border">
<Setter Property="Background" Value="White"/>
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Padding" Value="15"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="10" ShadowDepth="2" Opacity="0.1"/>
</Setter.Value>
</Setter>
</Style>
<!-- 数字显示样式 -->
<Style x:Key="DigitalDisplay" TargetType="TextBlock">
<Setter Property="FontSize" Value="42"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="#2C3E50"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
<SolidColorBrush x:Key="PrimaryBrush" Color="#2C3E50"/>
<SolidColorBrush x:Key="AccentBrush" Color="#3498DB"/>
<SolidColorBrush x:Key="SuccessBrush" Color="#27AE60"/>
<SolidColorBrush x:Key="DangerBrush" Color="#E74C3C"/>
<SolidColorBrush x:Key="WarningBrush" Color="#F39C12"/>
<SolidColorBrush x:Key="GrayBrush" Color="#7F8C8D"/>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<!-- 左侧:图表 -->
<ColumnDefinition Width="320"/>
<!-- 右侧:控制和结果 -->
</Grid.ColumnDefinitions>
<!-- 左侧:图表区域 -->
<Border Grid.Column="0" Style="{StaticResource CardStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Lang.FrictionCurve}" FontWeight="Bold" Margin="0,0,0,10" Foreground="{StaticResource GrayBrush}" FontSize="16"/>
<!-- 试验列表 -->
<Border Grid.Row="1" Background="#F8F9FA" CornerRadius="4" Padding="10" Margin="0,0,0,10">
<StackPanel>
<TextBlock Text="{Binding Lang.TestList}" FontWeight="Bold" FontSize="12" Margin="0,0,0,5" Foreground="{StaticResource GrayBrush}"/>
<ListBox ItemsSource="{Binding TestRecords}" SelectedItem="{Binding LatestResult}"
Background="Transparent" BorderThickness="0" MaxHeight="100">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,2">
<CheckBox IsChecked="{Binding IsVisible}" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock Text="{Binding SampleNumber}" FontWeight="Bold" VerticalAlignment="Center" FontSize="12"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Border>
<!-- 显示全部曲线勾选框 -->
<CheckBox Grid.Row="2" Content="{Binding Lang.ShowAllCurves}" IsChecked="{Binding ShowAllCurves}"
Margin="0,0,0,10" FontSize="12"/>
<!-- ScottPlot 实时曲线 -->
<scottplot:WpfPlot Grid.Row="3" x:Name="FrictionPlot" />
</Grid>
</Border>
<!-- 右侧:控制和结果 -->
<ScrollViewer Grid.Column="1" VerticalScrollBarVisibility="Auto">
<StackPanel>
<!-- 实时监控 -->
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock Text="{Binding Lang.RealTimeMonitor}" FontWeight="Bold" Margin="0,0,0,10" Foreground="{StaticResource GrayBrush}"/>
<TextBlock Text="{Binding Lang.Force}" FontSize="12" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding CurrentForce, StringFormat={}{0:F4}}" Style="{StaticResource DigitalDisplay}" Foreground="{StaticResource DangerBrush}" FontSize="36"/>
<Separator Margin="0,10"/>
<TextBlock Text="{Binding Lang.Displacement}" FontSize="12" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding CurrentDisp, StringFormat={}{0:F2}}" Style="{StaticResource DigitalDisplay}" FontSize="28"/>
</StackPanel>
</Border>
<!-- 测试控制 -->
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock Text="{Binding Lang.TestControl}" FontWeight="Bold" Margin="0,0,0,10" Foreground="{StaticResource GrayBrush}"/>
<Button Content="{Binding Lang.StartTest}" Command="{Binding StartCommand}" Height="80" Foreground="White" FontWeight="Bold" Margin="0,5" FontSize="18">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{StaticResource SuccessBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</Button.Style>
</Button>
<Button Content="{Binding Lang.Stop}" Command="{Binding StopCommand}" Height="60" Foreground="White" Margin="0,5" FontSize="16">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{StaticResource DangerBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</Button.Style>
</Button>
<Button Content="{Binding Lang.ResetSystem}" Command="{Binding ResetCommand}" Height="50" Foreground="White" Margin="0,5" FontSize="14">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Border>
<!-- 测试结果 -->
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock Text="{Binding Lang.TestResults}" FontWeight="Bold" Margin="0,0,0,10" Foreground="{StaticResource GrayBrush}"/>
<!-- 摩擦系数模式 -->
<StackPanel x:Name="FrictionResultsPanel">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Parameters.Standard}" Value="GB/T 2792">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock Text="{Binding Lang.StaticCOF}" FontSize="11"/>
<TextBlock Text="{Binding LatestResult.StaticCOF, StringFormat={}{0:F4}, FallbackValue=--}" FontSize="24" FontWeight="Bold" Foreground="{StaticResource WarningBrush}"/>
<Separator Margin="0,8"/>
<TextBlock Text="{Binding Lang.KineticCOF}" FontSize="11"/>
<TextBlock Text="{Binding LatestResult.KineticCOF, StringFormat={}{0:F4}, FallbackValue=--}" FontSize="24" FontWeight="Bold" Foreground="{StaticResource WarningBrush}"/>
<Separator Margin="0,8"/>
<TextBlock Text="{Binding Lang.MaxForce}" FontSize="11"/>
<TextBlock Text="{Binding LatestResult.MaxForce, StringFormat={}{0:F3}, FallbackValue=--}" FontSize="18" FontWeight="Bold"/>
</StackPanel>
<!-- 剥离强度模式 -->
<StackPanel x:Name="PeelResultsPanel">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Parameters.Standard}" Value="GB/T 2792">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock Text="{Binding Lang.PeelStrength}" FontSize="11"/>
<TextBlock Text="{Binding LatestResult.PeelStrength_N_25mm, StringFormat={}{0:F3}, FallbackValue=--}" FontSize="24" FontWeight="Bold" Foreground="{StaticResource SuccessBrush}"/>
<Separator Margin="0,8"/>
<TextBlock Text="{Binding Lang.AvgPeelForce}" FontSize="11"/>
<TextBlock Text="{Binding LatestResult.AvgKineticForce, StringFormat={}{0:F3}, FallbackValue=--}" FontSize="24" FontWeight="Bold" Foreground="{StaticResource AccentBrush}"/>
<Separator Margin="0,8"/>
<TextBlock Text="{Binding Lang.MaxForce}" FontSize="11"/>
<TextBlock Text="{Binding LatestResult.MaxForce, StringFormat={}{0:F3}, FallbackValue=--}" FontSize="18" FontWeight="Bold"/>
</StackPanel>
</StackPanel>
</Border>
<!-- 快速结果预览 -->
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock Text="{Binding Lang.CurrentTest}" FontWeight="Bold" Margin="0,0,0,10" Foreground="{StaticResource GrayBrush}"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Lang.DataPoints}" Margin="0,5" FontSize="11"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding DataPointsCount, FallbackValue=0}" FontWeight="Bold" Margin="5,5" HorizontalAlignment="Right" FontSize="11"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding Lang.TestTime}" Margin="0,5" FontSize="11"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding LatestResult.TestDateTime, StringFormat={}{0:HH:mm:ss}, FallbackValue=--}" FontWeight="Bold" Margin="5,5" HorizontalAlignment="Right" FontSize="11"/>
</Grid>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -0,0 +1,19 @@
using System.Windows.Controls;
using COFTester.ViewModels;
namespace COFTester.Views
{
public partial class TestPage : UserControl
{
public TestPage()
{
InitializeComponent();
}
public void SetViewModel(MainViewModel viewModel)
{
this.DataContext = viewModel;
viewModel.SetPlot(FrictionPlot);
}
}
}