#0051 调整CNC界面,增加集成硬件DLL的说明文档

This commit is contained in:
zhengxuan.zhang
2026-04-01 15:51:36 +08:00
parent 08fd25cdd0
commit 7a4bc2a2fb
5 changed files with 219 additions and 32 deletions
+171
View File
@@ -0,0 +1,171 @@
# RaySourceOperateView 集成技术路线
## 整体架构
采用 **DLL 直接引用 + Prism DI 容器手动注册 + AutoWireViewModel 自动装配** 的集成方式。
DLL 提供完整的 MVVM 三层(View / ViewModel / Service),主项目负责 DI 注册和 XAML 布局嵌入,数据通过注入的服务接口和 Prism EventAggregator 在两侧流动。
---
## 分层说明
### 1. DLL 引用层
`XP.Hardware.RaySource.dll` 放置于 `Libs/Hardware/` 目录,通过 `.csproj``<Reference HintPath>` 引用。
DLL 内部包含:
| 类型 | 名称 | 说明 |
|------|------|------|
| UserControl | `RaySourceOperateView` | 射线源操作界面 |
| ViewModel | `RaySourceOperateViewModel` | 对应 ViewModel |
| 服务接口/实现 | `IRaySourceService` / `RaySourceService` | 射线源业务逻辑 |
| 工厂 | `IRaySourceFactory` / `RaySourceFactory` | 策略工厂,支持 Comet225/320、Spellman225 |
| 服务 | `IFilamentLifetimeService` | 灯丝寿命管理 |
| 配置模型 | `RaySourceConfig` | 从 App.config 加载的配置 |
---
### 2. DI 注册层(App.xaml.cs → AppBootstrapper
主项目在 `RegisterTypes()` 中**手动注册** DLL 内所有服务,未走 Prism 的 `ConfigureModuleCatalog` 自动模块加载,目的是避免模块加载顺序问题,确保 DryIoc 容器在 Shell 创建前已具备所有依赖。
```csharp
// 注册 ViewModel(供 ViewModelLocator 自动装配)
containerRegistry.Register<XP.Hardware.RaySource.ViewModels.RaySourceOperateViewModel>();
// 注册配置(从 App.config 读取 RaySource:xxx 键值)
var raySourceConfig = XP.Hardware.RaySource.Config.ConfigLoader.LoadConfig();
containerRegistry.RegisterInstance(raySourceConfig);
// 注册核心服务(全部单例)
containerRegistry.RegisterSingleton<IRaySourceFactory, RaySourceFactory>();
containerRegistry.RegisterSingleton<IRaySourceService, RaySourceService>();
containerRegistry.RegisterSingleton<IFilamentLifetimeService, FilamentLifetimeService>();
```
---
### 3. XAML 嵌入层(MainWindow.xaml
通过 XML 命名空间直接引用 DLL 中的 View:
```xml
xmlns:views1="clr-namespace:XP.Hardware.RaySource.Views;assembly=XP.Hardware.RaySource"
```
在主窗口右侧面板顶部(Grid.Row="0",固定高度 250px)放置控件:
```xml
<views1:RaySourceOperateView Grid.Row="0" Grid.ColumnSpan="2" />
```
控件内部已设置 `prism:ViewModelLocator.AutoWireViewModel="True"`,Prism 按命名约定自动从 DI 容器解析 `RaySourceOperateViewModel` 并绑定为 DataContext。
---
### 4. 数据传递路线
数据流分四条路径:
**路径 A:配置数据(启动时,单向下行)**
```
App.config (RaySource:xxx 键值)
→ ConfigLoader.LoadConfig()
→ RaySourceConfig 实例
→ 注入到 RaySourceService / RaySourceOperateViewModel
```
App.config 中的关键配置项:
```xml
<add key="RaySource:PlcIpAddress" value="192.168.1.100" />
<add key="RaySource:MinVoltage" value="20" />
<add key="RaySource:MaxVoltage" value="225" />
<add key="RaySource:StatusPollingInterval" value="500" />
<add key="RaySource:EnableAutoStatusMonitoring" value="true" />
```
**路径 B:用户操作(UI → DLL 服务层)**
```
RaySourceOperateView(按钮点击)
→ RaySourceOperateViewModelCommand 绑定)
→ IRaySourceService.SetVoltageAsync() / TurnOnAsync() / ...
→ IXRaySource(具体策略实现,如 Comet225
→ 硬件通讯(B&R PVI / BR.AN.PviServices.dll
```
**路径 C:状态回传(DLL 服务层 → UI)**
```
硬件状态轮询(StatusPollingInterval = 500ms
→ RaySourceService 内部更新
→ RaySourceOperateViewModel 属性变更(INotifyPropertyChanged
→ RaySourceOperateView 数据绑定自动刷新
同时:
→ AppStateService 订阅 IRaySourceService 事件
→ 更新 RaySourceState(IsOn, Voltage, Power)
→ Dispatcher.BeginInvoke 调度到 UI 线程
→ 其他 ViewModel 通过 IAppStateService 读取全局射线源状态
```
`RaySourceState` 为不可变 record,定义于 `Models/StateModels.cs`
```csharp
public record RaySourceState(
bool IsOn, // 开关状态
double Voltage, // 电压 (kV)
double Power // 功率 (W)
)
{
public static readonly RaySourceState Default = new(false, 0, 0);
}
```
**路径 D:跨模块事件通讯(Prism EventAggregator**
```
DLL 内部发布事件(XP.Hardware.RaySource.Abstractions.Events):
XrayStateChangedEvent — 射线开关状态变化
StatusUpdatedEvent — 实时电压/电流数据
ErrorOccurredEvent — 错误通知
OperationResultEvent — 操作结果回调
主项目任意 ViewModel 订阅示例:
_eventAggregator.GetEvent<StatusUpdatedEvent>().Subscribe(data => { ... });
```
---
### 5. 完整依赖关系
```
RaySourceOperateViewDLL 中的 UserControl
└─ AutoWire → RaySourceOperateViewModelDLL,主项目注册到 DI
├─ IRaySourceService ← 单例,DLL 实现
├─ RaySourceConfig ← App.config 加载
├─ IFilamentLifetimeService ← 单例,DLL 实现
├─ IEventAggregator ← Prism 内置
├─ ILoggerService ← 主项目 LoggerServiceAdapter 适配 Serilog
└─ ILocalizationService ← 主项目注册,DLL 消费
```
---
### 6. 资源释放
`App.OnExit()` 中显式从容器解析并释放资源:
```csharp
var appStateService = bootstrapper.Container.Resolve<IAppStateService>();
appStateService?.Dispose();
var raySourceService = bootstrapper.Container.Resolve<IRaySourceService>();
raySourceService?.Dispose();
```
确保硬件连接在应用退出时正确断开。
+1 -3
View File
@@ -4,10 +4,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cnc="clr-namespace:XplorePlane.Views.Cnc"
Title="CNC 编辑器"
Width="1200"
Width="350"
Height="750"
MinWidth="900"
MinHeight="550"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner">
<cnc:CncPageView />
@@ -1,4 +1,7 @@
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
namespace XplorePlane.Views.Cnc
{
@@ -7,6 +10,12 @@ namespace XplorePlane.Views.Cnc
public CncEditorWindow()
{
InitializeComponent();
SourceInitialized += OnSourceInitialized;
}
private void OnSourceInitialized(object sender, EventArgs e)
{
WindowIconHelper.RemoveIcon(this);
}
}
}
+4 -4
View File
@@ -7,7 +7,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
d:DesignHeight="700"
d:DesignWidth="900"
d:DesignWidth="350"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
@@ -143,11 +143,11 @@
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<!-- 左侧:节点列表 | Left: node list -->
<ColumnDefinition Width="3*" MinWidth="200" />
<ColumnDefinition Width="3*" MinWidth="150" />
<!-- 分隔线 | Splitter -->
<ColumnDefinition Width="Auto" />
<!-- 右侧:参数面板 | Right: parameter panel -->
<ColumnDefinition Width="2*" MinWidth="200" />
<ColumnDefinition Width="2*" MinWidth="150" />
</Grid.ColumnDefinitions>
<!-- ── 左侧:CNC 节点列表 | Left: CNC node list ── -->
@@ -285,7 +285,7 @@
FontSize="11"
FontWeight="Bold"
Foreground="#555"
Text="参数配置 | Parameters" />
Text="参数配置" />
<!-- 动态参数内容区域(占位:根据 SelectedNode 类型渲染)| Dynamic parameter content area (placeholder for node-type-based rendering) -->
<ContentControl Content="{Binding SelectedNode}" />
@@ -1,4 +1,7 @@
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
namespace XplorePlane.Views.Cnc
{
@@ -7,6 +10,12 @@ namespace XplorePlane.Views.Cnc
public MatrixEditorWindow()
{
InitializeComponent();
SourceInitialized += OnSourceInitialized;
}
private void OnSourceInitialized(object sender, EventArgs e)
{
WindowIconHelper.RemoveIcon(this);
}
}
}