This commit is contained in:
xyy
2026-03-31 20:17:17 +08:00
parent 8bd9226955
commit e14d6de146
18 changed files with 865 additions and 206 deletions

View File

@@ -3,5 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,19 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace MembranePoreTester.Converters
{
public class BoolToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value is bool b && b) ? "开" : "关";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value?.ToString() == "开";
}
}
}

View File

@@ -3,6 +3,11 @@ using System.IO;
public static class ExportHelper
{
static ExportHelper()
{
// 设置许可上下文为非商业用途(开源项目/个人学习)
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
}
public static void ExportBubblePoint(BubblePointEntity entity, string filePath)
{
using var package = new ExcelPackage();

View File

@@ -1,57 +1,141 @@
public interface ITestRecord
{
/// <summary>主键ID自增</summary>
int Id { get; set; }
/// <summary>工位编号1~3</summary>
int StationId { get; set; }
/// <summary>测试日期时间</summary>
DateTime TestDate { get; set; }
/// <summary>测试人员姓名</summary>
string Tester { get; set; }
// 公共字段
}
/// <summary>
/// 泡点法测试记录实体,用于数据库存储。
/// 对应 GB/T 32361-2015 标准中的最大孔径测试记录。
/// </summary>
public class BubblePointEntity : ITestRecord
{
/// <summary>主键ID自增</summary>
public int Id { get; set; }
/// <summary>工位编号1~3</summary>
public int StationId { get; set; }
/// <summary>测试日期时间</summary>
public DateTime TestDate { get; set; }
public string Tester { get; set; }
public string SampleType { get; set; }
public string SampleSpec { get; set; }
public double RoomTemperature { get; set; }
public double SoakingTime { get; set; }
public string LiquidName { get; set; }
public double LiquidSurfaceTension { get; set; }
public string LiquidManufacturer { get; set; }
public double BubblePointPressure { get; set; }
public string PressureUnit { get; set; }
public double MaxPoreSize { get; set; }
/// <summary>测试人员姓名</summary>
public string? Tester { get; set; }
/// <summary>膜类型(平板膜/中空纤维膜)</summary>
public string? SampleType { get; set; }
/// <summary>样品规格(如直径、厚度等)</summary>
public string? SampleSpec { get; set; }
/// <summary>测试时室温°C</summary>
public double? RoomTemperature { get; set; }
/// <summary>膜在测试液体中的浸润时间(小时)</summary>
public double? SoakingTime { get; set; }
/// <summary>测试液体名称(如“水”、“乙醇”等)</summary>
public string? LiquidName { get; set; }
/// <summary>测试液体的表面张力mN/m25°C</summary>
public double? LiquidSurfaceTension { get; set; }
/// <summary>测试液体生产厂家(可选)</summary>
public string? LiquidManufacturer { get; set; }
/// <summary>泡点压力数值</summary>
public double? BubblePointPressure { get; set; }
/// <summary>泡点压力单位Pa / cmHg / psi</summary>
public string? PressureUnit { get; set; }
/// <summary>计算得出的最大孔径μm</summary>
public double? MaxPoreSize { get; set; }
}
/// <summary>
/// 孔分布测试记录实体,用于数据库存储。
/// 对应 GB/T 32361-2015 标准中的平均流量法测试记录。
/// </summary>
public class PoreDistributionEntity : ITestRecord
{
/// <summary>主键ID自增</summary>
public int Id { get; set; }
/// <summary>工位编号1~3</summary>
public int StationId { get; set; }
/// <summary>测试日期时间</summary>
public DateTime TestDate { get; set; }
/// <summary>测试人员姓名</summary>
public string Tester { get; set; }
/// <summary>膜类型(平板膜/中空纤维膜)</summary>
public string SampleType { get; set; }
/// <summary>样品规格(如直径、厚度等)</summary>
public string SampleSpec { get; set; }
/// <summary>测试时室温°C</summary>
public double RoomTemperature { get; set; }
/// <summary>膜在测试液体中的浸润时间(小时)</summary>
public double SoakingTime { get; set; }
/// <summary>测试液体名称(如“水”、“乙醇”等)</summary>
public string LiquidName { get; set; }
/// <summary>测试液体的表面张力mN/m25°C</summary>
public double LiquidSurfaceTension { get; set; }
/// <summary>测试液体生产厂家(可选)</summary>
public string LiquidManufacturer { get; set; }
/// <summary>压力单位Pa / cmHg / psi</summary>
public string PressureUnit { get; set; }
/// <summary>泡点压力(可选,用于记录)</summary>
public double BubblePointPressure { get; set; }
/// <summary>计算得出的平均孔径μm</summary>
public double AveragePoreSize { get; set; }
// 导航属性:数据点
/// <summary>导航属性:该测试记录对应的所有压力-流量数据点</summary>
public List<DataPointEntity> DataPoints { get; set; }
}
/// <summary>
/// 孔分布测试的数据点实体,存储每个压力点对应的湿膜和干膜流量。
/// 与 PoreDistributionEntity 构成一对多的关系。
/// </summary>
public class DataPointEntity
{
/// <summary>主键ID自增</summary>
public int Id { get; set; }
/// <summary>关联的孔分布测试记录ID</summary>
public int PoreDistributionId { get; set; }
/// <summary>测试压力值,单位由所属记录的 PressureUnit 决定</summary>
public double Pressure { get; set; }
/// <summary>湿膜流量L/min</summary>
public double WetFlow { get; set; }
/// <summary>干膜流量L/min</summary>
public double DryFlow { get; set; }
/// <summary>导航属性:所属的孔分布测试记录</summary>
public PoreDistributionEntity PoreDistribution { get; set; }
}

View File

@@ -45,7 +45,7 @@ namespace MembranePoreTester.Models
}
}
}
public string PressureUnit { get; set; } // Pa/cmHg/psi
public string PressureUnit { get; set; } = "Pa"; // Pa/cmHg/psi
public DateTime TestDate { get; set; } = DateTime.Now;
public string Tester { get; set; }

