From da43dfc59585bd27d761ad3810ddc66fb56a2d11 Mon Sep 17 00:00:00 2001 From: "zhengxuan.zhang" Date: Thu, 26 Mar 2026 17:04:10 +0800 Subject: [PATCH] =?UTF-8?q?#0048=20=E5=9B=BE=E5=83=8F=E7=AE=97=E5=AD=90?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E6=96=87=E4=BB=B6=EF=BC=8C=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=90=8E=E7=BC=80=20.imw?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- XplorePlane/Doc/全局数据状态读写.md | 241 ++++++++++++++++++ .../Pipeline/PipelinePersistenceService.cs | 2 +- .../PipelineEditorViewModel.cs | 4 +- XplorePlane/Views/Main/MainWindow.xaml | 2 +- XplorePlane/readme.txt | 15 +- 5 files changed, 259 insertions(+), 5 deletions(-) create mode 100644 XplorePlane/Doc/全局数据状态读写.md diff --git a/XplorePlane/Doc/全局数据状态读写.md b/XplorePlane/Doc/全局数据状态读写.md new file mode 100644 index 0000000..e70807c --- /dev/null +++ b/XplorePlane/Doc/全局数据状态读写.md @@ -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 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 e) + { + Console.WriteLine($"X轴: {e.OldValue.XM} → {e.NewValue.XM} μm"); + } + + private void OnSystemStateChanged(object sender, StateChangedEventArgs 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 + + + + + + + + + + + + + + + +``` + +## 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 | diff --git a/XplorePlane/Services/Pipeline/PipelinePersistenceService.cs b/XplorePlane/Services/Pipeline/PipelinePersistenceService.cs index ec6617f..7b1158f 100644 --- a/XplorePlane/Services/Pipeline/PipelinePersistenceService.cs +++ b/XplorePlane/Services/Pipeline/PipelinePersistenceService.cs @@ -74,7 +74,7 @@ namespace XplorePlane.Services if (!Directory.Exists(directory)) return Array.Empty(); - var files = Directory.GetFiles(directory, "*.pipeline.json"); + var files = Directory.GetFiles(directory, "*.imw"); var results = new List(); foreach (var file in files) diff --git a/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs b/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs index 0a0a6ec..4160d41 100644 --- a/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs +++ b/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs @@ -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() }; diff --git a/XplorePlane/Views/Main/MainWindow.xaml b/XplorePlane/Views/Main/MainWindow.xaml index 453fde8..b20e1fd 100644 --- a/XplorePlane/Views/Main/MainWindow.xaml +++ b/XplorePlane/Views/Main/MainWindow.xaml @@ -457,7 +457,7 @@ - +