This commit is contained in:
xyy
2026-04-01 20:29:45 +08:00
parent dd0bd405ee
commit 05c38a7dab
13 changed files with 976 additions and 158 deletions

View File

@@ -1,7 +1,7 @@
<Application x:Class="MembranePoreTester.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Views/MainWindow.xaml">
StartupUri="Views/TestView.xaml">
<Application.Resources>
<!-- 全局样式定义 -->
<ResourceDictionary>

View File

@@ -17,39 +17,50 @@
public ushort PressureRegisterStation1 { get; set; }
public ushort PressureRegisterStation2 { get; set; }
public ushort PressureRegisterStation3 { get; set; }
public ushort PressureModeRegister { get; set; }
public ushort PressCoil { get; set; }
public ushort StartCoil { get; set; }
public ushort EnableCoil { get; set; }
public ushort StopCoil { get; set; }
public ushort PressureUpperLimit { get; set; } = 300;
public ushort PressureRate { get; set; } = 280;
public ushort PressureCoeff { get; set; } = 282;
public ushort HPCoeff1 { get; set; } = 3120;
public ushort LPCoeff1 { get; set; } = 3122;
public ushort HPCoeff2 { get; set; } = 3124;
public ushort LPCoeff2 { get; set; } = 3126;
public ushort HPCoeff3 { get; set; } = 3128;
public ushort LPCoeff3 { get; set; } = 3130;
public ushort LargeFlowCoeff1 { get; set; } = 3048;
public ushort SmallFlowCoeff1 { get; set; } = 380;
public ushort LargeFlowCoeff2 { get; set; } = 1218;
public ushort SmallFlowCoeff2 { get; set; } = 1318;
public ushort LargeFlowCoeff3 { get; set; } = 1418;
public ushort SmallFlowCoeff3 { get; set; } = 1468;
public ushort FlowModeRegister1 { get; set; } = 5; // 工位1流量模式 (0=大,1=小)
public ushort FlowModeRegister2 { get; set; } = 6;
public ushort FlowModeRegister3 { get; set; } = 7;
// 校准系数地址需与PLC约定
public ushort PressureCalibZero { get; set; } = 3200;
public ushort PressureCalibSpan { get; set; } = 3202;
public ushort FlowCalibZero { get; set; } = 3204;
public ushort FlowCalibSpan { get; set; } = 3206;
// 设备参数地址示例请根据实际PLC地址修改
public ushort UpperLampData1 { get; set; } = 350;
public ushort UpperLampData2 { get; set; } = 351;
public ushort UpperLampData3 { get; set; } = 352;
public ushort UpperLampData4 { get; set; } = 353;
public ushort UpperLampData5 { get; set; } = 354;
public ushort UpperLampData6 { get; set; } = 355;
public ushort LowerLampData1 { get; set; } = 356;
public ushort LowerLampData2 { get; set; } = 357;
public ushort LowerLampData3 { get; set; } = 358;
public ushort LowerLampData4 { get; set; } = 359;
public ushort LowerLampData5 { get; set; } = 360;
public ushort LowerLampData6 { get; set; } = 361;
public ushort LeftEyeAreaCoeff { get; set; } = 212;
public ushort RightEyeAreaCoeff { get; set; } = 214;
public ushort SaveRateCorrectionCoeff { get; set; } = 428;
public ushort MiddleLamp1 { get; set; } = 362;
public ushort MiddleLamp2 { get; set; } = 363;
public ushort MiddleLamp3 { get; set; } = 364;
public ushort MiddleLamp4 { get; set; } = 365;
public ushort MiddleLamp5 { get; set; } = 366;
public ushort MiddleLamp6 { get; set; } = 367;
public ushort MiddleLamp7 { get; set; } = 368;
public ushort MotorLimit { get; set; } = 330;
public ushort ResetCompensation { get; set; } = 340; // 新增复位补偿
@@ -58,7 +69,11 @@
}
public enum ParameterDataType
{
Int16, // 16位整数
Float32 // 32位浮点数
}
public interface IPlcService
@@ -75,6 +90,10 @@
Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count);
Task WriteSingleRegisterAsync(ushort registerAddress, ushort value);
float UshortToFloat(ushort P1, ushort P2);
Task WriteMultipleRegistersAsync(ushort registerAddress, float value);
}
}

View File