View File

@@ -57,7 +57,7 @@ namespace MembranePoreTester.Models
/// <summary>
/// 测试人员姓名。
/// </summary>
public string Tester { get; set; }
public string Tester { get; set; } = "系统测试员";
// ---------- 计算结果(由计算逻辑填充) ----------

View File

@@ -158,16 +158,26 @@ namespace MembranePoreTester.ViewModels
});
}
private async Task ReadPlcAsync()
{
if (IsDisposed) return;
Record.BubblePointPressure = Record.BubbleCurrentPressure;
OnPropertyChanged(nameof(Record.BubblePointPressure));
try
{
double rawPressure = await _plcService.ReadPressureAsync(StationId);
rawPressure = Math.Round((double)rawPressure, 2);
double pressure = rawPressure * _plcConfig.PressureFactor;
// 更新当前压力显示
Record.BubbleCurrentPressure = pressure;
// 同时将泡点压力设为该值(符合涨破测试逻辑)
Record.BubblePointPressure = pressure;
OnPropertyChanged(nameof(Record.BubblePointPressure));
OnPropertyChanged(nameof(Record.BubbleCurrentPressure));
}
catch (Exception ex)
{
MessageBox.Show($"读取PLC失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -207,12 +217,12 @@ namespace MembranePoreTester.ViewModels
{
StationId = StationId,
TestDate = Record.TestDate,
Tester = Record.Tester,
SampleType = Record.SampleType,
SampleSpec = Record.SampleSpec,
Tester = Record.Tester ?? "系统操作员",
SampleType = Record.SampleType ?? "缺省值",
SampleSpec = Record.SampleSpec ?? "缺省值",
RoomTemperature = Record.RoomTemperature,
SoakingTime = Record.SoakingTime,
LiquidName = Record.Liquid?.Name,
LiquidName = Record.Liquid?.Name ?? "缺省值",
LiquidSurfaceTension = Record.Liquid?.SurfaceTension ?? 0,
LiquidManufacturer = Record.LiquidManufacturer,
BubblePointPressure = Record.BubblePointPressure,
@@ -232,12 +242,12 @@ namespace MembranePoreTester.ViewModels
Record.SampleType = entity.SampleType;
Record.SampleSpec = entity.SampleSpec;
Record.RoomTemperature = entity.RoomTemperature;
Record.SoakingTime = entity.SoakingTime;
Record.RoomTemperature = entity.RoomTemperature ?? 0;
Record.SoakingTime = entity.SoakingTime ?? 0;
Record.Liquid = TestLiquid.Predefined.FirstOrDefault(l => l.Name == entity.LiquidName)
?? new TestLiquid { Name = entity.LiquidName, SurfaceTension = entity.LiquidSurfaceTension };
?? new TestLiquid { Name = entity.LiquidName, SurfaceTension = entity.LiquidSurfaceTension ?? 0 };
Record.LiquidManufacturer = entity.LiquidManufacturer;
Record.BubblePointPressure = entity.BubblePointPressure;
Record.BubblePointPressure = entity.BubblePointPressure ?? 0;
Record.PressureUnit = entity.PressureUnit;
Record.TestDate = entity.TestDate;
Record.Tester = entity.Tester;
@@ -316,8 +326,23 @@ namespace MembranePoreTester.ViewModels
};
if (saveFileDialog.ShowDialog() == true)
{
// 转换为Entity后导出
var entity = new BubblePointEntity { /* 从Record复制 */ };
// 将 Record 转换为 Entity
var entity = new BubblePointEntity
{
StationId = this.StationId,
TestDate = Record.TestDate,
Tester = Record.Tester,
SampleType = Record.SampleType,
SampleSpec = Record.SampleSpec,
RoomTemperature = Record.RoomTemperature,
SoakingTime = Record.SoakingTime,
LiquidName = Record.Liquid?.Name,
LiquidSurfaceTension = Record.Liquid?.SurfaceTension,
LiquidManufacturer = Record.LiquidManufacturer,
BubblePointPressure = Record.BubblePointPressure,
PressureUnit = Record.PressureUnit,
MaxPoreSize = Record.MaxPoreSize
};
ExportHelper.ExportBubblePoint(entity, saveFileDialog.FileName);
MessageBox.Show("导出成功");
}

View File

@@ -91,59 +91,44 @@ namespace MembranePoreTester.ViewModels
private async void AutoCollectTimer_Tick(object sender, EventArgs e)
{
if (!IsActive) return; // 不在当前标签页,跳过采集
await SafeExecuteAsync($"AutoCollect_Station{StationId}", async () =>
if (!IsActive) return;
try
{
try
// 1. 读取当前压力
float rawPressure = await _plcService.ReadPressureAsync(StationId);
double pressure = Math.Round(rawPressure * _plcConfig.PressureFactor, 2);
// 2. 读取当前模式对应的流量
double flow = 0;
if (TestMode.Contains("湿膜"))
{
// 1. 读取当前压力
float rawPressure = await _plcService.ReadPressureAsync(StationId);
double pressure = Math.Round(rawPressure, 2);
double flow = 0;
if (TestMode == "湿膜")
{
float rawFlow = await _plcService.ReadWetFlowAsync();
flow = rawFlow;
}
else
{
float rawFlow = await _plcService.ReadDryFlowAsync();
flow = rawFlow;
}
flow = Math.Round(flow, 2);
// 3. 在 DataPoints 中查找是否存在相同压力的行(允许微小误差)
var existing = Record.DataPoints.FirstOrDefault(p => Math.Abs(p.Pressure - pressure) < 0.001);
if (existing != null)
{
// 更新对应列
if (TestMode == "湿膜")
existing.WetFlow = flow;
else
existing.DryFlow = flow;
}
else
{
// 新增一行
var newPoint = new Models.DataPoint { Pressure = pressure };
if (TestMode == "湿膜")
newPoint.WetFlow = flow;
else
newPoint.DryFlow = flow;
Record.DataPoints.Add(newPoint);
}
// 4. 刷新曲线
UpdatePlot();
float rawFlow = await _plcService.ReadWetFlowAsync();
flow = Math.Round(rawFlow, 2);
}
catch (Exception ex)
else
{
System.Diagnostics.Debug.WriteLine($"自动采集失败: {ex.Message}");
float rawFlow = await _plcService.ReadDryFlowAsync();
flow = Math.Round(rawFlow, 2);
}
});
// 3. 直接新增一行(不查找相同压力)
var newPoint = new Models.DataPoint
{
Pressure = pressure,
WetFlow = TestMode.Contains("湿膜") ? flow : 0,
DryFlow = TestMode.Contains("干膜") ? flow : 0
};
Record.DataPoints.Add(newPoint);
// 4. 刷新曲线
UpdatePlot();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"自动采集失败: {ex.Message}");
}
}
/// <summary>
/// 清空所有数据点,重置表格和曲线
/// </summary>
@@ -285,7 +270,10 @@ namespace MembranePoreTester.ViewModels
Record.DataPoints.CollectionChanged += (s, e) => UpdatePlot();
// 加到构造函数最后即可
ClearAllCommand = new RelayCommand(ClearAllData);
RemoveDataPointCommand = new RelayCommand(RemoveSelectedDataPoint, () => SelectedDataPoint != null);
// 延迟2秒后读取确保连接稳定
Task.Delay(2000).ContinueWith(async _ =>
@@ -295,6 +283,11 @@ namespace MembranePoreTester.ViewModels
}
private void ClearAllData()
{
ClearData();
}
private async Task ReadPressureModeAsync()
{
await SafeExecuteAsync($"ReadPressureModeAsync{StationId}", async () =>
@@ -338,7 +331,7 @@ namespace MembranePoreTester.ViewModels
// 始终读取压力
float rawPressure = await _plcService.ReadPressureAsync(StationId);
double pressure = rawPressure;
double pressure = Math.Round(rawPressure * _plcConfig.PressureFactor, 2);
if (SelectedDataPoint != null)
{
@@ -495,14 +488,14 @@ namespace MembranePoreTester.ViewModels
{
StationId = this.StationId,
TestDate = Record.TestDate,
Tester = Record.Tester,
Tester = Record.Tester ?? "系统操作员",
SampleType = Record.SampleType,
SampleSpec = Record.SampleSpec,
SampleSpec = Record.SampleSpec ?? "缺省值",
RoomTemperature = Record.RoomTemperature,
SoakingTime = Record.SoakingTime,
LiquidName = Record.Liquid?.Name,
LiquidSurfaceTension = Record.Liquid?.SurfaceTension ?? 0,
LiquidManufacturer = Record.LiquidManufacturer,
LiquidManufacturer = Record.LiquidManufacturer ?? "缺省值",
PressureUnit = Record.PressureUnit,
BubblePointPressure = Record.BubblePointPressure,
AveragePoreSize = AveragePoreSize, // 使用计算后的属性
@@ -685,10 +678,22 @@ namespace MembranePoreTester.ViewModels
}
public ICommand ClearAllCommand { get; }
// 修改命令初始化(构造函数中)
// 实现删除方法
private void RemoveSelectedDataPoint()
{
if (SelectedDataPoint != null)
{
Record.DataPoints.Remove(SelectedDataPoint);
SelectedDataPoint = null; // 清除选中状态
UpdatePlot(); // 刷新曲线
}
}
}
}

View File

@@ -70,8 +70,8 @@
<GroupBox Header="📊 压力数据">
<StackPanel>
<Label Content="当前压力(kPa)" FontWeight="Normal" Margin="5,0,0,0"/>
<TextBox Text="{Binding Record.BubbleCurrentPressure}" Width="150" Margin="5,0,5,5"/>
<Label Content="泡点压力(kPa)" FontWeight="Normal" Margin="5,0,0,0"/>
<TextBox HorizontalAlignment="Left" Text="{Binding Record.BubbleCurrentPressure}" Width="150" Margin="5,0,5,5"/>
<Label Content="泡点压力(Pa)" FontWeight="Normal" Margin="5,0,0,0"/>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Record.BubblePointPressure}" Width="150" Margin="5"/>
<Button Content="涨破" Command="{Binding ReadPlcCommand}" Padding="10,5" Margin="5" Background="#FF9800"/>
@@ -83,11 +83,16 @@
</StackPanel>
<!-- 结果显示 -->
<Border Grid.Row="1" Margin="0,10" Background="White" CornerRadius="8" BorderBrush="#E9ECF0" BorderThickness="1">
<Border Grid.Row="1" Margin="0,10" Background="White" CornerRadius="8" BorderBrush="#E9ECF0" BorderThickness="1">
<Grid>
<GroupBox Header="✨ 测试结果" BorderThickness="0" Margin="0">
<TextBlock FontSize="48" FontWeight="Bold" TextAlignment="Center"
Text="{Binding MaxPoreSize, StringFormat={}{0:F3} μm}" Foreground="#2196F3"/>
<GroupBox Header="✨ 测试结果" BorderThickness="0"
Margin="0" Padding="5,0">
<!-- 减少内边距 -->
<TextBlock FontSize="40" FontWeight="Bold" TextAlignment="Center"
Text="{Binding MaxPoreSize, StringFormat={}{0:F3} μm}"
Foreground="#2196F3"
Margin="0,-30,0,-10"/>
<!-- 负边距压缩垂直空间 -->
</GroupBox>
</Grid>
</Border>

View File

@@ -1,8 +1,92 @@
<Window x:Class="MembranePoreTester.Views.HistoryWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="历史记录" Height="600" Width="900"
WindowStartupLocation="CenterOwner">
Title="历史记录" Height="600" Width="1000"
WindowStartupLocation="CenterOwner" Loaded="Window_Loaded"
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="ComboBox">
<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="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"/>
</Style>
<!-- 统一 DatePicker 样式(保持一致性) -->
<Style TargetType="DatePicker">
<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"/>
</Style>
<!-- DataGrid 样式 -->
<Style TargetType="DataGrid">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="#E9ECF0"/>
<Setter Property="RowHeight" Value="28"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="GridLinesVisibility" Value="Horizontal"/>
<Setter Property="HeadersVisibility" Value="Column"/>
<Setter Property="RowBackground" Value="White"/>
<Setter Property="AlternatingRowBackground" Value="#F9F9F9"/>
</Style>
</Window.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -10,48 +94,53 @@
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 过滤条件 -->
<StackPanel Orientation="Horizontal" Grid.Row="0" Margin="0,0,0,10">
<Label Content="工位:"/>
<ComboBox x:Name="cmbStation" SelectedIndex="0" Width="80" Margin="5,0">
<ComboBoxItem Content="全部"/>
<ComboBoxItem Content="1"/>
<ComboBoxItem Content="2"/>
<ComboBoxItem Content="3"/>
</ComboBox>
<Label Content="测试类型:"/>
<ComboBox x:Name="cmbType" SelectedIndex="0" Width="120" Margin="5,0">
<ComboBoxItem Content="全部"/>
<ComboBoxItem Content="泡点法"/>
<ComboBoxItem Content="孔分布"/>
</ComboBox>
<Label Content="日期范围:"/>
<DatePicker x:Name="dpStart" Width="120"/>
<Label Content="-" Margin="2"/>
<DatePicker x:Name="dpEnd" Width="120"/>
<Button Content="查询" Click="Query_Click" Padding="15,5" Margin="10,0"/>
<Button Content="导出选中" Click="ExportSelected_Click" Padding="15,5"/>
</StackPanel>
<!-- 过滤条件区域(卡片式) -->
<Border Grid.Row="0" Background="White" CornerRadius="6" Padding="10" Margin="0,0,0,10" BorderBrush="#E9ECF0" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Label Content="工位:"/>
<ComboBox x:Name="cmbStation" Width="80">
<ComboBoxItem Content="全部"/>
<ComboBoxItem Content="1"/>
<ComboBoxItem Content="2"/>
<ComboBoxItem Content="3"/>
</ComboBox>
<Label Content="测试类型:"/>
<ComboBox x:Name="cmbType" Width="120">
<ComboBoxItem Content="全部"/>
<ComboBoxItem Content="泡点法"/>
<ComboBoxItem Content="孔分布"/>
</ComboBox>
<Label Content="日期范围:"/>
<DatePicker x:Name="dpStart" Width="120"/>
<Label Content="-" Margin="2,0,2,0" VerticalAlignment="Center"/>
<DatePicker x:Name="dpEnd" Width="120"/>
<Button Content="🔍 查询" Click="Query_Click" Padding="15,5" Background="#2196F3"/>
<Button Content="📊 导出选中" Click="ExportSelected_Click" Padding="15,5" Background="#4CAF50"/>
</StackPanel>
</Border>
<!-- 历史记录列表 -->
<DataGrid x:Name="dgHistory" Grid.Row="1" AutoGenerateColumns="False"
IsReadOnly="True" SelectionMode="Single" SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding Id}" Width="50"/>
<DataGridTextColumn Header="工位" Binding="{Binding StationId}" Width="50"/>
<DataGridTextColumn Header="类型" Binding="{Binding Type}" Width="80"/>
<DataGridTextColumn Header="测试日期" Binding="{Binding TestDate}" Width="150"/>
<DataGridTextColumn Header="样品类型" Binding="{Binding SampleType}" Width="100"/>
<DataGridTextColumn Header="规格" Binding="{Binding SampleSpec}" Width="100"/>
<DataGridTextColumn Header="测试者" Binding="{Binding Tester}" Width="100"/>
<DataGridTextColumn Header="结果" Binding="{Binding Result}" Width="150"/>
</DataGrid.Columns>
</DataGrid>
<!-- 历史记录列表(带边框) -->
<Border Grid.Row="1" Background="White" CornerRadius="6" BorderBrush="#E9ECF0" BorderThickness="1" Padding="5">
<DataGrid x:Name="dgHistory" AutoGenerateColumns="False"
IsReadOnly="True" SelectionMode="Single" SelectionUnit="FullRow"
BorderThickness="0" HorizontalGridLinesBrush="#E9ECF0">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding Id}" Width="50"/>
<DataGridTextColumn Header="工位" Binding="{Binding StationId}" Width="50"/>
<DataGridTextColumn Header="类型" Binding="{Binding Type}" Width="80"/>
<DataGridTextColumn Header="测试日期" Binding="{Binding TestDate}" Width="150"/>
<DataGridTextColumn Header="样品类型" Binding="{Binding SampleType}" Width="100"/>
<DataGridTextColumn Header="规格" Binding="{Binding SampleSpec}" Width="100"/>
<DataGridTextColumn Header="测试者" Binding="{Binding Tester}" Width="100"/>
<DataGridTextColumn Header="结果" Binding="{Binding Result}" Width="150"/>
</DataGrid.Columns>
</DataGrid>
</Border>
<!-- 底部按钮 -->
<!-- 底部按钮区域 -->
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
<Button Content="加载到当前工位" Click="LoadToCurrentStation_Click" Padding="15,5" Margin="5"/>
<Button Content="关闭" Click="Close_Click" Padding="15,5"/>
<Button Content="📂 加载到当前工位" Click="LoadToCurrentStation_Click" Padding="15,5" Background="#FF9800"/>
<Button Content="关闭" Click="Close_Click" Padding="15,5" Background="#607D8B"/>
</StackPanel>
</Grid>
</Window>

