#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:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cnc="clr-namespace:XplorePlane.Views.Cnc" xmlns:cnc="clr-namespace:XplorePlane.Views.Cnc"
Title="CNC 编辑器" Title="CNC 编辑器"
Width="1200" Width="350"
Height="750" Height="750"
MinWidth="900"
MinHeight="550"
ShowInTaskbar="False" ShowInTaskbar="False"
WindowStartupLocation="CenterOwner"> WindowStartupLocation="CenterOwner">
<cnc:CncPageView /> <cnc:CncPageView />
@@ -1,4 +1,7 @@
using System;
using System.Runtime.InteropServices;
using System.Windows; using System.Windows;
using System.Windows.Interop;
namespace XplorePlane.Views.Cnc namespace XplorePlane.Views.Cnc
{ {
@@ -7,6 +10,12 @@ namespace XplorePlane.Views.Cnc
public CncEditorWindow() public CncEditorWindow()
{ {
InitializeComponent(); InitializeComponent();
SourceInitialized += OnSourceInitialized;
}
private void OnSourceInitialized(object sender, EventArgs e)
{
WindowIconHelper.RemoveIcon(this);
} }
} }
} }
+29 -29
View File
@@ -1,4 +1,4 @@
<!-- CNC 编辑器主页面视图 | CNC editor main page view --> <!-- CNC 编辑器主页面视图 | CNC editor main page view -->
<UserControl <UserControl
x:Class="XplorePlane.Views.Cnc.CncPageView" x:Class="XplorePlane.Views.Cnc.CncPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
@@ -7,26 +7,26 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/" xmlns:prism="http://prismlibrary.com/"
d:DesignHeight="700" d:DesignHeight="700"
d:DesignWidth="900" d:DesignWidth="350"
prism:ViewModelLocator.AutoWireViewModel="True" prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<!-- 面板背景和边框颜色 | Panel background and border colors --> <!-- 面板背景和边框颜色 | Panel background and border colors -->
<SolidColorBrush x:Key="PanelBg" Color="White" /> <SolidColorBrush x:Key="PanelBg" Color="White" />
<SolidColorBrush x:Key="PanelBorder" Color="#cdcbcb" /> <SolidColorBrush x:Key="PanelBorder" Color="#cdcbcb" />
<SolidColorBrush x:Key="SeparatorBrush" Color="#E0E0E0" /> <SolidColorBrush x:Key="SeparatorBrush" Color="#E0E0E0" />
<SolidColorBrush x:Key="AccentBlue" Color="#E3F0FF" /> <SolidColorBrush x:Key="AccentBlue" Color="#E3F0FF" />
<FontFamily x:Key="CsdFont">Microsoft YaHei UI</FontFamily> <FontFamily x:Key="CsdFont">Microsoft YaHei UI</FontFamily>
<!-- 节点列表项样式 | Node list item style --> <!-- 节点列表项样式 | Node list item style -->
<Style x:Key="CncNodeItemStyle" TargetType="ListBoxItem"> <Style x:Key="CncNodeItemStyle" TargetType="ListBoxItem">
<Setter Property="Padding" Value="0" /> <Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" /> <Setter Property="Margin" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" /> <Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style> </Style>
<!-- 工具栏按钮样式 | Toolbar button style --> <!-- 工具栏按钮样式 | Toolbar button style -->
<Style x:Key="ToolbarBtn" TargetType="Button"> <Style x:Key="ToolbarBtn" TargetType="Button">
<Setter Property="Height" Value="28" /> <Setter Property="Height" Value="28" />
<Setter Property="Margin" Value="2,0" /> <Setter Property="Margin" Value="2,0" />
@@ -47,13 +47,13 @@
CornerRadius="4"> CornerRadius="4">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<!-- Row 0: 工具栏 | Toolbar --> <!-- Row 0: 工具栏 | Toolbar -->
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<!-- Row 1: 主内容区(左侧节点列表 + 右侧参数面板)| Main content (left: node list, right: parameter panel) --> <!-- Row 1: 主内容区(左侧节点列表 + 右侧参数面板)| Main content (left: node list, right: parameter panel) -->
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- ═══ 工具栏:节点插入命令 + 文件操作命令 | Toolbar: node insert commands + file operation commands ═══ --> <!-- ═══ 工具栏:节点插入命令 + 文件操作命令 | Toolbar: node insert commands + file operation commands ═══ -->
<Border <Border
Grid.Row="0" Grid.Row="0"
Padding="6,4" Padding="6,4"
@@ -61,7 +61,7 @@
BorderBrush="{StaticResource PanelBorder}" BorderBrush="{StaticResource PanelBorder}"
BorderThickness="0,0,0,1"> BorderThickness="0,0,0,1">
<WrapPanel Orientation="Horizontal"> <WrapPanel Orientation="Horizontal">
<!-- 文件操作按钮 | File operation buttons --> <!-- 文件操作按钮 | File operation buttons -->
<Button <Button
Command="{Binding NewProgramCommand}" Command="{Binding NewProgramCommand}"
Content="新建" Content="新建"
@@ -83,14 +83,14 @@
Style="{StaticResource ToolbarBtn}" Style="{StaticResource ToolbarBtn}"
ToolTip="导出 CSV | Export CSV" /> ToolTip="导出 CSV | Export CSV" />
<!-- 分隔线 | Separator --> <!-- 分隔线 | Separator -->
<Rectangle <Rectangle
Width="1" Width="1"
Height="20" Height="20"
Margin="4,0" Margin="4,0"
Fill="{StaticResource SeparatorBrush}" /> Fill="{StaticResource SeparatorBrush}" />
<!-- 节点插入按钮(9 种节点类型)| Node insert buttons (9 node types) --> <!-- 节点插入按钮(9 种节点类型)| Node insert buttons (9 node types) -->
<Button <Button
Command="{Binding InsertReferencePointCommand}" Command="{Binding InsertReferencePointCommand}"
Content="参考点" Content="参考点"
@@ -139,18 +139,18 @@
</WrapPanel> </WrapPanel>
</Border> </Border>
<!-- ═══ 主内容区:左侧节点列表 + 右侧参数面板 | Main content: left node list + right parameter panel ═══ --> <!-- ═══ 主内容区:左侧节点列表 + 右侧参数面板 | Main content: left node list + right parameter panel ═══ -->
<Grid Grid.Row="1"> <Grid Grid.Row="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<!-- 左侧:节点列表 | Left: node list --> <!-- 左侧:节点列表 | Left: node list -->
<ColumnDefinition Width="3*" MinWidth="200" /> <ColumnDefinition Width="3*" MinWidth="150" />
<!-- 分隔线 | Splitter --> <!-- 分隔线 | Splitter -->
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<!-- 右侧:参数面板 | Right: parameter panel --> <!-- 右侧:参数面板 | Right: parameter panel -->
<ColumnDefinition Width="2*" MinWidth="200" /> <ColumnDefinition Width="2*" MinWidth="150" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- ── 左侧:CNC 节点列表 | Left: CNC node list ── --> <!-- ── 左侧:CNC 节点列表 | Left: CNC node list ── -->
<ListBox <ListBox
x:Name="CncNodeListBox" x:Name="CncNodeListBox"
Grid.Column="0" Grid.Column="0"
@@ -164,15 +164,15 @@
<DataTemplate> <DataTemplate>
<Grid x:Name="NodeRoot" MinHeight="40"> <Grid x:Name="NodeRoot" MinHeight="40">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<!-- 图标列 | Icon column --> <!-- 图标列 | Icon column -->
<ColumnDefinition Width="40" /> <ColumnDefinition Width="40" />
<!-- 名称列 | Name column --> <!-- 名称列 | Name column -->
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<!-- 操作按钮列 | Action buttons column --> <!-- 操作按钮列 | Action buttons column -->
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- 节点图标 | Node icon --> <!-- 节点图标 | Node icon -->
<Border <Border
Grid.Column="0" Grid.Column="0"
Width="28" Width="28"
@@ -190,7 +190,7 @@
Stretch="Uniform" /> Stretch="Uniform" />
</Border> </Border>
<!-- 节点序号和名称 | Node index and name --> <!-- 节点序号和名称 | Node index and name -->
<StackPanel <StackPanel
Grid.Column="1" Grid.Column="1"
Margin="6,0,0,0" Margin="6,0,0,0"
@@ -209,7 +209,7 @@
Text="{Binding Name}" /> Text="{Binding Name}" />
</StackPanel> </StackPanel>
<!-- 悬停操作按钮:上移 / 下移 / 删除 | Hover actions: MoveUp / MoveDown / Delete --> <!-- 悬停操作按钮:上移 / 下移 / 删除 | Hover actions: MoveUp / MoveDown / Delete -->
<StackPanel <StackPanel
x:Name="NodeActions" x:Name="NodeActions"
Grid.Column="2" Grid.Column="2"
@@ -258,7 +258,7 @@
</StackPanel> </StackPanel>
</Grid> </Grid>
<DataTemplate.Triggers> <DataTemplate.Triggers>
<!-- 鼠标悬停时显示操作按钮 | Show action buttons on mouse hover --> <!-- 鼠标悬停时显示操作按钮 | Show action buttons on mouse hover -->
<Trigger SourceName="NodeRoot" Property="IsMouseOver" Value="True"> <Trigger SourceName="NodeRoot" Property="IsMouseOver" Value="True">
<Setter TargetName="NodeActions" Property="Visibility" Value="Visible" /> <Setter TargetName="NodeActions" Property="Visibility" Value="Visible" />
</Trigger> </Trigger>
@@ -267,13 +267,13 @@
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
</ListBox> </ListBox>
<!-- 垂直分隔线 | Vertical separator --> <!-- 垂直分隔线 | Vertical separator -->
<Rectangle <Rectangle
Grid.Column="1" Grid.Column="1"
Width="1" Width="1"
Fill="{StaticResource SeparatorBrush}" /> Fill="{StaticResource SeparatorBrush}" />
<!-- ── 右侧:参数面板(根据节点类型动态渲染)| Right: parameter panel (dynamic rendering by node type) ── --> <!-- ── 右侧:参数面板(根据节点类型动态渲染)| Right: parameter panel (dynamic rendering by node type) ── -->
<ScrollViewer <ScrollViewer
Grid.Column="2" Grid.Column="2"
HorizontalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled"
@@ -285,9 +285,9 @@
FontSize="11" FontSize="11"
FontWeight="Bold" FontWeight="Bold"
Foreground="#555" Foreground="#555"
Text="参数配置 | Parameters" /> Text="参数配置" />
<!-- 动态参数内容区域(占位:根据 SelectedNode 类型渲染)| Dynamic parameter content area (placeholder for node-type-based rendering) --> <!-- 动态参数内容区域(占位:根据 SelectedNode 类型渲染)| Dynamic parameter content area (placeholder for node-type-based rendering) -->
<ContentControl Content="{Binding SelectedNode}" /> <ContentControl Content="{Binding SelectedNode}" />
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
@@ -1,4 +1,7 @@
using System;
using System.Runtime.InteropServices;
using System.Windows; using System.Windows;
using System.Windows.Interop;
namespace XplorePlane.Views.Cnc namespace XplorePlane.Views.Cnc
{ {
@@ -7,6 +10,12 @@ namespace XplorePlane.Views.Cnc
public MatrixEditorWindow() public MatrixEditorWindow()
{ {
InitializeComponent(); InitializeComponent();
SourceInitialized += OnSourceInitialized;
}
private void OnSourceInitialized(object sender, EventArgs e)
{
WindowIconHelper.RemoveIcon(this);
} }
} }
} }