将流程图作为3级节点在左侧显示 ;优化布局宽度显示 ; 右侧详情面板的显示级别1级或2级

This commit is contained in:
zhengxuan.zhang
2026-05-25 10:59:39 +08:00
parent 03348a91ac
commit 581ed2f3df
7 changed files with 212 additions and 50 deletions
@@ -15,12 +15,12 @@ namespace XP.Hardware.Detector.Config
/// <summary>
/// 合成帧宽度(像素)| Synthetic frame width (pixels)
/// </summary>
public int Width { get; set; } = 256;
public int Width { get; set; } = 2800;
/// <summary>
/// 合成帧高度(像素)| Synthetic frame height (pixels)
/// </summary>
public int Height { get; set; } = 256;
public int Height { get; set; } = 2800;
/// <summary>
/// 模拟帧率(帧/秒)| Simulated frame rate (fps)
@@ -32,6 +32,7 @@ namespace XplorePlane.ViewModels.Cnc
private readonly ICncExecutionService _cncExecutionService;
private readonly IXpDataPathService _dataPathService;
private readonly IPipelinePersistenceService _pipelinePersistenceService;
private readonly IImageProcessingService _imageProcessingService;
private CncProgram _currentProgram;
private ObservableCollection<CncNodeViewModel> _nodes;
@@ -57,7 +58,8 @@ namespace XplorePlane.ViewModels.Cnc
ILoggerService logger,
ICncExecutionService cncExecutionService,
IXpDataPathService dataPathService,
IPipelinePersistenceService pipelinePersistenceService)
IPipelinePersistenceService pipelinePersistenceService,
IImageProcessingService imageProcessingService = null)
{
_cncProgramService = cncProgramService ?? throw new ArgumentNullException(nameof(cncProgramService));
ArgumentNullException.ThrowIfNull(appStateService);
@@ -66,6 +68,7 @@ namespace XplorePlane.ViewModels.Cnc
_cncExecutionService = cncExecutionService ?? throw new ArgumentNullException(nameof(cncExecutionService));
_dataPathService = dataPathService ?? throw new ArgumentNullException(nameof(dataPathService));
_pipelinePersistenceService = pipelinePersistenceService ?? throw new ArgumentNullException(nameof(pipelinePersistenceService));
_imageProcessingService = imageProcessingService; // optional — used for pipeline step display names
_nodes = new ObservableCollection<CncNodeViewModel>();
_treeNodes = new ObservableCollection<CncNodeViewModel>();
@@ -661,6 +664,12 @@ namespace XplorePlane.ViewModels.Cnc
IsExpanded = expansionState.TryGetValue(node.Id, out var isExpanded) ? isExpanded : true
};
// 为检测模块节点填充流水线步骤(三级树节点)
if (node is InspectionModuleNode imNode)
{
vm.SyncPipelineSteps(imNode.Pipeline, _imageProcessingService);
}
flatNodes.Add(vm);
if (vm.IsSavePosition)
@@ -3,6 +3,7 @@ using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using XplorePlane.Models;
@@ -30,10 +31,18 @@ namespace XplorePlane.ViewModels.Cnc
_modelChangedCallback = modelChangedCallback ?? throw new ArgumentNullException(nameof(modelChangedCallback));
_icon = GetIconForNodeType(model.NodeType);
Children = new ObservableCollection<CncNodeViewModel>();
PipelineSteps = new ObservableCollection<CncPipelineStepViewModel>();
}
public ObservableCollection<CncNodeViewModel> Children { get; }
/// <summary>
/// 检测模块的流水线步骤列表(三级节点)。
/// 仅 IsInspectionModule == true 时有内容。
/// Pipeline steps for InspectionModule nodes (3rd-level tree nodes).
/// </summary>
public ObservableCollection<CncPipelineStepViewModel> PipelineSteps { get; }
public CncNode Model => _model;
public Guid Id => _model.Id;
@@ -415,6 +424,7 @@ namespace XplorePlane.ViewModels.Cnc
if (_model is InspectionModuleNode im)
{
UpdateModel(im with { Pipeline = value ?? new PipelineModel() });
SyncPipelineSteps(value);
}
}
}
@@ -684,6 +694,7 @@ namespace XplorePlane.ViewModels.Cnc
RaisePropertyChanged(nameof(ManualImagePath));
RaisePropertyChanged(nameof(Pipeline));
RaisePropertyChanged(nameof(PipelineName));
RaisePropertyChanged(nameof(PipelineSteps));
RaisePropertyChanged(nameof(MarkerType));
RaisePropertyChanged(nameof(MarkerX));
RaisePropertyChanged(nameof(MarkerY));
@@ -699,6 +710,38 @@ namespace XplorePlane.ViewModels.Cnc
RaisePropertyChanged(nameof(ExecutionProgressText));
}
/// <summary>
/// 树形子节点集合:
/// - 对于检测模块节点,返回 PipelineSteps(流水线步骤作为三级节点)
/// - 对于其他节点,返回 Children(CNC 子节点)
/// Tree children: PipelineSteps for InspectionModule nodes, Children otherwise.
/// </summary>
public System.Collections.IEnumerable TreeChildren =>
IsInspectionModule ? (System.Collections.IEnumerable)PipelineSteps : Children;
/// <summary>
/// 将 PipelineModel 的节点同步到 PipelineSteps 集合(三级树节点)。
/// Syncs PipelineModel nodes into the PipelineSteps collection (3rd-level tree nodes).
/// </summary>
public void SyncPipelineSteps(PipelineModel pipeline, Services.IImageProcessingService imageProcessingService = null)
{
PipelineSteps.Clear();
if (pipeline?.Nodes == null)
return;
foreach (var node in pipeline.Nodes.OrderBy(n => n.Order))
{
// 优先使用服务获取中文显示名,回退到 OperatorKey
var displayName = imageProcessingService?.GetProcessorDisplayName(node.OperatorKey)
?? node.OperatorKey;
var icon = Services.ProcessorUiMetadata.GetOperatorIcon(node.OperatorKey);
PipelineSteps.Add(new CncPipelineStepViewModel(
displayName: displayName,
isEnabled: node.IsEnabled,
iconPath: icon));
}
}
private enum MotionAxis
{
StageX,
@@ -0,0 +1,51 @@
using Prism.Mvvm;
namespace XplorePlane.ViewModels.Cnc
{
/// <summary>
/// 检测模块流水线步骤的轻量 ViewModel,用于在 CNC 树形结构中作为三级节点显示。
/// Lightweight ViewModel for a pipeline step inside an InspectionModule node,
/// displayed as a 3rd-level node in the CNC tree.
/// </summary>
public class CncPipelineStepViewModel : BindableBase
{
private string _displayName;
private bool _isEnabled;
private string _iconPath;
public CncPipelineStepViewModel(string displayName, bool isEnabled, string iconPath = null)
{
_displayName = displayName;
_isEnabled = isEnabled;
_iconPath = iconPath ?? string.Empty;
}
/// <summary>算子显示名称(中文)| Operator display name</summary>
public string DisplayName
{
get => _displayName;
set => SetProperty(ref _displayName, value);
}
/// <summary>是否启用 | Whether the step is enabled</summary>
public bool IsEnabled
{
get => _isEnabled;
set
{
if (SetProperty(ref _isEnabled, value))
RaisePropertyChanged(nameof(StateText));
}
}
/// <summary>图标路径 | Icon path</summary>
public string IconPath
{
get => _iconPath;
set => SetProperty(ref _iconPath, value);
}
/// <summary>状态文字(已启用 / 已停用)| State text</summary>
public string StateText => _isEnabled ? "已启用" : "已停用";
}
}
+1 -1
View File
@@ -48,7 +48,7 @@ namespace XplorePlane.ViewModels
{
public class MainViewModel : BindableBase
{
private const double CncEditorHostWidth = 452d;
private const double CncEditorHostWidth = 560d;
private readonly ILoggerService _logger;
private readonly IContainerProvider _containerProvider;
private readonly IEventAggregator _eventAggregator;
+98 -45
View File
@@ -114,8 +114,8 @@
</UserControl.Resources>
<Border
Width="452"
MinWidth="452"
Width="560"
MinWidth="560"
HorizontalAlignment="Left"
Background="{StaticResource PanelBg}"
BorderBrush="{StaticResource PanelBorder}"
@@ -123,7 +123,7 @@
CornerRadius="4">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="260" />
<ColumnDefinition Width="1" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
@@ -141,6 +141,16 @@
BorderBrush="{StaticResource SeparatorBrush}"
BorderThickness="0,0,0,1">
<StackPanel>
<!-- 文件名标签:显示当前打开的程序文件名 -->
<TextBlock
Margin="2,0,0,4"
FontFamily="{StaticResource UiFont}"
FontSize="12"
FontWeight="SemiBold"
Foreground="#2B8A3E"
Text="{Binding ProgramTreeRoots[0].DisplayName}"
TextTrimming="CharacterEllipsis"
ToolTip="{Binding ProgramTreeRoots[0].DisplayName}" />
<WrapPanel/>
<WrapPanel Margin="0,4,0,0" Visibility="Collapsed">
<Button
@@ -251,53 +261,19 @@
Padding="3,5"
Background="Transparent"
BorderThickness="0"
ItemsSource="{Binding ProgramTreeRoots}"
ItemsSource="{Binding ProgramTreeRoots[0].Children}"
PreviewKeyDown="CncTreeView_PreviewKeyDown"
SelectedItemChanged="CncTreeView_SelectedItemChanged">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type vm:CncProgramTreeRootViewModel}"
ItemContainerStyle="{StaticResource TreeItemStyle}"
ItemsSource="{Binding Children}">
<Border
x:Name="ProgramRootCard"
Margin="0,1,0,3"
Padding="0,2"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="1"
CornerRadius="4">
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="1,0,4,0"
VerticalAlignment="Center"
FontFamily="{StaticResource UiFont}"
FontSize="12"
Foreground="#2B8A3E"
Text="◆" />
<TextBlock
VerticalAlignment="Center"
FontFamily="{StaticResource UiFont}"
FontSize="12"
FontWeight="SemiBold"
x:Name="ProgramRootNameText"
Text="{Binding DisplayName}"
TextTrimming="CharacterEllipsis" />
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="True">
<Setter TargetName="ProgramRootCard" Property="Background" Value="#E7F1FB" />
<Setter TargetName="ProgramRootCard" Property="BorderBrush" Value="#9FC6E8" />
<Setter TargetName="ProgramRootNameText" Property="Foreground" Value="#111111" />
</DataTrigger>
</DataTemplate.Triggers>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type vm:CncNodeViewModel}"
ItemContainerStyle="{StaticResource TreeItemStyle}"
ItemsSource="{Binding Children}">
ItemsSource="{Binding TreeChildren}">
<HierarchicalDataTemplate.ItemContainerStyle>
<!-- 三级节点(流水线步骤)的 TreeViewItem:取消额外缩进 -->
<Style BasedOn="{StaticResource TreeItemStyle}" TargetType="TreeViewItem">
<Setter Property="Margin" Value="-16,0,0,0" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<Border
x:Name="NodeCard"
Margin="0,1,0,1"
@@ -447,6 +423,83 @@
</MultiDataTrigger>
</DataTemplate.Triggers>
</HierarchicalDataTemplate>
<!-- 三级节点:检测模块的流水线步骤 -->
<DataTemplate DataType="{x:Type vm:CncPipelineStepViewModel}">
<Border
x:Name="StepCard"
Margin="0,1,0,1"
Padding="2,2,4,2"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="1"
CornerRadius="3">
<Grid MinHeight="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- 图标 -->
<Border
Grid.Column="0"
Width="14"
Height="14"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="Transparent"
CornerRadius="3">
<Image
Width="12"
Height="12"
Source="{Binding IconPath}"
Stretch="Uniform" />
</Border>
<!-- 名称 -->
<TextBlock
x:Name="StepNameText"
Grid.Column="1"
Margin="3,0,0,0"
VerticalAlignment="Center"
FontFamily="{StaticResource UiFont}"
FontSize="10.5"
Text="{Binding DisplayName}"
TextTrimming="CharacterEllipsis" />
<!-- 启用状态标签 -->
<Border
x:Name="StepStateBadge"
Grid.Column="2"
Margin="4,0,0,0"
Padding="4,1"
VerticalAlignment="Center"
Background="#E8F5E9"
BorderBrush="#A5D6A7"
BorderThickness="1"
CornerRadius="3">
<TextBlock
x:Name="StepStateText"
FontFamily="{StaticResource UiFont}"
FontSize="9.5"
Foreground="#2E7D32"
Text="{Binding StateText}" />
</Border>
</Grid>
</Border>
<DataTemplate.Triggers>
<!-- 已停用:灰色文字 + 橙色徽章 -->
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
<Setter TargetName="StepNameText" Property="Foreground" Value="#999999" />
<Setter TargetName="StepNameText" Property="TextDecorations" Value="Strikethrough" />
<Setter TargetName="StepStateBadge" Property="Background" Value="#FFF3E0" />
<Setter TargetName="StepStateBadge" Property="BorderBrush" Value="#FFCC80" />
<Setter TargetName="StepStateText" Property="Foreground" Value="#E65100" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style BasedOn="{StaticResource TreeItemStyle}" TargetType="TreeViewItem">
+7 -1
View File
@@ -85,7 +85,13 @@ namespace XplorePlane.Views.Cnc
{
if (DataContext is CncEditorViewModel viewModel)
{
viewModel.SelectedNode = e.NewValue as CncNodeViewModel;
// 三级节点(CncPipelineStepViewModel)点击时,保持当前 2 级节点选中不变
// Only update SelectedNode when a CncNodeViewModel is selected (1st or 2nd level)
if (e.NewValue is CncNodeViewModel nodeVm)
{
viewModel.SelectedNode = nodeVm;
}
// else: pipeline step clicked — keep existing SelectedNode unchanged
}
UpdateNodeVisualState();