View File

@@ -145,6 +145,11 @@ namespace MembranePoreTester.Views
private void Close_Click(object sender, RoutedEventArgs e) => Close();
public static event EventHandler<LoadRecordEventArgs> LoadRecordEvent;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Query_Click(sender, e);
}
}
public class LoadRecordEventArgs : EventArgs

View File

@@ -3,64 +3,146 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MembranePoreTester.Views"
xmlns:viewModels="clr-namespace:MembranePoreTester.ViewModels"
xmlns:conv="clr-namespace:MembranePoreTester.Converters"
Title="膜孔径测试系统 (GB/T 32361-2015)"
Width="1024" Height="768"
WindowStartupLocation="CenterScreen" KeyDown="Window_KeyDown">
WindowStartupLocation="CenterScreen" KeyDown="Window_KeyDown"
Background="#F5F7FA" FontFamily="Segoe UI">
<Window.DataContext>
<viewModels:MainViewModel />
</Window.DataContext>
<Window.Resources>
<conv:BoolToStringConverter x:Key="BoolToStringConverter"/>
<!-- 统一按钮样式(与子视图保持一致) -->
<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>
<!-- 统一 ComboBox 样式 -->
<Style TargetType="ComboBox">
<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"/>
</Style>
<!-- 统一 TabControl 样式 -->
<Style TargetType="TabControl">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
<Style TargetType="TabItem">
<Setter Property="FontSize" Value="13"/>
<Setter Property="Padding" Value="15,8"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="#5F6A7A"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Border Background="{TemplateBinding Background}"
BorderThickness="0,0,0,2"
BorderBrush="Transparent"
Padding="{TemplateBinding Padding}">
<ContentPresenter ContentSource="Header" HorizontalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Blue"/>
<Setter Property="BorderBrush" Value="#2196F3"/>
<Setter Property="Background" Value="#E3F2FD"/>
<!-- 添加浅蓝背景 -->
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Blue"/>
<Setter Property="Background" Value="#F5F5F5"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<DockPanel>
<!-- 全局历史记录按钮 -->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="5">
<Button Content="历史记录" Click="OpenHistory_Click" Padding="10,5" Margin="5"/>
</StackPanel>
<!-- 顶部历史记录栏 -->
<Border DockPanel.Dock="Top" Background="White" BorderBrush="#E9ECF0" BorderThickness="0,0,0,1" Padding="10,5">
<StackPanel Orientation="Horizontal">
<Button Content="📋 历史记录" Click="OpenHistory_Click" Width="100" HorizontalAlignment="Left" Background="#607D8B"/>
<Button Content="阀门控制" Click="OpenValveControl_Click" Padding="10,5" Margin="5"/>
</StackPanel>
</Border>
<!-- 工位选项卡 -->
<TabControl x:Name="stationTabControl" ItemsSource="{Binding Stations}">
<TabControl x:Name="stationTabControl" ItemsSource="{Binding Stations}" Background="White" Margin="10,10,10,10">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Name}" FontSize="14" FontWeight="Medium"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<DockPanel>
<DockPanel Margin="0,10,0,0">
<!-- 工位控制栏 -->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="5">
<ComboBox ItemsSource="{Binding PressureModeList}"
SelectedItem="{Binding SelectedPressureMode}" Width="80" Margin="5"/>
<Border DockPanel.Dock="Top" Background="White" CornerRadius="6" Padding="10" Margin="0,0,0,10" BorderBrush="#E9ECF0" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding PressureModeList}" SelectedItem="{Binding SelectedPressureMode}" Width="80"/>
<Button Content="▶ 启动" Command="{Binding StartCommand}" Width="80" Background="#4CAF50"/>
<Button Content="⏹ 停止" Command="{Binding StopCommand}" Width="80" Background="#F44336"/>
<Button Content="{Binding PressButtonText}" Command="{Binding PressCommand}" Width="80" Background="#FF9800"/>
<Button Content="启动" Command="{Binding StartCommand}" Width="80" Margin="5"/>
<Button Content="停止" Command="{Binding StopCommand}" Width="80" Margin="5"/>
<Button Content="{Binding PressButtonText}" Command="{Binding PressCommand}" Width="80" Margin="5"/>
<!-- 替换为状态指示灯 -->
<StackPanel Orientation="Horizontal" Margin="5">
<!-- 带阴影和光泽的圆形指示灯 -->
<Grid Width="18" Height="18" Margin="2">
<Ellipse Fill="{Binding EnableStatusColor}">
<Ellipse.Effect>
<DropShadowEffect BlurRadius="3" ShadowDepth="0" Opacity="0.5"/>
</Ellipse.Effect>
</Ellipse>
<Ellipse Margin="2,2,2,2" Opacity="0.6">
<Ellipse.Fill>
<RadialGradientBrush>
<GradientStop Color="White" Offset="0.2"/>
<GradientStop Color="Transparent" Offset="1"/>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Grid>
<TextBlock Text="{Binding EnableStatusText}" VerticalAlignment="Center" Margin="5,0,0,0"/>
<!-- 状态指示灯 -->
<StackPanel Orientation="Horizontal" Margin="15,0,0,0">
<Grid Width="18" Height="18" Margin="2">
<Ellipse Fill="{Binding EnableStatusColor}">
<Ellipse.Effect>
<DropShadowEffect BlurRadius="3" ShadowDepth="0" Opacity="0.3"/>
</Ellipse.Effect>
</Ellipse>
<Ellipse Margin="2" Opacity="0.6">
<Ellipse.Fill>
<RadialGradientBrush>
<GradientStop Color="White" Offset="0.2"/>
<GradientStop Color="Transparent" Offset="1"/>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Grid>
<TextBlock Text="{Binding EnableStatusText}" VerticalAlignment="Center" Margin="5,0,0,0" FontSize="12"/>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
<!-- 测试类型选项卡 -->
<TabControl x:Name="stationTabControl" ItemsSource="{Binding Stations}" SelectionChanged="TabControl_SelectionChanged">
<!-- 测试类型选项卡修复嵌套绑定直接定义两个TabItem -->
<TabControl SelectionChanged="TabControl_SelectionChanged">
<TabItem Header="泡点法测试最大孔径">
<local:BubblePointView DataContext="{Binding BubblePointVM}"/>
</TabItem>