@@ -104,6 +104,51 @@ namespace MembranePoreTester.Communication
return await ReadFloatAsync(startAddress);
}
/// <summary>
/// ushort转为float类型
/// </summary>
/// <param name="P1"></param>
/// <param name="P2"></param>
/// <returns>float型数据</returns>
public float UshortToFloat(ushort P1, ushort P2)
{
int intSign, intSignRest, intExponent, intExponentRest;
float faResult, faDigit;
intSign = P1 / 32768;
intSignRest = P1 % 32768;
intExponent = intSignRest / 128;
intExponentRest = intSignRest % 128;
faDigit = (float)(intExponentRest * 65536 + P2) / 8388608;
faResult = (float)Math.Pow(-1, intSign) * (float)Math.Pow(2, intExponent - 127) * (faDigit + 1);
return faResult;
}
public async Task WriteMultipleRegistersAsync(ushort registerAddress, float value)
{
await EnsureConnectedAsync();
await Task.Delay(100);
await _master.WriteMultipleRegistersAsync(_config.SlaveId, registerAddress, SplitFloatToUShortArray((float)value));
}
/// <summary>
/// Float转为Ushort数组发送
/// </summary>
/// <param name="value"></param>
/// <returns>返回ushort数组</returns>
public ushort[] SplitFloatToUShortArray(float value)
{
byte[] floatBytes = BitConverter.GetBytes(value);
ushort[] ushortArray = new ushort[floatBytes.Length / 2];
for (int i = 0, j = 0; i < floatBytes.Length; i += 2, j++)
{
ushortArray[j] = BitConverter.ToUInt16(floatBytes, i);
}
return ushortArray;
}
public void Dispose()
{

263
ViewModels/TestViewModel.cs Normal file
View File

@@ -0,0 +1,263 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Windows.Forms;
using System.Windows.Threading;
namespace MembranePoreTester
{
public partial class TestViewModel : ObservableObject
{
private readonly DispatcherTimer _angleUpdateTimer;
private bool _isTestRunning;
public TestViewModel()
{
_angleUpdateTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(100)
};
_angleUpdateTimer.Tick += AngleUpdateTimer_Tick;
// 默认值
ResolutionAngle = 0.1;
RotationSpeed = 30;
LeftEyeCoeff = 1.0;
RightEyeCoeff = 1.0;
RetentionCorrectionCoeff = 1.0;
IsSampleMode = true;
}
// 当前模式显示文本
[ObservableProperty]
private string _currentMode = "试样测试";
// 模式切换
private bool _isBlankMode;
public bool IsBlankMode
{
get => _isBlankMode;
set
{
if (SetProperty(ref _isBlankMode, value) && value)
{
CurrentMode = "空白模式";
IsSampleMode = false;
OnModeChanged();
}
}
}
private bool _isSampleMode;
public bool IsSampleMode
{
get => _isSampleMode;
set
{
if (SetProperty(ref _isSampleMode, value) && value)
{
CurrentMode = "试样测试";
IsBlankMode = false;
OnModeChanged();
}
}
}
// 参数设置
[ObservableProperty]
private double _resolutionAngle;
[ObservableProperty]
private double _rotationSpeed;
[ObservableProperty]
private double _currentAngle;
// 视野面积数据
[ObservableProperty]
private double _leftEyeArea;
[ObservableProperty]
private double _rightEyeArea;
[ObservableProperty]
private double _binocularArea;
[ObservableProperty]
private double _lowerField;
[ObservableProperty]
private double _blankArea;
[ObservableProperty]
private double _fieldRetentionRate;
// 灯条数据
[ObservableProperty]
private double _upperLampData;
[ObservableProperty]
private double _middleLampData;
[ObservableProperty]
private double _lowerLampData;
// 面积系数
[ObservableProperty]
private double _leftEyeCoeff;
[ObservableProperty]
private double _rightEyeCoeff;
[ObservableProperty]
private double _retentionCorrectionCoeff;
// 命令
[RelayCommand]
private void Reset()
{
CurrentAngle = 0;
// 用户在此添加硬件复位代码
}
[RelayCommand]
private void OpenLeftEye()
{
// 用户实现左眼开逻辑
UpdateLeftEyeArea();
}
[RelayCommand]
private void OpenRightEye()
{
// 用户实现右眼开逻辑
UpdateRightEyeArea();
}
[RelayCommand]
private void Reverse()
{
// 反转方向
RotationSpeed = -Math.Abs(RotationSpeed);
}
[RelayCommand]
private void Forward()
{
// 正转方向
RotationSpeed = Math.Abs(RotationSpeed);
}
[RelayCommand]
private void StartTest()
{
if (_isTestRunning) return;
_isTestRunning = true;
_angleUpdateTimer.Start();
}
[RelayCommand]
private void StopTest()
{
_isTestRunning = false;
_angleUpdateTimer.Stop();
}
// 导航命令(供用户跳转页面)
[RelayCommand]
private void NavigateHome()
{
// 用户实现:关闭当前窗口或切换主窗口内容
}
[RelayCommand]
private void NavigateTest()
{
// 已在测试界面,可忽略
}
[RelayCommand]
private void NavigateData()
{
// 跳转到数据记录界面
}
[RelayCommand]
private void NavigateRecord()
{
// 跳转到记录画面
}
// 内部辅助方法
private void OnModeChanged()
{
if (IsBlankMode)
{
BlankArea = 0;
FieldRetentionRate = 0;
}
else
{
CalculateFieldAreas();
}
}
private void AngleUpdateTimer_Tick(object sender, EventArgs e)
{
if (!_isTestRunning) return;
// 模拟角度变化
CurrentAngle += RotationSpeed * 0.1;
if (CurrentAngle >= 360) CurrentAngle -= 360;
if (CurrentAngle < 0) CurrentAngle += 360;
CalculateFieldAreas();
UpdateLampData();
}
private void CalculateFieldAreas()
{
double angle = CurrentAngle;
LeftEyeArea = Math.Round(50 + 30 * Math.Sin(angle * Math.PI / 180) * LeftEyeCoeff, 3);
RightEyeArea = Math.Round(50 + 30 * Math.Cos(angle * Math.PI / 180) * RightEyeCoeff, 3);
BinocularArea = Math.Round(LeftEyeArea + RightEyeArea - 20, 3);
LowerField = Math.Round(30 + 20 * Math.Sin(angle * Math.PI / 180), 1);
if (IsBlankMode)
{
BlankArea = Math.Round(10 + 5 * Math.Sin(angle * Math.PI / 180), 3);
FieldRetentionRate = BinocularArea > 0
? Math.Round((BinocularArea - BlankArea) / BinocularArea * 100, 2)
: 0;
}
else
{
FieldRetentionRate = Math.Round(BinocularArea / 100 * 100 * RetentionCorrectionCoeff, 2);
if (FieldRetentionRate > 100) FieldRetentionRate = 100;
if (FieldRetentionRate < 0) FieldRetentionRate = 0;
}
}
private void UpdateLeftEyeArea()
{
LeftEyeArea = Math.Round(LeftEyeArea + 5, 3);
if (LeftEyeArea > 100) LeftEyeArea = 100;
CalculateFieldAreas();
}
private void UpdateRightEyeArea()
{
RightEyeArea = Math.Round(RightEyeArea + 5, 3);
if (RightEyeArea > 100) RightEyeArea = 100;
CalculateFieldAreas();
}
private void UpdateLampData()
{
Random rand = new Random();
UpperLampData = Math.Round(rand.NextDouble() * 100, 2);
MiddleLampData = Math.Round(rand.NextDouble() * 100, 2);
LowerLampData = Math.Round(rand.NextDouble() * 100, 2);
}
}
}

