#0048 图像算子流程文件,保存文件后缀 .imw
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
# AppStateService 使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
`AppStateService` 是全局单例状态管理服务,聚合所有硬件和系统状态。核心特性:
|
||||
|
||||
- **不可变状态**:所有状态模型均为 `record` 类型,更新即替换
|
||||
- **线程安全**:内部使用 `Interlocked.Exchange` 原子替换,可从任意线程调用 `Update`
|
||||
- **UI 线程调度**:事件和 `PropertyChanged` 自动通过 `Dispatcher.BeginInvoke` 调度到 UI 线程
|
||||
- **WPF 绑定友好**:继承 `BindableBase`,支持直接在 XAML 中绑定
|
||||
|
||||
## 1. 获取服务实例
|
||||
|
||||
通过 Prism 依赖注入获取(ViewModel 构造函数注入):
|
||||
|
||||
```csharp
|
||||
public class MyViewModel : BindableBase
|
||||
{
|
||||
private readonly IAppStateService _appState;
|
||||
|
||||
public MyViewModel(IAppStateService appState)
|
||||
{
|
||||
_appState = appState;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 读取状态
|
||||
|
||||
所有状态属性均为只读,直接访问即可:
|
||||
|
||||
```csharp
|
||||
// 读取射线源状态
|
||||
RaySourceState ray = _appState.RaySourceState;
|
||||
bool isOn = ray.IsOn; // 开关状态
|
||||
double voltage = ray.Voltage; // 电压 (kV)
|
||||
double power = ray.Power; // 功率 (W)
|
||||
|
||||
// 读取运动状态
|
||||
MotionState motion = _appState.MotionState;
|
||||
double xPos = motion.XM; // X 轴位置 (μm)
|
||||
double yPos = motion.YM; // Y 轴位置 (μm)
|
||||
|
||||
// 读取探测器状态
|
||||
DetectorState det = _appState.DetectorState;
|
||||
bool connected = det.IsConnected;
|
||||
double fps = det.FrameRate;
|
||||
|
||||
// 读取系统状态
|
||||
SystemState sys = _appState.SystemState;
|
||||
OperationMode mode = sys.OperationMode;
|
||||
bool hasError = sys.HasError;
|
||||
```
|
||||
|
||||
## 3. 更新状态
|
||||
|
||||
状态模型是不可变 record,更新时创建新实例或使用 `with` 表达式:
|
||||
|
||||
```csharp
|
||||
// 方式一:创建全新实例
|
||||
_appState.UpdateRaySourceState(new RaySourceState(
|
||||
IsOn: true,
|
||||
Voltage: 120.0,
|
||||
Power: 50.0
|
||||
));
|
||||
|
||||
// 方式二:基于当前状态用 with 修改部分字段(推荐)
|
||||
var current = _appState.RaySourceState;
|
||||
_appState.UpdateRaySourceState(current with { Voltage = 150.0 });
|
||||
|
||||
// 更新运动状态
|
||||
_appState.UpdateMotionState(new MotionState(
|
||||
XM: 1000.0, YM: 2000.0, ZT: 500.0, ZD: 300.0,
|
||||
TiltD: 0, Dist: 0,
|
||||
XMSpeed: 100, YMSpeed: 100, ZTSpeed: 50, ZDSpeed: 50,
|
||||
TiltDSpeed: 0, DistSpeed: 0
|
||||
));
|
||||
|
||||
// 更新系统状态(报告错误)
|
||||
_appState.UpdateSystemState(_appState.SystemState with
|
||||
{
|
||||
HasError = true,
|
||||
ErrorMessage = "探测器连接超时"
|
||||
});
|
||||
```
|
||||
|
||||
> **注意**:传入 `null` 会抛出 `ArgumentNullException`;`Dispose()` 后调用 `Update` 会被静默忽略。
|
||||
|
||||
## 4. 订阅状态变更事件
|
||||
|
||||
每种状态都有对应的类型化事件,回调自动在 UI 线程执行:
|
||||
|
||||
```csharp
|
||||
public class RaySourceMonitorViewModel : BindableBase, IDisposable
|
||||
{
|
||||
private readonly IAppStateService _appState;
|
||||
|
||||
public RaySourceMonitorViewModel(IAppStateService appState)
|
||||
{
|
||||
_appState = appState;
|
||||
|
||||
// 订阅射线源状态变更
|
||||
_appState.RaySourceStateChanged += OnRaySourceStateChanged;
|
||||
|
||||
// 订阅运动状态变更
|
||||
_appState.MotionStateChanged += OnMotionStateChanged;
|
||||
|
||||
// 订阅系统状态变更
|
||||
_appState.SystemStateChanged += OnSystemStateChanged;
|
||||
}
|
||||
|
||||
private void OnRaySourceStateChanged(object sender, StateChangedEventArgs<RaySourceState> e)
|
||||
{
|
||||
// e.OldValue - 变更前的状态
|
||||
// e.NewValue - 变更后的状态(已在 UI 线程)
|
||||
if (!e.OldValue.IsOn && e.NewValue.IsOn)
|
||||
{
|
||||
// 射线刚刚开启
|
||||
Console.WriteLine($"射线已开启,电压={e.NewValue.Voltage}kV");
|
||||
}
|
||||
|
||||
if (e.OldValue.Voltage != e.NewValue.Voltage)
|
||||
{
|
||||
Console.WriteLine($"电压变化: {e.OldValue.Voltage}kV → {e.NewValue.Voltage}kV");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMotionStateChanged(object sender, StateChangedEventArgs<MotionState> e)
|
||||
{
|
||||
Console.WriteLine($"X轴: {e.OldValue.XM} → {e.NewValue.XM} μm");
|
||||
}
|
||||
|
||||
private void OnSystemStateChanged(object sender, StateChangedEventArgs<SystemState> e)
|
||||
{
|
||||
if (e.NewValue.HasError)
|
||||
{
|
||||
Console.WriteLine($"系统错误: {e.NewValue.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// 取消订阅,防止内存泄漏
|
||||
_appState.RaySourceStateChanged -= OnRaySourceStateChanged;
|
||||
_appState.MotionStateChanged -= OnMotionStateChanged;
|
||||
_appState.SystemStateChanged -= OnSystemStateChanged;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. XAML 数据绑定
|
||||
|
||||
直接绑定到 `IAppStateService` 的属性(需要将服务暴露为 ViewModel 属性):
|
||||
|
||||
```csharp
|
||||
// ViewModel
|
||||
public class DashboardViewModel : BindableBase
|
||||
{
|
||||
public IAppStateService AppState { get; }
|
||||
|
||||
public DashboardViewModel(IAppStateService appState)
|
||||
{
|
||||
AppState = appState;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- XAML -->
|
||||
<StackPanel DataContext="{Binding AppState}">
|
||||
<!-- 射线源状态 -->
|
||||
<TextBlock Text="{Binding RaySourceState.Voltage, StringFormat='电压: {0:F1} kV'}" />
|
||||
<TextBlock Text="{Binding RaySourceState.Power, StringFormat='功率: {0:F1} W'}" />
|
||||
<Ellipse Width="12" Height="12"
|
||||
Fill="{Binding RaySourceState.IsOn, Converter={StaticResource BoolToColorConverter}}" />
|
||||
|
||||
<!-- 运动位置 -->
|
||||
<TextBlock Text="{Binding MotionState.XM, StringFormat='X: {0:F2} μm'}" />
|
||||
<TextBlock Text="{Binding MotionState.YM, StringFormat='Y: {0:F2} μm'}" />
|
||||
|
||||
<!-- 系统状态 -->
|
||||
<TextBlock Text="{Binding SystemState.OperationMode}" />
|
||||
<TextBlock Text="{Binding SystemState.ErrorMessage}" Foreground="Red"
|
||||
Visibility="{Binding SystemState.HasError, Converter={StaticResource BoolToVisConverter}}" />
|
||||
</StackPanel>
|
||||
```
|
||||
|
||||
## 6. 完整场景示例:硬件状态轮询 + UI 更新
|
||||
|
||||
```csharp
|
||||
public class HardwarePollingService : IDisposable
|
||||
{
|
||||
private readonly IAppStateService _appState;
|
||||
private readonly IRaySourceService _raySource;
|
||||
private readonly Timer _timer;
|
||||
|
||||
public HardwarePollingService(IAppStateService appState, IRaySourceService raySource)
|
||||
{
|
||||
_appState = appState;
|
||||
_raySource = raySource;
|
||||
|
||||
// 每 500ms 轮询一次硬件状态
|
||||
_timer = new Timer(PollHardware, null, 0, 500);
|
||||
}
|
||||
|
||||
private async void PollHardware(object state)
|
||||
{
|
||||
// 从硬件读取(在后台线程执行)
|
||||
var voltageResult = await _raySource.ReadVoltageAsync();
|
||||
var currentResult = await _raySource.ReadCurrentAsync();
|
||||
|
||||
if (voltageResult.Success && currentResult.Success)
|
||||
{
|
||||
var voltage = (double)voltageResult.Value;
|
||||
var current = (double)currentResult.Value;
|
||||
|
||||
// 线程安全更新,事件自动调度到 UI 线程
|
||||
_appState.UpdateRaySourceState(new RaySourceState(
|
||||
IsOn: _raySource.IsXRayOn,
|
||||
Voltage: voltage,
|
||||
Power: voltage * current / 1000.0
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => _timer?.Dispose();
|
||||
}
|
||||
```
|
||||
|
||||
## 可用状态一览
|
||||
|
||||
| 属性 | 类型 | 关键字段 |
|
||||
|------|------|----------|
|
||||
| `MotionState` | `MotionState` | XM, YM, ZT, ZD, TiltD, Dist 及各轴速度 |
|
||||
| `RaySourceState` | `RaySourceState` | IsOn, Voltage, Power |
|
||||
| `DetectorState` | `DetectorState` | IsConnected, IsAcquiring, FrameRate, Resolution |
|
||||
| `SystemState` | `SystemState` | OperationMode, HasError, ErrorMessage |
|
||||
| `CameraState` | `CameraState` | IsConnected, IsStreaming, CurrentFrame, Width, Height, FrameRate |
|
||||
| `CalibrationMatrix` | `CalibrationMatrix` | 3×3 仿射矩阵,支持 Transform(px, py) |
|
||||
| `LinkedViewState` | `LinkedViewState` | TargetPosition, IsExecuting, LastRequestTime |
|
||||
| `RecipeExecutionState` | `RecipeExecutionState` | CurrentStepIndex, TotalSteps, Status, CurrentRecipeName |
|
||||
@@ -74,7 +74,7 @@ namespace XplorePlane.Services
|
||||
if (!Directory.Exists(directory))
|
||||
return Array.Empty<PipelineModel>();
|
||||
|
||||
var files = Directory.GetFiles(directory, "*.pipeline.json");
|
||||
var files = Directory.GetFiles(directory, "*.imw");
|
||||
var results = new List<PipelineModel>();
|
||||
|
||||
foreach (var file in files)
|
||||
|
||||
@@ -374,7 +374,7 @@ namespace XplorePlane.ViewModels
|
||||
|
||||
var dialog = new SaveFileDialog
|
||||
{
|
||||
Filter = "流水线文件 (*.pipeline.json)|*.pipeline.json",
|
||||
Filter = "图像处理流水线 (*.imw)|*.imw",
|
||||
FileName = PipelineName,
|
||||
InitialDirectory = GetPipelineDirectory()
|
||||
};
|
||||
@@ -423,7 +423,7 @@ namespace XplorePlane.ViewModels
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Filter = "流水线文件 (*.pipeline.json)|*.pipeline.json",
|
||||
Filter = "图像处理流水线 (*.imw)|*.imw",
|
||||
InitialDirectory = GetPipelineDirectory()
|
||||
};
|
||||
|
||||
|
||||
@@ -457,7 +457,7 @@
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="340" />
|
||||
</Grid.RowDefinitions>
|
||||
<views:RaySourcePanelView Grid.Row="0" Grid.ColumnSpan="2" />
|
||||
<views:RaySourceOperateView Grid.Row="0" Grid.ColumnSpan="2" />
|
||||
<views:MotionControlPanelView
|
||||
Grid.Row="1"
|
||||
Grid.RowSpan="2"
|
||||
|
||||
+14
-1
@@ -45,11 +45,24 @@
|
||||
----------------------
|
||||
1、各窗体间数据流的传递,全局数据结构的设计(包括一个基本的说明文档)√
|
||||
2、将telerik 升级到 2024.1.408.310;调整界面和主题;引入 硬件层依赖 √
|
||||
3、图像算子流程文件,保存文件后缀 .imw, image process workflow 缩写
|
||||
3、图像算子流程文件,保存文件后缀 .imw, image process workflow 缩写 √
|
||||
4、CNC保存文件后缀为.xp, 表示 XplorePlane CNC file 的缩写
|
||||
5、硬件层射线源控件的集成
|
||||
|
||||
|
||||
|
||||
|
||||
2026.3.27
|
||||
----------------------
|
||||
CNC及矩阵功能的设计与实现,包含以下功能:
|
||||
1、CNC功能设计与实现,包含以下功能:
|
||||
a. CNC状态的定义和管理
|
||||
b. CNC界面设计与实现
|
||||
c. CNC相关数据的传递和处理
|
||||
2、CNC相关的编排工具,如插入节点,插入位置,图像模块,等
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user