View File

@@ -103,5 +103,12 @@ namespace MembranePoreTester.Views
}
return null;
}
private void OpenValveControl_Click(object sender, RoutedEventArgs e)
{
var win = new ValveControlWindow();
win.Owner = this;
win.Show();
}
}
}

View File

@@ -87,29 +87,6 @@
</Grid>
</GroupBox>
<!--<GroupBox Header="校准参数">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0">压力零点校准:</Label>
<TextBox x:Name="txtPressureZero" Grid.Row="0" Grid.Column="1"/>
<Label Grid.Row="1" Grid.Column="0">压力量程校准:</Label>
<TextBox x:Name="txtPressureSpan" Grid.Row="1" Grid.Column="1"/>
<Label Grid.Row="2" Grid.Column="0">流量零点校准:</Label>
<TextBox x:Name="txtFlowZero" Grid.Row="2" Grid.Column="1"/>
<Label Grid.Row="3" Grid.Column="0">流量量程校准:</Label>
<TextBox x:Name="txtFlowSpan" Grid.Row="3" Grid.Column="1"/>
</Grid>
</GroupBox>-->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,20,0,0">
<!--<Button Content="读取参数" Click="ReadParameters_Click" Width="100" Margin="5"/>-->

View File

@@ -155,6 +155,13 @@
</ComboBox>
<ComboBox Grid.Row="3" HorizontalAlignment="Right" Grid.Column="3" ItemsSource="{Binding PressureModeList}"
SelectedItem="{Binding SelectedPressureMode}" Width="100"/>
<Button Grid.Row="3" Grid.Column="3"
Content="🗑 清除数据"
Command="{Binding ClearAllCommand}"
Background="#F44336"
Width="90"
HorizontalAlignment="Center"
Margin="0,5,5,5"/>
</Grid>
</GroupBox>
@@ -181,11 +188,12 @@
<!-- 湿膜表格 -->
<GroupBox Grid.Column="0" Header="💧 湿膜数据" Margin="0,0,5,0">
<DataGrid ItemsSource="{Binding Record.DataPoints}"
SelectedItem="{Binding SelectedDataPoint}"
AutoGenerateColumns="False"
CanUserAddRows="False"
ColumnWidth="*">
<DataGrid.Columns>
<DataGridTextColumn Header="压力"
<DataGridTextColumn Header="压力(kPa)"
Binding="{Binding Pressure, UpdateSourceTrigger=PropertyChanged}"
MinWidth="100"/>
<DataGridTextColumn Header="湿膜流量(L/min)"
@@ -198,11 +206,12 @@
<!-- 干膜表格 -->
<GroupBox Grid.Column="1" Header="🔥 干膜数据" Margin="5,0,0,0">
<DataGrid ItemsSource="{Binding Record.DataPoints}"
SelectedItem="{Binding SelectedDataPoint}"
AutoGenerateColumns="False"
CanUserAddRows="False"
ColumnWidth="*">
<DataGrid.Columns>
<DataGridTextColumn Header="压力"
<DataGridTextColumn Header="压力(kPa)"
Binding="{Binding Pressure, UpdateSourceTrigger=PropertyChanged}"
MinWidth="100"/>
<DataGridTextColumn Header="干膜流量(L/min)"
@@ -235,27 +244,30 @@
<!-- 计算结果及操作按钮区域 -->
<Border Grid.Row="2" Margin="0,10,0,0" Background="White" CornerRadius="6" Padding="10" BorderBrush="#E9ECF0" BorderThickness="1">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<GroupBox Header="📏 平均孔径" Margin="5" BorderThickness="0">
<TextBlock Text="{Binding AveragePoreSize, StringFormat={}{0:F3} μm}" FontSize="20" FontWeight="Bold" Foreground="#2196F3"/>
<GroupBox Header="📏 平均孔径" Margin="0" BorderThickness="0">
<TextBlock Text="{Binding AveragePoreSize, StringFormat={}{0:F2} μm}" FontSize="14" FontWeight="Bold" Foreground="#2196F3"/>
</GroupBox>
<Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" Margin="10,0"/>
<Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" Margin="2,0"/>
<GroupBox Header="🎯 孔分布区间计算" Margin="5" BorderThickness="0">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding LowerPore}" Width="60" Margin="2"/>
<TextBlock Text="~" VerticalAlignment="Center" Margin="2"/>
<TextBox Text="{Binding UpperPore}" Width="60" Margin="2"/>
<TextBlock Text="μm" VerticalAlignment="Center" Margin="5,0,10,0"/>
<Button Content="计算" Command="{Binding CalculateCommand}" Margin="5,0" Padding="12,5" Background="#2196F3"/>
<TextBlock Text="{Binding RangePercentage, StringFormat={}{0:F1}%}" FontSize="16" FontWeight="Bold" VerticalAlignment="Center" Margin="10,0" Foreground="#4CAF50"/>
<TextBox Text="{Binding LowerPore}" Width="48" Margin="1"/>
<TextBlock Text="~" VerticalAlignment="Center" Margin="1"/>
<TextBox Text="{Binding UpperPore}" Width="48" Margin="1"/>
<TextBlock Text="μm" VerticalAlignment="Center" Margin="2,0,5,0"/>
<Button Content="计算" Command="{Binding CalculateCommand}" Margin="2,0" Padding="8,5" Background="#2196F3"/>
<TextBlock Text="{Binding RangePercentage, StringFormat={}{0:F1}%}" FontSize="14" FontWeight="Bold" VerticalAlignment="Center" Margin="5,0" Foreground="#4CAF50"/>
</StackPanel>
</GroupBox>
<Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" Margin="10,0"/>
<StackPanel Orientation="Horizontal">
<Button Content="🔧 大流量校准" Command="{Binding OpenFlowCalibCommand}" Background="#607D8B"/>
<Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" Margin="3,0"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="🔧 大流量校准" Command="{Binding OpenFlowCalibCommand}" Background="#607D8B"/>
<Button Content="🔧 小流量校准" Command="{Binding OpenFlowCalibCommand2}" Background="#607D8B"/>
<Button Content="📄 生成报告" Command="{Binding GenerateReportCommand}" Background="#2196F3"/>
<Button Content="💾 保存到历史" Command="{Binding SaveCommand}" Background="#4CAF50"/>
<Button Content="📊 导出Excel" Command="{Binding ExportCommand}" Background="#FF9800"/>
</StackPanel>
</StackPanel>
</Border>

