修改CNC样式 √ 隐藏根节点,修改保存位置0 修改为位置1---N √ 运行对一级节点进行重命名 √

This commit is contained in:
zhengxuan.zhang
2026-05-25 13:33:04 +08:00
parent 581ed2f3df
commit d51d2b0013
8 changed files with 497 additions and 279 deletions
@@ -722,9 +722,9 @@ namespace XplorePlane.Services.Cnc
var manualImage = _mainViewportService?.LatestManualImage as BitmapSource;
if (manualImage != null)
{
_logger.ForModule<CncExecutionService>().Info(
"[图像链路] TryGetSourceImage:从 MainViewportService.LatestManualImage 获取图像,尺寸 {W}x{H}",
manualImage.PixelWidth, manualImage.PixelHeight);
//_logger.ForModule<CncExecutionService>().Info(
// "[图像链路] TryGetSourceImage:从 MainViewportService.LatestManualImage 获取图像,尺寸 {W}x{H}",
// manualImage.PixelWidth, manualImage.PixelHeight);
return manualImage;
}
@@ -374,8 +374,8 @@ namespace XplorePlane.Services.Cnc
result.Add(indexedNode switch
{
ReferencePointNode referencePointNode => referencePointNode with { Name = $"\u53C2\u8003\u70B9_{referencePointNumber++}" },
SavePositionNode savePositionNode => savePositionNode with { Name = $"\u4FDD\u5B58\u4F4D\u7F6E_{savePositionNumber++}" },
InspectionModuleNode inspectionModuleNode => inspectionModuleNode with { Name = $"\u68C0\u6D4B\u6A21\u5757_{inspectionModuleNumber++}" },
SavePositionNode savePositionNode => savePositionNode with { Name = $"\u4F4D\u7F6E{++savePositionNumber}" },
InspectionModuleNode inspectionModuleNode => inspectionModuleNode with { Name = $"\u6A21\u5757{++inspectionModuleNumber}" },
_ => indexedNode
});
}
@@ -77,11 +77,11 @@ namespace XplorePlane.Services.MainViewport
try
{
_logger.Info(
"[图像链路] DetectorFramePipeline 收到帧 #{N},分辨率 {W}x{H},采集时间 {T},当前队列深度 AcquireQ={AQ} ProcessQ={PQ}",
args.FrameNumber, args.Width, args.Height,
args.CaptureTime.ToString("HH:mm:ss.fff"),
AcquireQueueCount, ProcessQueueCount);
//_logger.Info(
// "[图像链路] DetectorFramePipeline 收到帧 #{N},分辨率 {W}x{H},采集时间 {T},当前队列深度 AcquireQ={AQ} ProcessQ={PQ}",
// args.FrameNumber, args.Width, args.Height,
// args.CaptureTime.ToString("HH:mm:ss.fff"),
// AcquireQueueCount, ProcessQueueCount);
var rawPixels = new ushort[args.ImageData.Length];
Array.Copy(args.ImageData, rawPixels, rawPixels.Length);
@@ -1005,15 +1005,50 @@ namespace XplorePlane.ViewModels.Cnc
result.Add(indexedNode switch
{
ReferencePointNode referencePointNode => referencePointNode with { Name = $"\u53C2\u8003\u70B9_{referencePointNumber++}" },
SavePositionNode savePositionNode => savePositionNode with { Name = $"\u4FDD\u5B58\u4F4D\u7F6E_{savePositionNumber++}" },
InspectionModuleNode inspectionModuleNode => inspectionModuleNode with { Name = $"\u68C0\u6D4B\u6A21\u5757_{inspectionModuleNumber++}" },
// 保存位置:用户自定义名称时保留,否则用"位置N"1-based
SavePositionNode savePositionNode => IsDefaultSavePositionName(savePositionNode.Name)
? savePositionNode with { Name = $"\u4F4D\u7F6E{++savePositionNumber}" }
: (CncNode)(savePositionNode with { Index = i }),
// 检测模块:用户自定义名称时保留,否则用"模块N"1-based
InspectionModuleNode inspectionModuleNode => IsDefaultInspectionModuleName(inspectionModuleNode.Name)
? inspectionModuleNode with { Name = $"\u6A21\u5757{++inspectionModuleNumber}" }
: (CncNode)(inspectionModuleNode with { Index = i }),
_ => indexedNode
});
// 无论是否重命名,计数器都要递增以保持后续编号连续
if (indexedNode is SavePositionNode sp && !IsDefaultSavePositionName(sp.Name))
savePositionNumber++;
if (indexedNode is InspectionModuleNode im && !IsDefaultInspectionModuleName(im.Name))
inspectionModuleNumber++;
}
return result.AsReadOnly();
}
/// <summary>判断是否为系统默认的保存位置名称("位置N" 格式)</summary>
private static bool IsDefaultSavePositionName(string name)
{
if (string.IsNullOrWhiteSpace(name)) return true;
if (name.StartsWith("\u4F4D\u7F6E", StringComparison.Ordinal))
return int.TryParse(name[2..], out _);
// 兼容旧格式 "保存位置_N"
if (name.StartsWith("\u4FDD\u5B58\u4F4D\u7F6E_", StringComparison.Ordinal))
return true;
return false;
}
/// <summary>判断是否为系统默认的检测模块名称("模块N" 或旧格式 "检测模块_N"</summary>
private static bool IsDefaultInspectionModuleName(string name)
{
if (string.IsNullOrWhiteSpace(name)) return true;
if (name.StartsWith("\u6A21\u5757", StringComparison.Ordinal))
return int.TryParse(name[2..], out _);
// 兼容旧格式 "检测模块_N"
if (name.StartsWith("\u68C0\u6D4B\u6A21\u5757_", StringComparison.Ordinal))
return true;
return false;
}
private static bool IsSavePositionChild(CncNodeType type)
{
return type is CncNodeType.InspectionMarker
+300 -209
View File
@@ -20,18 +20,105 @@
<local:BoolToDisplayTextConverter x:Key="BoolToDisplayTextConverter" />
<local:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
<SolidColorBrush x:Key="PanelBg" Color="White" />
<SolidColorBrush x:Key="PanelBg" Color="#F5F6FA" />
<SolidColorBrush x:Key="PanelBorder" Color="#CDCBCB" />
<SolidColorBrush x:Key="SeparatorBrush" Color="#E5E5E5" />
<SolidColorBrush x:Key="HeaderBg" Color="#F7F7F7" />
<SolidColorBrush x:Key="TreeChildLineBrush" Color="#C7D4DF" />
<SolidColorBrush x:Key="HeaderBg" Color="#F0F1F5" />
<SolidColorBrush x:Key="TreeChildLineBrush" Color="#C8CDD8" />
<FontFamily x:Key="UiFont">Microsoft YaHei UI</FontFamily>
<!-- ══════════════════════════════════════════════
现代风格 TreeViewItem:实心三角展开箭头
══════════════════════════════════════════════ -->
<!-- 展开/折叠箭头:实心三角 ▶ / ▼ -->
<Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
<Setter Property="Focusable" Value="False" />
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid Width="16" Height="16" Background="Transparent">
<!-- 折叠状态:▶ 右向三角 -->
<Path
x:Name="ArrowRight"
HorizontalAlignment="Center" VerticalAlignment="Center"
Data="M 0,0 L 5,4 L 0,8 Z"
Fill="#666666"
Stretch="None" />
<!-- 展开状态:▼ 下向三角 -->
<Path
x:Name="ArrowDown"
HorizontalAlignment="Center" VerticalAlignment="Center"
Data="M 0,0 L 8,0 L 4,5 Z"
Fill="#666666"
Stretch="None"
Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="ArrowRight" Property="Visibility" Value="Collapsed" />
<Setter TargetName="ArrowDown" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- TreeViewItem ControlTemplate -->
<Style x:Key="TreeItemStyle" TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeViewItem">
<StackPanel>
<!-- 节点行:箭头 + 内容 -->
<Grid x:Name="HeaderRow">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- 展开/折叠箭头 -->
<ToggleButton
x:Name="Expander"
Grid.Column="0"
HorizontalAlignment="Center" VerticalAlignment="Center"
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource ExpandCollapseToggleStyle}"
Visibility="Hidden" />
<!-- 节点内容 -->
<ContentPresenter
x:Name="PART_Header"
Grid.Column="1"
ContentSource="Header"
HorizontalAlignment="Stretch" />
</Grid>
<!-- 子节点区域 -->
<ItemsPresenter
x:Name="ItemsHost"
Margin="8,0,0,0"
Visibility="Collapsed" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="HasItems" Value="True">
<Setter TargetName="Expander" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="IsExpanded" Value="True">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="EditorTitle" TargetType="TextBlock">
@@ -265,237 +352,241 @@
PreviewKeyDown="CncTreeView_PreviewKeyDown"
SelectedItemChanged="CncTreeView_SelectedItemChanged">
<TreeView.Resources>
<!-- ── 2级节点:CncNodeViewModel ── -->
<HierarchicalDataTemplate
DataType="{x:Type vm:CncNodeViewModel}"
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"
Padding="0"
x:Name="NodeBg"
Margin="0,1,4,1"
Padding="6,4,6,4"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="1"
CornerRadius="4">
<Grid x:Name="NodeRoot" MinHeight="23">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
CornerRadius="6"
SnapsToDevicePixels="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="22" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid Grid.RowSpan="2" Grid.Column="0">
<Border
x:Name="ChildStem"
Width="1"
Margin="7,0,0,0"
HorizontalAlignment="Left"
Background="{StaticResource TreeChildLineBrush}" />
<Border
x:Name="ChildBranch"
Width="8"
Height="1"
Margin="7,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Background="{StaticResource TreeChildLineBrush}" />
</Grid>
<Border
Grid.RowSpan="2"
Grid.Column="1"
Width="16"
Height="16"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="Transparent"
CornerRadius="4">
<Image
Width="13"
Height="13"
Source="{Binding Icon}"
Stretch="Uniform" />
</Border>
<TextBlock
x:Name="NodeNameText"
Grid.Row="0"
Grid.Column="2"
Margin="3,0,0,0"
VerticalAlignment="Center"
FontFamily="{StaticResource UiFont}"
FontSize="11"
FontWeight="SemiBold"
Text="{Binding Name}"
TextTrimming="CharacterEllipsis" />
<StackPanel
x:Name="NodeActions"
Grid.Row="0"
Grid.Column="3"
Margin="0,0,2,0"
VerticalAlignment="Center"
Orientation="Horizontal"
Visibility="Collapsed">
<Button
Width="20"
Height="20"
Margin="1,0"
Background="White"
BorderBrush="#E05050"
BorderThickness="1"
Command="{Binding DataContext.DeleteNodeCommand, RelativeSource={RelativeSource AncestorType=TreeView}}"
Content="×"
Cursor="Hand"
FontSize="10"
ToolTip="删除" />
</StackPanel>
<Grid
x:Name="WaitDelayProgressHost"
Grid.Row="1"
Grid.Column="2"
Grid.ColumnSpan="2"
Margin="3,2,6,1"
Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ProgressBar
Height="6"
Minimum="0"
Maximum="100"
Value="{Binding ExecutionProgressPercent}" />
<TextBlock
Grid.Column="1"
Margin="6,-4,0,0"
VerticalAlignment="Center"
FontFamily="{StaticResource UiFont}"
FontSize="10"
Foreground="#444444"
Text="{Binding ExecutionProgressText}" />
</Grid>
</Grid>
</Border>
<DataTemplate.Triggers>
<Trigger SourceName="NodeRoot" Property="IsMouseOver" Value="True">
<Setter TargetName="NodeActions" Property="Visibility" Value="Visible" />
<Setter TargetName="NodeCard" Property="Background" Value="#F6FAFD" />
<Setter TargetName="NodeCard" Property="BorderBrush" Value="#DFEAF3" />
</Trigger>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="True">
<Setter TargetName="NodeActions" Property="Visibility" Value="Visible" />
<Setter TargetName="NodeCard" Property="Background" Value="#DCEEFF" />
<Setter TargetName="NodeCard" Property="BorderBrush" Value="#71A9DB" />
<Setter TargetName="NodeNameText" Property="Foreground" Value="#1F2D3D" />
</DataTrigger>
<DataTrigger Binding="{Binding ExecutionState}" Value="Running">
<Setter TargetName="NodeCard" Property="Background" Value="#FFD54F" />
<Setter TargetName="NodeCard" Property="BorderBrush" Value="#C89B00" />
<Setter TargetName="NodeNameText" Property="Foreground" Value="#1F1F1F" />
</DataTrigger>
<DataTrigger Binding="{Binding ExecutionState}" Value="Succeeded">
<Setter TargetName="NodeCard" Property="Background" Value="#FF2E7D32" />
<Setter TargetName="NodeCard" Property="BorderBrush" Value="#FF1B5E20" />
<Setter TargetName="NodeNameText" Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding ExecutionState}" Value="Failed">
<Setter TargetName="NodeCard" Property="Background" Value="#FFC62828" />
<Setter TargetName="NodeCard" Property="BorderBrush" Value="#FF8E0000" />
<Setter TargetName="NodeNameText" Property="Foreground" Value="White" />
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsWaitDelay}" Value="True" />
<Condition Binding="{Binding IsRunningNode}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter TargetName="WaitDelayProgressHost" Property="Visibility" Value="Visible" />
</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" />
<ColumnDefinition Width="24" />
</Grid.ColumnDefinitions>
<!-- 图标 -->
<Border
<Image
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>
Width="16" Height="16"
HorizontalAlignment="Center" VerticalAlignment="Center"
Source="{Binding Icon}"
Stretch="Uniform" />
<!-- 名称 -->
<!-- 名称(普通显示) -->
<TextBlock
x:Name="StepNameText"
x:Name="NodeNameText"
Grid.Column="1"
Margin="3,0,0,0"
Margin="4,0,8,0"
VerticalAlignment="Center"
FontFamily="{StaticResource UiFont}"
FontSize="10.5"
Text="{Binding DisplayName}"
TextTrimming="CharacterEllipsis" />
FontSize="12"
Foreground="#1A1A2E"
Text="{Binding Name}"
TextTrimming="CharacterEllipsis"
MouseLeftButtonDown="NodeNameText_MouseLeftButtonDown" />
<!-- 启用状态标签 -->
<Border
x:Name="StepStateBadge"
Grid.Column="2"
Margin="4,0,0,0"
Padding="4,1"
<!-- 名称(编辑模式,仅 SavePosition 节点双击后显示) -->
<TextBox
x:Name="NodeNameEditor"
Grid.Column="1"
Margin="2,0,6,0"
VerticalAlignment="Center"
Background="#E8F5E9"
BorderBrush="#A5D6A7"
FontFamily="{StaticResource UiFont}"
FontSize="12"
Text="{Binding Name, UpdateSourceTrigger=Explicit}"
Visibility="Collapsed"
BorderBrush="#7EB3F5"
BorderThickness="1"
CornerRadius="3">
<TextBlock
x:Name="StepStateText"
FontFamily="{StaticResource UiFont}"
FontSize="9.5"
Foreground="#2E7D32"
Text="{Binding StateText}" />
</Border>
Padding="3,1"
LostFocus="NodeNameEditor_LostFocus"
KeyDown="NodeNameEditor_KeyDown" />
<!-- 状态点 -->
<Ellipse
x:Name="StatusDot"
Grid.Column="2"
Width="7" Height="7"
Margin="0,0,4,0"
VerticalAlignment="Center"
Fill="#BBBBBB" />
<!-- 状态文字 -->
<TextBlock
x:Name="StatusText"
Grid.Column="3"
Margin="0,0,6,0"
VerticalAlignment="Center"
FontFamily="{StaticResource UiFont}"
FontSize="11"
Foreground="#AAAAAA"
Text="禁用" />
<!-- ⋮ 菜单按钮(选中时显示) -->
<Button
x:Name="NodeMenuBtn"
Grid.Column="4"
Width="22" Height="22"
Padding="0"
Background="Transparent"
BorderThickness="0"
Content="⋮"
Cursor="Hand"
FontSize="14"
Foreground="#555555"
ToolTip="更多操作"
Visibility="Collapsed"
Command="{Binding DataContext.DeleteNodeCommand, RelativeSource={RelativeSource AncestorType=TreeView}}"
Tag="{Binding}" />
</Grid>
</Border>
<DataTemplate.Triggers>
<!-- 已停用:灰色文字 + 橙色徽章 -->
<!-- 悬停 -->
<Trigger SourceName="NodeBg" Property="IsMouseOver" Value="True">
<Setter TargetName="NodeBg" Property="Background" Value="#EAECF5" />
<Setter TargetName="NodeMenuBtn" Property="Visibility" Value="Visible" />
</Trigger>
<!-- 选中 -->
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="True">
<Setter TargetName="NodeBg" Property="Background" Value="#DDE6FB" />
<Setter TargetName="NodeNameText" Property="Foreground" Value="#1A1A2E" />
<Setter TargetName="NodeMenuBtn" Property="Visibility" Value="Visible" />
</DataTrigger>
<!-- 不涉及启用/禁用的节点类型:隐藏状态点和状态文字 -->
<DataTrigger Binding="{Binding IsSavePosition}" Value="True">
<Setter TargetName="StatusDot" Property="Visibility" Value="Collapsed" />
<Setter TargetName="StatusText" Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsReferencePoint}" Value="True">
<Setter TargetName="StatusDot" Property="Visibility" Value="Collapsed" />
<Setter TargetName="StatusText" Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsPauseDialog}" Value="True">
<Setter TargetName="StatusDot" Property="Visibility" Value="Collapsed" />
<Setter TargetName="StatusText" Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsWaitDelay}" Value="True">
<Setter TargetName="StatusDot" Property="Visibility" Value="Collapsed" />
<Setter TargetName="StatusText" Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsCompleteProgram}" Value="True">
<Setter TargetName="StatusDot" Property="Visibility" Value="Collapsed" />
<Setter TargetName="StatusText" Property="Visibility" Value="Collapsed" />
</DataTrigger>
<!-- 检测模块:同样不显示启用/禁用状态 -->
<DataTrigger Binding="{Binding IsInspectionModule}" Value="True">
<Setter TargetName="StatusDot" Property="Visibility" Value="Collapsed" />
<Setter TargetName="StatusText" Property="Visibility" Value="Collapsed" />
</DataTrigger>
<!-- 执行中 -->
<DataTrigger Binding="{Binding ExecutionState}" Value="Running">
<Setter TargetName="NodeBg" Property="Background" Value="#FFF8E1" />
<Setter TargetName="StatusDot" Property="Fill" Value="#FFA000" />
<Setter TargetName="StatusDot" Property="Visibility" Value="Visible" />
<Setter TargetName="StatusText" Property="Text" Value="执行中" />
<Setter TargetName="StatusText" Property="Foreground" Value="#FFA000" />
<Setter TargetName="StatusText" Property="Visibility" Value="Visible" />
</DataTrigger>
<!-- 执行成功 -->
<DataTrigger Binding="{Binding ExecutionState}" Value="Succeeded">
<Setter TargetName="NodeBg" Property="Background" Value="#E8F5E9" />
<Setter TargetName="StatusDot" Property="Fill" Value="#43A047" />
<Setter TargetName="StatusDot" Property="Visibility" Value="Visible" />
<Setter TargetName="StatusText" Property="Text" Value="完成" />
<Setter TargetName="StatusText" Property="Foreground" Value="#43A047" />
<Setter TargetName="StatusText" Property="Visibility" Value="Visible" />
</DataTrigger>
<!-- 执行失败 -->
<DataTrigger Binding="{Binding ExecutionState}" Value="Failed">
<Setter TargetName="NodeBg" Property="Background" Value="#FFEBEE" />
<Setter TargetName="StatusDot" Property="Fill" Value="#E53935" />
<Setter TargetName="StatusDot" Property="Visibility" Value="Visible" />
<Setter TargetName="StatusText" Property="Text" Value="失败" />
<Setter TargetName="StatusText" Property="Foreground" Value="#E53935" />
<Setter TargetName="StatusText" Property="Visibility" Value="Visible" />
</DataTrigger>
</DataTemplate.Triggers>
</HierarchicalDataTemplate>
<!-- ── 3级节点:流水线步骤 CncPipelineStepViewModel ── -->
<DataTemplate DataType="{x:Type vm:CncPipelineStepViewModel}">
<Grid MinHeight="20" Background="Transparent">
<Grid.ColumnDefinitions>
<!-- 左侧竖线区 -->
<ColumnDefinition Width="3" />
<ColumnDefinition Width="6" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- 左侧竖线 -->
<Rectangle
Grid.Column="0"
Width="2"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Fill="{StaticResource TreeChildLineBrush}"
RadiusX="1" RadiusY="1" />
<!-- 图标 -->
<Image
Grid.Column="2"
Width="16" Height="16"
HorizontalAlignment="Center" VerticalAlignment="Center"
Source="{Binding IconPath}"
Stretch="Uniform" />
<!-- 名称 -->
<TextBlock
x:Name="StepNameText"
Grid.Column="3"
Margin="4,0,8,0"
VerticalAlignment="Center"
FontFamily="{StaticResource UiFont}"
FontSize="12"
Foreground="#333344"
Text="{Binding DisplayName}"
TextTrimming="CharacterEllipsis" />
<!-- 状态点 -->
<Ellipse
x:Name="StepDot"
Grid.Column="4"
Width="7" Height="7"
Margin="0,0,4,0"
VerticalAlignment="Center"
Fill="#43A047" />
<!-- 状态文字 -->
<TextBlock
x:Name="StepStateText"
Grid.Column="5"
Margin="0,0,8,0"
VerticalAlignment="Center"
FontFamily="{StaticResource UiFont}"
FontSize="11"
Foreground="#43A047"
Text="启用" />
</Grid>
<DataTemplate.Triggers>
<!-- 已停用 -->
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
<Setter TargetName="StepNameText" Property="Foreground" Value="#999999" />
<Setter TargetName="StepNameText" Property="Foreground" Value="#AAAAAA" />
<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" />
<Setter TargetName="StepDot" Property="Fill" Value="#BBBBBB" />
<Setter TargetName="StepStateText" Property="Text" Value="停用" />
<Setter TargetName="StepStateText" Property="Foreground" Value="#AAAAAA" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
+147 -55
View File
@@ -2,6 +2,7 @@ using Prism.Ioc;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
@@ -86,18 +87,88 @@ namespace XplorePlane.Views.Cnc
if (DataContext is CncEditorViewModel viewModel)
{
// 三级节点(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();
UpdatePropertyEditorState();
}
// ── 节点名称内联编辑 ──────────────────────────────────────────
private void NodeNameText_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount != 2) return;
if (sender is not TextBlock tb) return;
// 只允许 SavePosition 和 InspectionModule 节点重命名
var nodeVm = (tb.DataContext as CncNodeViewModel)
?? FindAncestor<TreeViewItem>(tb)?.DataContext as CncNodeViewModel;
if (nodeVm == null || (!nodeVm.IsSavePosition && !nodeVm.IsInspectionModule)) return;
// 找到同级的 TextBox 编辑器
var parent = VisualTreeHelper.GetParent(tb) as Grid;
if (parent == null) return;
var editor = FindVisualDescendants<TextBox>(parent).FirstOrDefault(x => x.Name == "NodeNameEditor");
if (editor == null) return;
// 切换到编辑模式
tb.Visibility = Visibility.Collapsed;
editor.Visibility = Visibility.Visible;
editor.Text = nodeVm.Name;
editor.SelectAll();
editor.Focus();
e.Handled = true;
}
private void NodeNameEditor_LostFocus(object sender, RoutedEventArgs e)
{
CommitNodeRename(sender as TextBox);
}
private void NodeNameEditor_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return || e.Key == Key.Enter)
{
CommitNodeRename(sender as TextBox);
e.Handled = true;
}
else if (e.Key == Key.Escape)
{
CancelNodeRename(sender as TextBox);
e.Handled = true;
}
}
private void CommitNodeRename(TextBox editor)
{
if (editor == null) return;
var nodeVm = FindAncestor<TreeViewItem>(editor)?.DataContext as CncNodeViewModel;
if (nodeVm == null) return;
var newName = editor.Text?.Trim();
if (!string.IsNullOrWhiteSpace(newName) && newName != nodeVm.Name)
nodeVm.Name = newName;
ExitRenameMode(editor);
}
private void CancelNodeRename(TextBox editor)
{
if (editor == null) return;
ExitRenameMode(editor);
}
private static void ExitRenameMode(TextBox editor)
{
var parent = VisualTreeHelper.GetParent(editor) as Grid;
if (parent == null) return;
var tb = FindVisualDescendants<TextBlock>(parent).FirstOrDefault(x => x.Name == "NodeNameText");
if (tb != null) tb.Visibility = Visibility.Visible;
editor.Visibility = Visibility.Collapsed;
}
private void CncTreeView_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Delete || DataContext is not CncEditorViewModel viewModel)
@@ -113,47 +184,91 @@ namespace XplorePlane.Views.Cnc
private void CncTreeView_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
if (DataContext is not CncEditorViewModel viewModel)
{
return;
}
var position = Mouse.GetPosition(CncTreeView);
var hit = VisualTreeHelper.HitTest(CncTreeView, position);
var treeViewItem = FindAncestor<TreeViewItem>(hit?.VisualHit);
if (treeViewItem?.DataContext is not CncNodeViewModel nodeVm)
// 右键点在节点上
if (treeViewItem?.DataContext is CncNodeViewModel nodeVm)
{
CncTreeView.ContextMenu = null;
return;
viewModel.SelectedNode = nodeVm;
UpdatePropertyEditorState();
CncTreeView.ContextMenu = new ContextMenu
{
Items =
{
new MenuItem
{
Header = "在上方插入位置",
Command = viewModel.PrepareInsertAboveCommand,
CommandParameter = nodeVm
},
new MenuItem
{
Header = "在下方插入位置",
Command = viewModel.PrepareInsertBelowCommand,
CommandParameter = nodeVm
},
new Separator(),
new MenuItem
{
Header = "全部展开",
Tag = "ExpandAll"
},
new MenuItem
{
Header = "全部折叠",
Tag = "CollapseAll"
}
}
};
}
else
{
// 右键点在空白处:只显示展开/折叠
CncTreeView.ContextMenu = new ContextMenu
{
Items =
{
new MenuItem { Header = "全部展开", Tag = "ExpandAll" },
new MenuItem { Header = "全部折叠", Tag = "CollapseAll" }
}
};
}
viewModel.SelectedNode = nodeVm;
UpdateNodeVisualState();
UpdatePropertyEditorState();
CncTreeView.ContextMenu = new ContextMenu
// 绑定展开/折叠事件
foreach (var item in CncTreeView.ContextMenu.Items)
{
Items =
if (item is MenuItem mi && mi.Tag is string tag)
{
new MenuItem
mi.Click += (_, _) =>
{
Header = "\u5728\u4E0A\u65B9\u63D2\u5165\u4F4D\u7F6E",
Command = viewModel.PrepareInsertAboveCommand,
CommandParameter = nodeVm
},
new MenuItem
{
Header = "\u5728\u4E0B\u65B9\u63D2\u5165\u4F4D\u7F6E",
Command = viewModel.PrepareInsertBelowCommand,
CommandParameter = nodeVm
}
bool expand = tag == "ExpandAll";
SetAllExpanded(CncTreeView.Items, expand);
};
}
};
}
}
/// <summary>递归设置所有 CncNodeViewModel 的展开状态</summary>
private static void SetAllExpanded(System.Collections.IEnumerable items, bool expand)
{
foreach (var item in items)
{
if (item is CncNodeViewModel nodeVm)
{
nodeVm.IsExpanded = expand;
SetAllExpanded(nodeVm.Children, expand);
}
}
}
private void CncTreeView_LayoutUpdated(object sender, EventArgs e)
{
HideInlineDeleteButtons();
UpdateNodeVisualState();
UpdatePropertyEditorState();
}
@@ -171,23 +286,7 @@ namespace XplorePlane.Views.Cnc
private void UpdateNodeVisualState()
{
foreach (var item in FindVisualDescendants<TreeViewItem>(CncTreeView))
{
if (item.DataContext is not CncNodeViewModel)
{
continue;
}
var card = FindNodeCard(item);
if (card == null)
{
continue;
}
card.ClearValue(Border.BackgroundProperty);
card.ClearValue(Border.BorderBrushProperty);
ClearNodeTextForeground(card);
}
// 新样式通过 DataTemplate Triggers 控制高亮,无需 code-behind 干预
}
private void UpdatePropertyEditorState()
@@ -319,14 +418,7 @@ namespace XplorePlane.Views.Cnc
private static Border FindNodeCard(DependencyObject root)
{
foreach (var border in FindVisualDescendants<Border>(root))
{
if (border.DataContext is CncNodeViewModel && border.CornerRadius.TopLeft == 4 && border.BorderThickness.Left == 1)
{
return border;
}
}
// 新样式不再使用 NodeCard,保留方法避免编译错误
return null;
}
@@ -61,9 +61,9 @@
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="4*" MinHeight="180" />
<RowDefinition Height="6*" MinHeight="200" />
<RowDefinition Height="Auto" />
<RowDefinition Height="2*" MinHeight="80" />
<RowDefinition Height="5*" />
</Grid.RowDefinitions>
<Border
+1 -1
View File
@@ -597,7 +597,7 @@
</telerik:RadRibbonGroup>
</telerik:RadRibbonTab>
<telerik:RadRibbonTab Header="帮助">
<telerik:RadRibbonGroup Header="关于">
<telerik:RadRibbonGroup Header="帮助">
<telerik:RadRibbonGroup.Variants>
<telerik:GroupVariant Priority="0" Variant="Large" />
</telerik:RadRibbonGroup.Variants>