View File

@@ -2,11 +2,87 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MembranePoreTester"
Title="设备控制系统"
Width="1024" Height="768"
Title="智能设备控制平台"
Width="1024" MinHeight="768" WindowState="Maximized"
WindowStartupLocation="CenterScreen"
ResizeMode="NoResize"
Background="{StaticResource WindowBackground}">
ResizeMode="CanResize"
Background="#F5F7FA" FontFamily="Segoe UI">
<Window.Resources>
<!-- 统一按钮样式 -->
<Style TargetType="Button">
<Setter Property="Background" Value="#2196F3"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="12,6"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}" CornerRadius="4" Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#1976D2"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#0D47A1"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- 统一文本框样式 -->
<Style TargetType="TextBox">
<Setter Property="Height" Value="28"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="#D0D3D9"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="5,2"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<!-- 统一标签样式 -->
<Style TargetType="Label">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Foreground" Value="#2C3E50"/>
<Setter Property="Margin" Value="5,5,5,5"/>
</Style>
<!-- 统一GroupBox样式 -->
<Style TargetType="GroupBox">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="#E9ECF0"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="0,0,0,12"/>
<Setter Property="Padding" Value="12"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupBox">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="6">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{TemplateBinding Header}" FontWeight="SemiBold" FontSize="14" Margin="0,0,0,12"/>
<ContentPresenter Grid.Row="1" Content="{TemplateBinding Content}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -14,25 +90,31 @@
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 标题 -->
<TextBlock Grid.Row="0" Text="智能设备控制平台" FontSize="24" FontWeight="Bold"
Foreground="White" HorizontalAlignment="Center" Margin="0,0,0,12"/>
<!-- 标题 -->
<Border Grid.Row="0" Background="#2196F3" CornerRadius="6" Padding="12" Margin="0,0,0,12">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="智能设备控制平台" FontSize="24" FontWeight="Bold" Foreground="White"/>
<TextBlock Text="v1.0" FontSize="14" Foreground="#E3F2FD" VerticalAlignment="Bottom" Margin="10,0,0,5"/>
</StackPanel>
</Border>
<!-- 主要内容区 -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="2.5*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 左侧参数区 -->
<ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto">
<ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="0,0,12,0">
<!-- 灯条数据组 -->
<GroupBox Header="灯条数据">
<GroupBox Header="📊 灯条数据">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
@@ -44,46 +126,46 @@
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 第1行 -->
<Label Grid.Row="0" Grid.Column="0" Content="上灯条数据1"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding DeviceData.UpperLampData1, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<Label Grid.Row="1" Grid.Column="0" Content="下灯条数据1"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding DeviceData.LowerLampData1, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<Label Grid.Row="2" Grid.Column="0" Content="上灯条数据2"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding DeviceData.UpperLampData2, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<Label Grid.Row="3" Grid.Column="0" Content="下灯条数据2"/>
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding DeviceData.LowerLampData2, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<Label Grid.Row="4" Grid.Column="0" Content="上灯条数据3"/>
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding DeviceData.UpperLampData3, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<Label Grid.Row="5" Grid.Column="0" Content="下灯条数据3"/>
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding DeviceData.LowerLampData3, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBox IsReadOnly="True" Name="txtUpperLampData1" Grid.Row="0" Grid.Column="1" Text="{Binding DeviceData.UpperLampData1, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<Label Grid.Row="0" Grid.Column="2" Content="上灯条数据4"/>
<TextBox Grid.Row="0" Grid.Column="3" Text="{Binding DeviceData.UpperLampData4, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBox IsReadOnly="True" Name="txtUpperLampData4" Grid.Row="0" Grid.Column="3" Text="{Binding DeviceData.UpperLampData4, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<!-- 第2行 -->
<Label Grid.Row="1" Grid.Column="0" Content="下灯条数据1"/>
<TextBox IsReadOnly="True" Grid.Row="1" Name="txtLowerLampData1" Grid.Column="1" Text="{Binding DeviceData.LowerLampData1, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<Label Grid.Row="1" Grid.Column="2" Content="下灯条数据4"/>
<TextBox Grid.Row="1" Grid.Column="3" Text="{Binding DeviceData.LowerLampData4, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBox IsReadOnly="True" Grid.Row="1" Name="txtLowerLampData4" Grid.Column="3" Text="{Binding DeviceData.LowerLampData4, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<!-- 第3行 -->
<Label Grid.Row="2" Grid.Column="0" Content="上灯条数据2"/>
<TextBox IsReadOnly="True" Name="txtUpperLampData2" Grid.Row="2" Grid.Column="1" Text="{Binding DeviceData.UpperLampData2, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<Label Grid.Row="2" Grid.Column="2" Content="上灯条数据5"/>
<TextBox Grid.Row="2" Grid.Column="3" Text="{Binding DeviceData.UpperLampData5, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBox IsReadOnly="True" Name="txtUpperLampData5" Grid.Row="2" Grid.Column="3" Text="{Binding DeviceData.UpperLampData5, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<!-- 第4行 -->
<Label Grid.Row="3" Grid.Column="0" Content="下灯条数据2"/>
<TextBox IsReadOnly="True" Grid.Row="3" Name="txtLowerLampData2" Grid.Column="1" Text="{Binding DeviceData.LowerLampData2, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<Label Grid.Row="3" Grid.Column="2" Content="下灯条数据5"/>
<TextBox Grid.Row="3" Grid.Column="3" Text="{Binding DeviceData.LowerLampData5, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBox IsReadOnly="True" Name="txtLowerLampData5" Grid.Row="3" Grid.Column="3" Text="{Binding DeviceData.LowerLampData5, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<!-- 第5行 -->
<Label Grid.Row="4" Grid.Column="0" Content="上灯条数据3"/>
<TextBox IsReadOnly="True" Name="txtUpperLampData3" Grid.Row="4" Grid.Column="1" Text="{Binding DeviceData.UpperLampData3, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<Label Grid.Row="4" Grid.Column="2" Content="上灯条数据6"/>
<TextBox Grid.Row="4" Grid.Column="3" Text="{Binding DeviceData.UpperLampData6, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBox IsReadOnly="True" Name="txtUpperLampData6" Grid.Row="4" Grid.Column="3" Text="{Binding DeviceData.UpperLampData6, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<!-- 第6行 -->
<Label Grid.Row="5" Grid.Column="0" Content="下灯条数据3"/>
<TextBox IsReadOnly="True" Grid.Row="5" Name="txtLowerLampData3" Grid.Column="1" Text="{Binding DeviceData.LowerLampData3, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<Label Grid.Row="5" Grid.Column="2" Content="下灯条数据6"/>
<TextBox Grid.Row="5" Grid.Column="3" Text="{Binding DeviceData.LowerLampData6, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBox IsReadOnly="True" Name="txtLowerLampData6" Grid.Row="5" Grid.Column="3" Text="{Binding DeviceData.LowerLampData6, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
</Grid>
</GroupBox>
<!-- 面积系数 -->
<GroupBox Header="面积系数">
<!-- 面积系数与保存率矫正系数 -->
<GroupBox Header="📐 面积系数">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -96,53 +178,53 @@
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="左眼面积系数"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding DeviceData.LeftEyeAreaCoeff, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBox x:Name="txtLeftEyeAreaCoeff" Grid.Row="0" Grid.Column="1" Text="{Binding DeviceData.LeftEyeAreaCoeff, UpdateSourceTrigger=PropertyChanged}" Width="120"/>
<Label Grid.Row="1" Grid.Column="0" Content="右眼面积系数"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding DeviceData.RightEyeAreaCoeff, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBox x:Name="txtRightEyeAreaCoeff" Grid.Row="1" Grid.Column="1" Text="{Binding DeviceData.RightEyeAreaCoeff, UpdateSourceTrigger=PropertyChanged}" Width="120"/>
<Label Grid.Row="2" Grid.Column="0" Content="保存率矫正系数"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding DeviceData.SaveRateCorrectionCoeff, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBox x:Name="txtSaveRateCorrectionCoeff" Grid.Row="2" Grid.Column="1" Text="{Binding DeviceData.SaveRateCorrectionCoeff, UpdateSourceTrigger=PropertyChanged}" Width="120"/>
</Grid>
</GroupBox>
<!-- 中灯数据组 -->
<GroupBox Header="中灯数据">
<GroupBox Header="💡 中灯数据">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 第1行 -->
<Label Grid.Row="0" Grid.Column="0" Content="中灯1"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp1, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBox IsReadOnly="True" Name="txtMiddleLamp1" Grid.Row="0" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp1, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<Label Grid.Row="0" Grid.Column="2" Content="中灯5"/>
<TextBox IsReadOnly="True" Name="txtMiddleLamp5" Grid.Row="0" Grid.Column="3" Text="{Binding DeviceData.MiddleLamp5, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<!-- 第2行 -->
<Label Grid.Row="1" Grid.Column="0" Content="中灯2"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp2, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBox IsReadOnly="True" Name="txtMiddleLamp2" Grid.Row="1" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp2, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<Label Grid.Row="1" Grid.Column="2" Content="中灯6"/>
<TextBox IsReadOnly="True" Name="txtMiddleLamp6" Grid.Row="1" Grid.Column="3" Text="{Binding DeviceData.MiddleLamp6, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<!-- 第3行 -->
<Label Grid.Row="2" Grid.Column="0" Content="中灯3"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp3, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBox IsReadOnly="True" Name="txtMiddleLamp3" Grid.Row="2" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp3, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<Label Grid.Row="2" Grid.Column="2" Content="中灯7"/>
<TextBox IsReadOnly="True" Name="txtMiddleLamp7" Grid.Row="2" Grid.Column="3" Text="{Binding DeviceData.MiddleLamp7, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<!-- 第4行 -->
<Label Grid.Row="3" Grid.Column="0" Content="中灯4"/>
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp4, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<Label Grid.Row="4" Grid.Column="0" Content="中灯5"/>
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp5, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<Label Grid.Row="5" Grid.Column="0" Content="中灯6"/>
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp6, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<Label Grid.Row="6" Grid.Column="0" Content="中灯7"/>
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp7, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBox IsReadOnly="True" Name="txtMiddleLamp4" Grid.Row="3" Grid.Column="1" Text="{Binding DeviceData.MiddleLamp4, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
</Grid>
</GroupBox>
</StackPanel>
@@ -150,40 +232,49 @@
<!-- 右侧控制区 -->
<StackPanel Grid.Column="1" Margin="12,0,0,0">
<!-- 电机限位 -->
<GroupBox Header="电机状态">
<!-- 电机状态 -->
<GroupBox Header="⚙️ 电机状态">
<StackPanel Margin="5">
<Label Content="电机限位"/>
<TextBox Text="{Binding DeviceData.MotorLimit, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,12"/>
<Button Content="保存参数" Command="{Binding SaveParametersCommand}" Margin="0,5"/>
<Button Content="读取参数" Command="{Binding LoadParametersCommand}" Margin="0,5"/>
<Label Content="电机限位" FontWeight="SemiBold"/>
<TextBox x:Name="txtMotorLimit" Text="{Binding DeviceData.MotorLimit, UpdateSourceTrigger=PropertyChanged}" Height="28" Margin="0,0,0,12"/>
<Label Content="复位补偿" FontWeight="SemiBold"/>
<TextBox x:Name="txtResetCompensation" Text="{Binding DeviceData.MotorLimit, UpdateSourceTrigger=PropertyChanged}" Height="28" Margin="0,0,0,12"/>
<!--<Button Content="💾 保存参数" Command="{Binding SaveParametersCommand}" Margin="0,5" Background="#4CAF50"/>
<Button Content="🔄 读取参数" Command="{Binding LoadParametersCommand}" Margin="0,5" Background="#FF9800"/>-->
</StackPanel>
</GroupBox>
<!-- 控制按钮组 -->
<GroupBox Header="设备控制">
<GroupBox Header="🎮 设备控制">
<WrapPanel Margin="5">
<Button Content="复位" Command="{Binding ResetCommand}" Width="80" Margin="5"/>
<Button Content="左眼开" Command="{Binding OpenLeftEyeCommand}" Width="80" Margin="5"/>
<Button Content="反转" Command="{Binding ReverseCommand}" Width="80" Margin="5"/>
<Button Content="右眼开" Command="{Binding OpenRightEyeCommand}" Width="80" Margin="5"/>
<Button Content="正转" Command="{Binding ForwardCommand}" Width="80" Margin="5"/>
<Button Content="🔄 复位" Command="{Binding ResetCommand}" Width="80" Background="#607D8B"/>
<Button Content="👁️ 左眼开" Command="{Binding OpenLeftEyeCommand}" Width="80" Background="#4CAF50"/>
<Button Content="↩️ 反转" Command="{Binding ReverseCommand}" Width="80" Background="#FF9800"/>
<Button Content="👁️ 右眼开" Command="{Binding OpenRightEyeCommand}" Width="80" Background="#4CAF50"/>
<Button Content="↪️ 正转" Command="{Binding ForwardCommand}" Width="80" Background="#FF9800"/>
</WrapPanel>
</GroupBox>
<!-- 通信状态(可选) -->
<GroupBox Header="通信状态">
<TextBlock Text="{Binding CommunicationStatus}" Foreground="{Binding StatusColor}" FontWeight="Bold" Margin="5"/>
<!-- 通信状态 -->
<GroupBox Header="📡 通信状态">
<Border Background="#F8F9FA" CornerRadius="4" Padding="10" Margin="5">
<StackPanel>
<Ellipse Width="12" Height="12" Fill="{Binding StatusColor}" Margin="0,0,0,5" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding CommunicationStatus}" FontWeight="Bold" Foreground="{Binding StatusColor}" TextWrapping="Wrap"/>
</StackPanel>
</Border>
</GroupBox>
</StackPanel>
</Grid>
<!-- 底部导航栏 -->
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,12,0,0">
<Button Content="主页" Command="{Binding NavigateHomeCommand}" Width="100" Margin="5"/>
<Button Content="测试界面" Command="{Binding NavigateTestCommand}" Width="100" Margin="5"/>
<Button Content="数据记录" Command="{Binding NavigateDataRecordCommand}" Width="100" Margin="5"/>
<Button Content="记录画面" Command="{Binding NavigateRecordScreenCommand}" Width="100" Margin="5"/>
</StackPanel>
<Border Grid.Row="2" Background="White" CornerRadius="6" Padding="10" Margin="0,12,0,0" BorderBrush="#E9ECF0" BorderThickness="1">
<WrapPanel HorizontalAlignment="Center">
<Button Content="🏠 主页" Command="{Binding NavigateHomeCommand}" Width="100" Background="#607D8B"/>
<Button Content="🧪 测试界面" Command="{Binding NavigateTestCommand}" Width="100" Background="#2196F3"/>
<Button Content="📊 数据记录" Command="{Binding NavigateDataRecordCommand}" Width="100" Background="#4CAF50"/>
<Button Content="📸 记录画面" Command="{Binding NavigateRecordScreenCommand}" Width="100" Background="#FF9800"/>
</WrapPanel>
</Border>
</Grid>
</Window>

View File

@@ -1,14 +1,189 @@
using System.Windows;
using MembranePoreTester.ViewModels;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using MembranePoreTester.Communication;
namespace MembranePoreTester
{
public partial class MainWindow : Window
{
private readonly IPlcService _plcService;
private readonly PlcConfiguration _config;
private DispatcherTimer _autoRefreshTimer;
// 文本框映射:(文本框, 地址, 参数名, 是否浮点数)
private readonly List<(TextBox textBox, ushort address, string name, bool isFloat)> _textBoxMapping = new();
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
_plcService = App.PlcService;
_config = App.PlcConfig;
// 16位整数灯条、中灯、电机
AddMapping(txtUpperLampData1, _config.UpperLampData1, "上灯条数据1", false);
AddMapping(txtLowerLampData1, _config.LowerLampData1, "下灯条数据1", false);
AddMapping(txtUpperLampData2, _config.UpperLampData2, "上灯条数据2", false);
AddMapping(txtLowerLampData2, _config.LowerLampData2, "下灯条数据2", false);
AddMapping(txtUpperLampData3, _config.UpperLampData3, "上灯条数据3", false);
AddMapping(txtLowerLampData3, _config.LowerLampData3, "下灯条数据3", false);
AddMapping(txtUpperLampData4, _config.UpperLampData4, "上灯条数据4", false);
AddMapping(txtLowerLampData4, _config.LowerLampData4, "下灯条数据4", false);
AddMapping(txtUpperLampData5, _config.UpperLampData5, "上灯条数据5", false);
AddMapping(txtLowerLampData5, _config.LowerLampData5, "下灯条数据5", false);
AddMapping(txtUpperLampData6, _config.UpperLampData6, "上灯条数据6", false);
AddMapping(txtLowerLampData6, _config.LowerLampData6, "下灯条数据6", false);
AddMapping(txtMiddleLamp1, _config.MiddleLamp1, "中灯1", false);
AddMapping(txtMiddleLamp2, _config.MiddleLamp2, "中灯2", false);
AddMapping(txtMiddleLamp3, _config.MiddleLamp3, "中灯3", false);
AddMapping(txtMiddleLamp4, _config.MiddleLamp4, "中灯4", false);
AddMapping(txtMiddleLamp5, _config.MiddleLamp5, "中灯5", false);
AddMapping(txtMiddleLamp6, _config.MiddleLamp6, "中灯6", false);
AddMapping(txtMiddleLamp7, _config.MiddleLamp7, "中灯7", false);
AddMapping(txtMotorLimit, _config.MotorLimit, "电机限位", true);
AddMapping(txtResetCompensation, _config.ResetCompensation, "复位补偿", true);
AddMapping(txtLeftEyeAreaCoeff, _config.LeftEyeAreaCoeff, "左眼面积系数", true);
AddMapping(txtRightEyeAreaCoeff, _config.RightEyeAreaCoeff, "右眼面积系数", true);
AddMapping(txtSaveRateCorrectionCoeff, _config.SaveRateCorrectionCoeff, "保存率矫正系数", true);
RegisterTextBoxEvents();
Loaded += async (s, e) =>
{
_autoRefreshTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_autoRefreshTimer.Tick += AutoRefreshTimer_Tick;
_autoRefreshTimer.Start();
await LoadAllParameters();
};
Closed += (s, e) => _autoRefreshTimer?.Stop();
}
private void AddMapping(TextBox textBox, ushort address, string name, bool isFloat)
{
if (textBox != null)
{
_textBoxMapping.Add((textBox, address, name, isFloat));
}
}
private void RegisterTextBoxEvents()
{
foreach (var (tb, addr, name, isFloat) in _textBoxMapping)
{
tb.LostFocus += async (s, e) =>
{
if (float.TryParse(tb.Text, out float value))
{
try
{
if (isFloat)
await WriteFloatAsync(addr, value);
else
await WriteInt16Async(addr, (ushort)value);
System.Diagnostics.Debug.WriteLine($"{name} 已写入: {value}");
}
catch (Exception ex)
{
MessageBox.Show($"写入 {name} 失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
else
{
MessageBox.Show($"请输入有效的数值", "输入错误", MessageBoxButton.OK, MessageBoxImage.Warning);
await UpdateSingleParameter(tb, addr, isFloat);
}
};
}
}
private async void AutoRefreshTimer_Tick(object sender, EventArgs e)
{
await Dispatcher.InvokeAsync(async () =>
{
foreach (var (tb, addr, name, isFloat) in _textBoxMapping)
{
if (!tb.IsFocused)
{
try
{
if (isFloat)
{
float val = await ReadFloatAsync(addr);
tb.Text = val.ToString("F3");
}
else
{
ushort val = await ReadInt16Async(addr);
tb.Text = val.ToString();
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"自动刷新 {name} 失败: {ex.Message}");
}
}
}
});
}
private async Task UpdateSingleParameter(TextBox tb, ushort addr, bool isFloat)
{
try
{
if (isFloat)
{
float val = await ReadFloatAsync(addr);
tb.Text = val.ToString("F3");
}
else
{
ushort val = await ReadInt16Async(addr);
tb.Text = val.ToString();
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"更新失败: {ex.Message}");
}
}
private async Task LoadAllParameters()
{
foreach (var (tb, addr, name, isFloat) in _textBoxMapping)
{
await UpdateSingleParameter(tb, addr, isFloat);
}
}
// 16位整数读写
private async Task<ushort> ReadInt16Async(ushort address)
{
var registers = await ((ModbusTcpPlcService)_plcService).ReadHoldingRegistersAsync(address, 1);
return registers[0];
}
private async Task WriteInt16Async(ushort address, ushort value)
{
await ((ModbusTcpPlcService)_plcService).WriteSingleRegisterAsync(address, value);
}
// 32位浮点数读写
private async Task<float> ReadFloatAsync(ushort address)
{
var registers = await ((ModbusTcpPlcService)_plcService).ReadHoldingRegistersAsync(address, 2);
return ((ModbusTcpPlcService)_plcService).UshortToFloat(registers[1], registers[0]);
}
private async Task WriteFloatAsync(ushort address, float value)
{
await ((ModbusTcpPlcService)_plcService).WriteMultipleRegistersAsync(address, value);
}
}
}

245
Views/TestView.xaml Normal file
View File

@@ -0,0 +1,245 @@
<Window x:Class="MembranePoreTester.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="测试界面"
Width="1024" MinHeight="768" WindowState="Maximized"
WindowStartupLocation="CenterOwner"
Background="#F5F7FA" FontFamily="Segoe UI" KeyDown="Window_KeyDown">
<Window.Resources>
<!-- 统一按钮样式(与 MainWindow 一致) -->
<Style TargetType="Button">
<Setter Property="Background" Value="#2196F3"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="12,6"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}" CornerRadius="4" Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#1976D2"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#0D47A1"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- 统一文本框样式 -->
<Style TargetType="TextBox">
<Setter Property="Height" Value="28"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="#D0D3D9"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="5,2"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<!-- 只读文本框样式 -->
<Style x:Key="ReadOnlyTextBox" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="Background" Value="#F5F5F5"/>
<Setter Property="Foreground" Value="#2196F3"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<!-- 统一标签样式 -->
<Style TargetType="Label">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Foreground" Value="#2C3E50"/>
<Setter Property="Margin" Value="5"/>
</Style>
<!-- 统一GroupBox样式 -->
<Style TargetType="GroupBox">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="#E9ECF0"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="0,0,0,12"/>
<Setter Property="Padding" Value="12"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupBox">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="6">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{TemplateBinding Header}" FontWeight="SemiBold" FontSize="14" Margin="0,0,0,12"/>
<ContentPresenter Grid.Row="1" Content="{TemplateBinding Content}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 统一 RadioButton 样式 -->
<Style TargetType="RadioButton">
<Setter Property="FontSize" Value="12"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Margin" Value="5"/>
</Style>
</Window.Resources>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 标题栏(蓝底卡片) -->
<Border Grid.Row="0" Background="#2196F3" CornerRadius="6" Padding="12" Margin="0,0,0,12">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="测试界面" FontSize="20" FontWeight="Bold" Foreground="White" VerticalAlignment="Center"/>
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="当前模式:" Foreground="White" FontSize="14" VerticalAlignment="Center" Margin="0,0,10,0"/>
<Border Background="White" CornerRadius="20" Padding="12,4">
<TextBlock Text="{Binding CurrentMode}" Foreground="#2196F3" FontSize="14" FontWeight="Bold"/>
</Border>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" >
<RadioButton Content="空白模式" IsChecked="{Binding IsBlankMode}" GroupName="TestMode" Foreground="White" FontSize="13"/>
<RadioButton Content="试样测试" IsChecked="{Binding IsSampleMode}" GroupName="TestMode" Foreground="White" FontSize="13" Margin="10,5,0,0"/>
</StackPanel>
</Grid>
</Border>
<!-- 参数设置区域 -->
<GroupBox Grid.Row="1" Header="⚙️ 参数设置" Margin="0,0,0,12">
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Center">
<Label Content="分辨角度:" Margin="0,0,5,0"/>
<TextBox Text="{Binding ResolutionAngle, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<Label Content="°" Margin="5,0,0,0"/>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center">
<Label Content="转动速度:" Margin="0,0,5,0"/>
<TextBox Text="{Binding RotationSpeed, UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<Label Content="°/s" Margin="5,0,0,0"/>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Center">
<Label Content="当前角度:" Margin="0,0,5,0"/>
<TextBox Text="{Binding CurrentAngle, UpdateSourceTrigger=PropertyChanged}" Width="80" Background="#F5F5F5" Foreground="#2196F3" FontWeight="Bold"/>
<Label Content="°" Margin="5,0,0,0"/>
</StackPanel>
</Grid>
</GroupBox>
<!-- 视野面积数据区域 -->
<GroupBox Grid.Row="2" Header="👁️ 视野面积数据" Margin="0,0,0,12">
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Horizontal" Margin="5">
<Label Content="左目视野面积:" Width="110"/>
<TextBox Text="{Binding LeftEyeArea}" Width="100" IsReadOnly="True" Background="#F5F5F5" Foreground="#2196F3" FontWeight="Bold"/>
<Label Content="cm²" Margin="5,0,0,0"/>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Margin="5">
<Label Content="右目视野面积:" Width="110"/>
<TextBox Text="{Binding RightEyeArea}" Width="100" IsReadOnly="True" Background="#F5F5F5" Foreground="#2196F3" FontWeight="Bold"/>
<Label Content="cm²" Margin="5,0,0,0"/>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal" Margin="5">
<Label Content="双目视野面积:" Width="110"/>
<TextBox Text="{Binding BinocularArea}" Width="100" IsReadOnly="True" Background="#F5F5F5" Foreground="#2196F3" FontWeight="Bold"/>
<Label Content="cm²" Margin="5,0,0,0"/>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" Margin="5">
<Label Content="下方视野:" Width="110"/>
<TextBox Text="{Binding LowerField}" Width="100" IsReadOnly="True" Background="#F5F5F5" Foreground="#2196F3" FontWeight="Bold"/>
<Label Content="°" Margin="5,0,0,0"/>
</StackPanel>
<StackPanel Grid.Row="2" Grid.Column="0" Orientation="Horizontal" Margin="5">
<Label Content="空白视野面积:" Width="110"/>
<TextBox Text="{Binding BlankArea}" Width="100" IsReadOnly="True" Background="#F5F5F5" Foreground="#2196F3" FontWeight="Bold"/>
<Label Content="cm²" Margin="5,0,0,0"/>
</StackPanel>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" Margin="5">
<Label Content="视野保存率:" Width="110"/>
<TextBox Text="{Binding FieldRetentionRate}" Width="100" IsReadOnly="True" Background="#F5F5F5" Foreground="#2196F3" FontWeight="Bold"/>
<Label Content="%" Margin="5,0,0,0"/>
</StackPanel>
</Grid>
</GroupBox>
<!-- 控制按钮区域 -->
<GroupBox Grid.Row="3" Header="🎮 设备控制" Margin="0,0,0,12">
<WrapPanel HorizontalAlignment="Center" Margin="5">
<Button Content="🔄 复位" Command="{Binding ResetCommand}" Background="#607D8B"/>
<Button Content="👁️ 左眼开" Command="{Binding OpenLeftEyeCommand}" Background="#4CAF50"/>
<Button Content="↩️ 反转" Command="{Binding ReverseCommand}" Background="#FF9800"/>
<Button Content="👁️ 右眼开" Command="{Binding OpenRightEyeCommand}" Background="#4CAF50"/>
<Button Content="↪️ 正转" Command="{Binding ForwardCommand}" Background="#FF9800"/>
<Button Content="▶️ 测试" Command="{Binding StartTestCommand}" Background="#2196F3"/>
<Button Content="⏹️ 停止" Command="{Binding StopTestCommand}" Background="#F44336"/>
</WrapPanel>
</GroupBox>
<!-- 底部导航栏 -->
<Border Grid.Row="4" Background="White" CornerRadius="6" Padding="10" Margin="0,12,0,0" BorderBrush="#E9ECF0" BorderThickness="1">
<WrapPanel HorizontalAlignment="Center">
<Button Content="🏠 主页" Command="{Binding NavigateHomeCommand}" Width="100" Background="#607D8B"/>
<Button Content="🧪 测试界面" Command="{Binding NavigateTestCommand}" Width="100" Background="#2196F3"/>
<Button Content="📊 数据记录" Command="{Binding NavigateDataRecordCommand}" Width="100" Background="#4CAF50"/>
<Button Content="📸 记录画面" Command="{Binding NavigateRecordScreenCommand}" Width="100" Background="#FF9800"/>
</WrapPanel>
</Border>
</Grid>
</ScrollViewer>
</Window>

29
Views/TestView.xaml.cs Normal file
View File

@@ -0,0 +1,29 @@
using MembranePoreTester.ViewModels;
using System.Windows;
using System.Windows.Input;
namespace MembranePoreTester
{
/// <summary>
/// TestView.xaml 的交互逻辑
/// </summary>
public partial class TestView : Window
{
public TestView()
{
InitializeComponent();
// 如果需要在打开时自动开始模拟,可以在此设置 DataContext
DataContext = new TestViewModel();
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.P)
{
var win = new MainWindow();
win.Owner = this;
win.ShowDialog();
}
}
}
}

View File

@@ -3,55 +3,6 @@
"IpAddress": "192.168.1.10",
"Port": 502,
"SlaveId": 1,
"PressureRegisterStation1": 6,
"PressureRegisterStation2": 8,
"PressureRegisterStation3": 10,
"PressureModeRegister": 200, // 高压/低压选择寄存器0=低压1=高压)
"PressCoil": 100, // 加压线圈M100
"StartCoil": 20, // 启动线圈M20
"EnableCoil": 21, // 使能线圈M21
"StopCoil": 7, // 停止线圈M7
"PressureFactor": 1.0,
"WetFlowRegister": 2,
"DryFlowRegister": 4,
"WetFlowFactor": 1.0,
"DryFlowFactor": 1.0,
"PressureUpperLimit": 300,
"PressureRate": 280,
"PressureCoeff": 282,
"HPCoeff1": 3120,
"LPCoeff1": 3122,
"HPCoeff2": 3124,
"LPCoeff2": 3126,
"HPCoeff3": 3128,
"LPCoeff3": 3130,
"LargeFlowCoeff1": 3048,
"SmallFlowCoeff1": 380,
"LargeFlowCoeff2": 1218,
"SmallFlowCoeff2": 1318,
"LargeFlowCoeff3": 1418,
"SmallFlowCoeff3": 1468,
"FlowModeRegister1": 5,
"FlowModeRegister2": 6,
"FlowModeRegister3": 7,
"PressureCalibZero": 3200,
"PressureCalibSpan": 3202,
"FlowCalibZero": 3204,
"FlowCalibSpan": 3206
}