View File

@@ -0,0 +1,161 @@
<Window x:Class="MembranePoreTester.Views.ValveControlWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MembranePoreTester.Converters"
Title="阀门控制" Height="768" Width="1024"
WindowStartupLocation="CenterOwner"
Background="#F5F7FA" FontFamily="Segoe UI">
<Window.Resources>
<local:BoolToStringConverter x:Key="BoolToStringConverter"/>
<!-- 统一按钮样式 -->
<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="ComboBox">
<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="VerticalAlignment" Value="Center"/>
</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="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Foreground" Value="#2C3E50"/>
</Style>
<!-- ToggleButton 样式(开关按钮) -->
<Style TargetType="ToggleButton">
<Setter Property="Background" Value="#9E9E9E"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="8,4"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border Background="{TemplateBinding Background}" CornerRadius="4" Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="#4CAF50"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#757575"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="#45a049"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- ItemsControl 项容器样式(圆角边框,交替背景) -->
<Style TargetType="ContentPresenter">
<Setter Property="Margin" Value="0,2"/>
</Style>
<Style TargetType="Border" x:Key="ValveItemBorder">
<Setter Property="Background" Value="White"/>
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="BorderBrush" Value="#E9ECF0"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="10,8"/>
<Setter Property="Margin" Value="0,2"/>
</Style>
</Window.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 工位选择卡片 -->
<Border Grid.Row="0" Background="White" CornerRadius="6" Padding="10" Margin="0,0,0,10" BorderBrush="#E9ECF0" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<TextBlock Text="工位:" VerticalAlignment="Center" Margin="5"/>
<ComboBox x:Name="cmbStation" Width="80">
<ComboBoxItem IsSelected="True" Content="工位1"/>
<ComboBoxItem Content="工位2"/>
<ComboBoxItem Content="工位3"/>
</ComboBox>
<Button Content="🔄 刷新状态" Click="Refresh_Click" Margin="10,0,0,0" Background="#607D8B"/>
</StackPanel>
</Border>
<!-- 阀门列表(带滚动条,整体卡片) -->
<Border Grid.Row="1" Background="White" CornerRadius="6" BorderBrush="#E9ECF0" BorderThickness="1" Padding="5">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="valveList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource ValveItemBorder}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Grid.Column="1" Text="{Binding CoilAddress}" Foreground="#7F8C8D" VerticalAlignment="Center" FontSize="11"/>
<ToggleButton Grid.Column="2" IsChecked="{Binding IsOn, Mode=TwoWay}"
Checked="ToggleButton_Checked"
Unchecked="ToggleButton_Unchecked"
Content="{Binding IsOn, Converter={StaticResource BoolToStringConverter}}"
HorizontalAlignment="Right" Width="70"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
</Grid>
</Window>

View File

@@ -0,0 +1,181 @@
using MembranePoreTester.Communication;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;
namespace MembranePoreTester.Views
{
public partial class ValveControlWindow : Window
{
private readonly IPlcService _plcService;
private readonly PlcConfiguration _config;
private readonly DispatcherTimer _refreshTimer;
private readonly Dictionary<int, List<ValveInfo>> _valvesByStation = new()
{
[1] = new()
{
new ValveInfo { Name = "高压进气阀", CoilAddress = 100 },
new ValveInfo { Name = "低压进气阀", CoilAddress = 101 },
new ValveInfo { Name = "排气阀", CoilAddress = 102 },
new ValveInfo { Name = "低压保护阀", CoilAddress = 103 },
new ValveInfo { Name = "大流量阀", CoilAddress = 104 },
new ValveInfo { Name = "小流量阀", CoilAddress = 105 },
new ValveInfo { Name = "底部出气阀", CoilAddress = 106 },
new ValveInfo { Name = "顶部出气阀", CoilAddress = 107 }
},
[2] = new()
{
new ValveInfo { Name = "高压进气阀", CoilAddress = 108 },
new ValveInfo { Name = "低压进气阀", CoilAddress = 109 },
new ValveInfo { Name = "排气阀", CoilAddress = 110 },
new ValveInfo { Name = "低压保护阀", CoilAddress = 111 },
new ValveInfo { Name = "大流量阀", CoilAddress = 112 },
new ValveInfo { Name = "小流量阀", CoilAddress = 113 },
new ValveInfo { Name = "底部出气阀", CoilAddress = 114 },
new ValveInfo { Name = "顶部出气阀", CoilAddress = 115 }
},
[3] = new()
{
new ValveInfo { Name = "高压进气阀", CoilAddress = 116 },
new ValveInfo { Name = "低压进气阀", CoilAddress = 117 },
new ValveInfo { Name = "排气阀", CoilAddress = 118 },
new ValveInfo { Name = "低压保护阀", CoilAddress = 119 },
new ValveInfo { Name = "大流量阀", CoilAddress = 120 },
new ValveInfo { Name = "小流量阀", CoilAddress = 121 },
new ValveInfo { Name = "底部出气阀", CoilAddress = 122 },
new ValveInfo { Name = "顶部出气阀", CoilAddress = 123 }
}
};
public ValveControlWindow()
{
InitializeComponent();
_plcService = App.PlcService;
_config = App.PlcConfig;
_refreshTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(1)
};
_refreshTimer.Tick += async (s, e) => await RefreshValveStates();
_refreshTimer.Start();
Loaded += (s, e) => ShowStation(1);
}
private void ShowStation(int station)
{
if (valveList == null) return; // 防御性检查
if (_valvesByStation.TryGetValue(station, out var valves))
{
valveList.ItemsSource = valves;
}
else
{
valveList.ItemsSource = null;
}
}
private async void CmbStation_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (valveList == null) return; // 初始化未完成
if (cmbStation.SelectedItem is ComboBoxItem item)
{
int station = int.Parse(item.Content.ToString().Replace("工位", ""));
ShowStation(station);
await RefreshValveStates();
}
}
private async Task RefreshValveStates()
{
if (valveList.ItemsSource is not List<ValveInfo> valves) return;
foreach (var valve in valves)
{
try
{
bool state = await _plcService.ReadCoilAsync((ushort)valve.CoilAddress);
valve.IsOn = state;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"读取线圈{valve.CoilAddress}失败: {ex.Message}");
}
}
}
private async void ToggleButton_Checked(object sender, RoutedEventArgs e)
{
if (sender is ToggleButton btn && btn.DataContext is ValveInfo valve)
{
await WriteCoil(valve.CoilAddress, true);
}
}
private async void ToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
if (sender is ToggleButton btn && btn.DataContext is ValveInfo valve)
{
await WriteCoil(valve.CoilAddress, false);
}
}
private async Task WriteCoil(int coilAddress, bool value)
{
try
{
await _plcService.WriteCoilAsync((ushort)coilAddress, value);
await RefreshValveStates();
}
catch (Exception ex)
{
MessageBox.Show($"控制阀门失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private async void Refresh_Click(object sender, RoutedEventArgs e)
{
await RefreshValveStates();
}
protected override void OnClosed(EventArgs e)
{
_refreshTimer?.Stop();
base.OnClosed(e);
}
}
public class ValveInfo : INotifyPropertyChanged
{
public string Name { get; set; }
public int CoilAddress { get; set; }
private bool _isOn;
public bool IsOn
{
get => _isOn;
set
{
if (_isOn != value)
{
_isOn = value;
OnPropertyChanged(nameof(IsOn));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}

View File

@@ -20,7 +20,7 @@
"StopCoil": 7, // 停止线圈M7ON 停止测试
// 压力值系数(用于单位转换,例如 kPa 转 Pa 时设为 1000
"PressureFactor": 1.0,
"PressureFactor": 1000.0,
// 流量寄存器地址(每个工位共用,但需配合流量模式切换)
"WetFlowRegister": 12, // 湿膜流量寄存器起始地址D2D3