Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f64a0f7b31 | |||
| dbfa09a9fd | |||
| f3e77562b1 | |||
| 9911566675 |
@@ -364,9 +364,20 @@ namespace XplorePlane.Services.Cnc
|
||||
private static IReadOnlyList<CncNode> RenumberNodes(List<CncNode> nodes)
|
||||
{
|
||||
var result = new List<CncNode>(nodes.Count);
|
||||
int referencePointNumber = 0;
|
||||
int savePositionNumber = 0;
|
||||
int inspectionModuleNumber = 0;
|
||||
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
result.Add(nodes[i] with { Index = i });
|
||||
var indexedNode = nodes[i] with { Index = i };
|
||||
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++}" },
|
||||
_ => indexedNode
|
||||
});
|
||||
}
|
||||
return result.AsReadOnly();
|
||||
}
|
||||
@@ -414,7 +425,7 @@ namespace XplorePlane.Services.Cnc
|
||||
private SavePositionNode CreateSavePositionNode(Guid id, int index)
|
||||
{
|
||||
return new SavePositionNode(
|
||||
id, index, $"保存位置_{index}",
|
||||
id, index, $"检测位置_{index}",
|
||||
MotionState: _appStateService.MotionState);
|
||||
}
|
||||
private double TryReadCurrent()
|
||||
|
||||
@@ -36,6 +36,8 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
private string _programName;
|
||||
private string _programDisplayName = "新建检测程序.xp";
|
||||
private Guid? _preferredSelectedNodeId;
|
||||
private Guid? _pendingInsertAnchorNodeId;
|
||||
private bool _pendingInsertAfterAnchor;
|
||||
|
||||
public CncEditorViewModel(
|
||||
ICncProgramService cncProgramService,
|
||||
@@ -69,6 +71,8 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
.ObservesProperty(() => SelectedNode);
|
||||
MoveNodeUpCommand = new DelegateCommand<CncNodeViewModel>(ExecuteMoveNodeUp);
|
||||
MoveNodeDownCommand = new DelegateCommand<CncNodeViewModel>(ExecuteMoveNodeDown);
|
||||
PrepareInsertAboveCommand = new DelegateCommand<CncNodeViewModel>(nodeVm => SetPendingInsertAnchor(nodeVm, insertAfter: false));
|
||||
PrepareInsertBelowCommand = new DelegateCommand<CncNodeViewModel>(nodeVm => SetPendingInsertAnchor(nodeVm, insertAfter: true));
|
||||
|
||||
SaveProgramCommand = new DelegateCommand(async () => await ExecuteSaveProgramAsync());
|
||||
LoadProgramCommand = new DelegateCommand(async () => await ExecuteLoadProgramAsync());
|
||||
@@ -144,6 +148,8 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
public DelegateCommand DeleteNodeCommand { get; }
|
||||
public DelegateCommand<CncNodeViewModel> MoveNodeUpCommand { get; }
|
||||
public DelegateCommand<CncNodeViewModel> MoveNodeDownCommand { get; }
|
||||
public DelegateCommand<CncNodeViewModel> PrepareInsertAboveCommand { get; }
|
||||
public DelegateCommand<CncNodeViewModel> PrepareInsertBelowCommand { get; }
|
||||
public DelegateCommand SaveProgramCommand { get; }
|
||||
public DelegateCommand LoadProgramCommand { get; }
|
||||
public DelegateCommand NewProgramCommand { get; }
|
||||
@@ -162,6 +168,7 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
int afterIndex = ResolveInsertAfterIndex(nodeType);
|
||||
_currentProgram = _cncProgramService.InsertNode(_currentProgram, afterIndex, node);
|
||||
_preferredSelectedNodeId = node.Id;
|
||||
ClearPendingInsertAnchor();
|
||||
|
||||
OnProgramEdited();
|
||||
_logger.Info("Inserted node: Type={NodeType}", nodeType);
|
||||
@@ -179,21 +186,24 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
|
||||
try
|
||||
{
|
||||
int deletedIndex = SelectedNode.Index;
|
||||
|
||||
if (SelectedNode.IsSavePosition)
|
||||
{
|
||||
var nodes = _currentProgram.Nodes.ToList();
|
||||
int startIndex = SelectedNode.Index;
|
||||
int startIndex = deletedIndex;
|
||||
int endIndex = GetSavePositionBlockEndIndex(startIndex);
|
||||
nodes.RemoveRange(startIndex, endIndex - startIndex + 1);
|
||||
_currentProgram = ReplaceProgramNodes(nodes);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentProgram = _cncProgramService.RemoveNode(_currentProgram, SelectedNode.Index);
|
||||
_currentProgram = _cncProgramService.RemoveNode(_currentProgram, deletedIndex);
|
||||
}
|
||||
|
||||
OnProgramEdited();
|
||||
_logger.Info("Deleted node at index: {Index}", SelectedNode.Index);
|
||||
ClearPendingInsertAnchorIfMissing();
|
||||
_logger.Info("Deleted node at index: {Index}", deletedIndex);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
@@ -305,6 +315,7 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
ProgramName = _currentProgram.Name;
|
||||
ProgramDisplayName = Path.GetFileName(dlg.FileName);
|
||||
IsModified = false;
|
||||
ClearPendingInsertAnchor();
|
||||
RefreshNodes();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -320,6 +331,7 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
ProgramName = _currentProgram.Name;
|
||||
ProgramDisplayName = FormatProgramDisplayName(_currentProgram.Name);
|
||||
IsModified = false;
|
||||
ClearPendingInsertAnchor();
|
||||
RefreshNodes();
|
||||
}
|
||||
|
||||
@@ -404,6 +416,8 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
|
||||
private void RefreshNodes()
|
||||
{
|
||||
NormalizeDefaultNodeNamesInCurrentProgram();
|
||||
|
||||
var selectedId = _preferredSelectedNodeId ?? SelectedNode?.Id;
|
||||
var expansionState = Nodes.ToDictionary(node => node.Id, node => node.IsExpanded);
|
||||
|
||||
@@ -454,6 +468,31 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
_preferredSelectedNodeId = null;
|
||||
}
|
||||
|
||||
private void NormalizeDefaultNodeNamesInCurrentProgram()
|
||||
{
|
||||
if (_currentProgram?.Nodes == null || _currentProgram.Nodes.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var normalizedNodes = ApplyDefaultNodeNames(_currentProgram.Nodes);
|
||||
|
||||
bool changed = false;
|
||||
for (int i = 0; i < normalizedNodes.Count; i++)
|
||||
{
|
||||
if (!Equals(normalizedNodes[i], _currentProgram.Nodes[i]))
|
||||
{
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
_currentProgram = _currentProgram with { Nodes = normalizedNodes };
|
||||
}
|
||||
}
|
||||
|
||||
private int ResolveInsertAfterIndex(CncNodeType nodeType)
|
||||
{
|
||||
if (_currentProgram == null || _currentProgram.Nodes.Count == 0)
|
||||
@@ -461,8 +500,19 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (TryResolvePendingInsertAfterIndex(nodeType, out int pendingAfterIndex))
|
||||
{
|
||||
return pendingAfterIndex;
|
||||
}
|
||||
|
||||
if (!IsSavePositionChild(nodeType))
|
||||
{
|
||||
int? currentSavePositionIndex = FindOwningSavePositionIndex(SelectedNode?.Index);
|
||||
if (currentSavePositionIndex.HasValue)
|
||||
{
|
||||
return GetSavePositionBlockEndIndex(currentSavePositionIndex.Value);
|
||||
}
|
||||
|
||||
return SelectedNode?.Index ?? _currentProgram.Nodes.Count - 1;
|
||||
}
|
||||
|
||||
@@ -475,6 +525,75 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
return GetSavePositionBlockEndIndex(savePositionIndex.Value);
|
||||
}
|
||||
|
||||
private void SetPendingInsertAnchor(CncNodeViewModel nodeVm, bool insertAfter)
|
||||
{
|
||||
if (_currentProgram == null || nodeVm == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_pendingInsertAnchorNodeId = nodeVm.Id;
|
||||
_pendingInsertAfterAnchor = insertAfter;
|
||||
SelectedNode = nodeVm;
|
||||
}
|
||||
|
||||
private bool TryResolvePendingInsertAfterIndex(CncNodeType nodeType, out int afterIndex)
|
||||
{
|
||||
afterIndex = -1;
|
||||
|
||||
if (!_pendingInsertAnchorNodeId.HasValue || _currentProgram == null || IsSavePositionChild(nodeType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int anchorIndex = FindNodeIndexById(_pendingInsertAnchorNodeId.Value);
|
||||
if (anchorIndex < 0)
|
||||
{
|
||||
ClearPendingInsertAnchor();
|
||||
return false;
|
||||
}
|
||||
|
||||
afterIndex = _pendingInsertAfterAnchor ? anchorIndex : anchorIndex - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
private int FindNodeIndexById(Guid nodeId)
|
||||
{
|
||||
if (_currentProgram?.Nodes == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _currentProgram.Nodes.Count; i++)
|
||||
{
|
||||
if (_currentProgram.Nodes[i].Id == nodeId)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void ClearPendingInsertAnchor()
|
||||
{
|
||||
_pendingInsertAnchorNodeId = null;
|
||||
_pendingInsertAfterAnchor = false;
|
||||
}
|
||||
|
||||
private void ClearPendingInsertAnchorIfMissing()
|
||||
{
|
||||
if (!_pendingInsertAnchorNodeId.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (FindNodeIndexById(_pendingInsertAnchorNodeId.Value) < 0)
|
||||
{
|
||||
ClearPendingInsertAnchor();
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveSavePositionChild(CncNodeViewModel nodeVm, bool moveDown)
|
||||
{
|
||||
int? parentIndex = FindOwningSavePositionIndex(nodeVm.Index);
|
||||
@@ -627,10 +746,7 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
|
||||
private CncProgram ReplaceProgramNodes(List<CncNode> nodes)
|
||||
{
|
||||
var renumberedNodes = nodes
|
||||
.Select((node, index) => node with { Index = index })
|
||||
.ToList()
|
||||
.AsReadOnly();
|
||||
var renumberedNodes = ApplyDefaultNodeNames(nodes);
|
||||
|
||||
return _currentProgram with
|
||||
{
|
||||
@@ -639,6 +755,28 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyList<CncNode> ApplyDefaultNodeNames(IReadOnlyList<CncNode> nodes)
|
||||
{
|
||||
var result = new List<CncNode>(nodes.Count);
|
||||
int referencePointNumber = 0;
|
||||
int savePositionNumber = 0;
|
||||
int inspectionModuleNumber = 0;
|
||||
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
var indexedNode = nodes[i] with { Index = i };
|
||||
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++}" },
|
||||
_ => indexedNode
|
||||
});
|
||||
}
|
||||
|
||||
return result.AsReadOnly();
|
||||
}
|
||||
|
||||
private static bool IsSavePositionChild(CncNodeType type)
|
||||
{
|
||||
return type is CncNodeType.InspectionMarker
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
set => UpdateModel(_model with { Name = value ?? string.Empty });
|
||||
}
|
||||
|
||||
public bool IsReadOnlyNodeProperties => IsReferencePoint || IsSavePosition;
|
||||
|
||||
public CncNodeType NodeType => _model.NodeType;
|
||||
|
||||
public string NodeTypeDisplay => NodeType.ToString();
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace XplorePlane.ViewModels
|
||||
{
|
||||
public class MainViewModel : BindableBase
|
||||
{
|
||||
private const double CncEditorHostWidth = 710d;
|
||||
private const double CncEditorHostWidth = 502d;
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly IContainerProvider _containerProvider;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
@@ -9,21 +9,20 @@
|
||||
xmlns:views="clr-namespace:XplorePlane.Views"
|
||||
xmlns:vm="clr-namespace:XplorePlane.ViewModels.Cnc"
|
||||
d:DesignHeight="760"
|
||||
d:DesignWidth="702"
|
||||
d:DesignWidth="502"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<local:InverseBooleanToVisibilityConverter x:Key="InverseBoolToVisibilityConverter" />
|
||||
<local:BoolToDisplayTextConverter x:Key="BoolToDisplayTextConverter" />
|
||||
<local:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
|
||||
<SolidColorBrush x:Key="PanelBg" Color="White" />
|
||||
<SolidColorBrush x:Key="PanelBorder" Color="#CDCBCB" />
|
||||
<SolidColorBrush x:Key="SeparatorBrush" Color="#E5E5E5" />
|
||||
<SolidColorBrush x:Key="HeaderBg" Color="#F7F7F7" />
|
||||
<SolidColorBrush x:Key="TreeParentBg" Color="#F6F8FB" />
|
||||
<SolidColorBrush x:Key="TreeChildBg" Color="#FBFCFE" />
|
||||
<SolidColorBrush x:Key="TreeAccentBrush" Color="#2E6FA3" />
|
||||
<SolidColorBrush x:Key="TreeChildLineBrush" Color="#C7D4DF" />
|
||||
<FontFamily x:Key="UiFont">Microsoft YaHei UI</FontFamily>
|
||||
|
||||
@@ -58,6 +57,19 @@
|
||||
<Setter Property="FontSize" Value="11" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="DisplayValueLabel" TargetType="Label">
|
||||
<Setter Property="Height" Value="28" />
|
||||
<Setter Property="Padding" Value="8,3" />
|
||||
<Setter Property="Margin" Value="0,0,0,8" />
|
||||
<Setter Property="Background" Value="#F7F8FA" />
|
||||
<Setter Property="BorderBrush" Value="#D9DDE3" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="FontFamily" Value="{StaticResource UiFont}" />
|
||||
<Setter Property="FontSize" Value="11" />
|
||||
<Setter Property="Foreground" Value="#333333" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="EditorCheck" TargetType="CheckBox">
|
||||
<Setter Property="Margin" Value="0,2,0,8" />
|
||||
<Setter Property="FontFamily" Value="{StaticResource UiFont}" />
|
||||
@@ -86,8 +98,8 @@
|
||||
</UserControl.Resources>
|
||||
|
||||
<Border
|
||||
Width="702"
|
||||
MinWidth="702"
|
||||
Width="502"
|
||||
MinWidth="502"
|
||||
HorizontalAlignment="Left"
|
||||
Background="{StaticResource PanelBg}"
|
||||
BorderBrush="{StaticResource PanelBorder}"
|
||||
@@ -97,9 +109,7 @@
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200" />
|
||||
<ColumnDefinition Width="1" />
|
||||
<ColumnDefinition Width="250" />
|
||||
<ColumnDefinition Width="1" />
|
||||
<ColumnDefinition Width="250" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid Grid.Column="0">
|
||||
@@ -233,24 +243,15 @@
|
||||
Stretch="Uniform" />
|
||||
</Border>
|
||||
|
||||
<StackPanel
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Margin="3,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="10"
|
||||
Foreground="#888888"
|
||||
Text="{Binding Index, StringFormat='[{0}] '}" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="11"
|
||||
FontWeight="SemiBold"
|
||||
Text="{Binding Name}" />
|
||||
</StackPanel>
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="11"
|
||||
FontWeight="SemiBold"
|
||||
Text="{Binding Name}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<StackPanel
|
||||
x:Name="NodeActions"
|
||||
@@ -299,225 +300,188 @@
|
||||
Width="1"
|
||||
Fill="{StaticResource SeparatorBrush}" />
|
||||
|
||||
<ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto">
|
||||
<Grid Margin="10">
|
||||
<StackPanel Visibility="{Binding SelectedNode, Converter={StaticResource NullToVisibilityConverter}}">
|
||||
<TextBlock Style="{StaticResource EditorTitle}" Text="节点属性" />
|
||||
<Grid Grid.Column="2">
|
||||
<ScrollViewer
|
||||
x:Name="NodePropertyEditor"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<Grid Margin="10">
|
||||
<StackPanel Visibility="{Binding SelectedNode, Converter={StaticResource NullToVisibilityConverter}}">
|
||||
<TextBlock Style="{StaticResource EditorTitle}" Text="节点属性" />
|
||||
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="名称" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Name, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="名称" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Name, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<UniformGrid Margin="0,0,0,8" Columns="2">
|
||||
<StackPanel Margin="0,0,6,0">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="索引" />
|
||||
<TextBox Style="{StaticResource EditorBox}" IsReadOnly="True" Text="{Binding SelectedNode.Index, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="类型" />
|
||||
<TextBox Style="{StaticResource EditorBox}" IsReadOnly="True" Text="{Binding SelectedNode.NodeTypeDisplay, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</UniformGrid>
|
||||
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="运动参数"
|
||||
Visibility="{Binding SelectedNode.IsMotionSnapshotNode, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<UniformGrid Margin="8,8,8,6" Columns="2">
|
||||
<UniformGrid Margin="0,0,0,8" Columns="2">
|
||||
<StackPanel Margin="0,0,6,0">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="XM" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.XM, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="索引" />
|
||||
<Label Style="{StaticResource DisplayValueLabel}" Content="{Binding SelectedNode.Index, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="YM" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.YM, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,6,0">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="ZT" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.ZT, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="ZD" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.ZD, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,6,0">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="TiltD" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.TiltD, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="Dist" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Dist, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="类型" />
|
||||
<Label Style="{StaticResource DisplayValueLabel}" Content="{Binding SelectedNode.NodeTypeDisplay, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</UniformGrid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="射线源"
|
||||
Visibility="{Binding SelectedNode.IsReferencePoint, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="8,8,8,6">
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="射线源开启" IsChecked="{Binding SelectedNode.IsRayOn}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="电压 (kV)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Voltage, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="电流 (uA)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Current, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="运动参数"
|
||||
Visibility="{Binding SelectedNode.IsMotionSnapshotNode, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<UniformGrid Margin="8,8,8,6" Columns="2">
|
||||
<StackPanel Margin="0,0,6,0">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="XM" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.XM, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="YM" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.YM, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,6,0">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="ZT" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.ZT, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="ZD" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.ZD, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,6,0">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="TiltD" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.TiltD, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="Dist" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Dist, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
</UniformGrid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="保存参数"
|
||||
Visibility="{Binding SelectedNode.IsSaveNode, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="8,8,8,6">
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="射线源开启" IsChecked="{Binding SelectedNode.IsRayOn}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="电压 (kV)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Voltage, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="功率 (W)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Power, UpdateSourceTrigger=LostFocus}" />
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器已连接" IsChecked="{Binding SelectedNode.DetectorConnected}" />
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器正在采集" IsChecked="{Binding SelectedNode.DetectorAcquiring}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="帧率" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FrameRate, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="分辨率" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Resolution, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="射线源"
|
||||
Visibility="{Binding SelectedNode.IsReferencePoint, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="8,8,8,6">
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="射线源开启" IsChecked="{Binding SelectedNode.IsRayOn}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="电压 (kV)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Voltage, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="电流 (uA)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Current, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="保存参数"
|
||||
Visibility="{Binding SelectedNode.IsSaveNodeWithImage, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="8,8,8,6">
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="射线源开启" IsChecked="{Binding SelectedNode.IsRayOn}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="电压 (kV)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Voltage, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="功率 (W)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Power, UpdateSourceTrigger=LostFocus}" />
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器已连接" IsChecked="{Binding SelectedNode.DetectorConnected}" />
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器正在采集" IsChecked="{Binding SelectedNode.DetectorAcquiring}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="帧率" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FrameRate, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="分辨率" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Resolution, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="图像文件" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.ImageFileName, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="保存参数"
|
||||
Visibility="{Binding SelectedNode.IsSaveNode, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="8,8,8,6">
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="射线源开启" IsChecked="{Binding SelectedNode.IsRayOn}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="电压 (kV)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Voltage, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="功率 (W)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Power, UpdateSourceTrigger=LostFocus}" />
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器已连接" IsChecked="{Binding SelectedNode.DetectorConnected}" />
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器正在采集" IsChecked="{Binding SelectedNode.DetectorAcquiring}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="帧率" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FrameRate, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="分辨率" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Resolution, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="检测模块"
|
||||
Visibility="{Binding SelectedNode.IsInspectionModule, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="8,8,8,6">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="Pipeline 名称" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.PipelineName, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="保存参数"
|
||||
Visibility="{Binding SelectedNode.IsSaveNodeWithImage, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="8,8,8,6">
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="射线源开启" IsChecked="{Binding SelectedNode.IsRayOn}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="电压 (kV)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Voltage, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="功率 (W)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Power, UpdateSourceTrigger=LostFocus}" />
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器已连接" IsChecked="{Binding SelectedNode.DetectorConnected}" />
|
||||
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器正在采集" IsChecked="{Binding SelectedNode.DetectorAcquiring}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="帧率" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FrameRate, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="分辨率" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Resolution, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="图像文件" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.ImageFileName, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="检测标记"
|
||||
Visibility="{Binding SelectedNode.IsInspectionMarker, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="8,8,8,6">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="标记类型" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.MarkerType, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="MarkerX" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.MarkerX, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="MarkerY" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.MarkerY, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="检测标记"
|
||||
Visibility="{Binding SelectedNode.IsInspectionMarker, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="8,8,8,6">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="标记类型" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.MarkerType, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="MarkerX" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.MarkerX, UpdateSourceTrigger=LostFocus}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="MarkerY" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.MarkerY, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="消息弹窗"
|
||||
Visibility="{Binding SelectedNode.IsPauseDialog, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="8,8,8,6">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="标题" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DialogTitle, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="消息内容" />
|
||||
<TextBox
|
||||
MinHeight="68"
|
||||
Margin="0,0,0,8"
|
||||
Padding="8,6"
|
||||
AcceptsReturn="True"
|
||||
BorderBrush="#CFCFCF"
|
||||
BorderThickness="1"
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="11"
|
||||
Text="{Binding SelectedNode.DialogMessage, UpdateSourceTrigger=PropertyChanged}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="消息弹窗"
|
||||
Visibility="{Binding SelectedNode.IsPauseDialog, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="8,8,8,6">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="标题" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DialogTitle, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="消息内容" />
|
||||
<TextBox
|
||||
MinHeight="68"
|
||||
Margin="0,0,0,8"
|
||||
Padding="8,6"
|
||||
AcceptsReturn="True"
|
||||
BorderBrush="#CFCFCF"
|
||||
BorderThickness="1"
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="11"
|
||||
Text="{Binding SelectedNode.DialogMessage, UpdateSourceTrigger=PropertyChanged}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="等待参数"
|
||||
Visibility="{Binding SelectedNode.IsWaitDelay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="10,8,10,6">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="延时 (ms)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DelayMilliseconds, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
|
||||
<Border
|
||||
Padding="12"
|
||||
Background="#FAFAFA"
|
||||
BorderBrush="#E6E6E6"
|
||||
BorderThickness="1"
|
||||
CornerRadius="6"
|
||||
Visibility="{Binding SelectedNode, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Invert}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Text="未选择节点"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="10"
|
||||
Foreground="#666666"
|
||||
Text="从左侧树中选择一个节点后,这里会显示可编辑的参数。"
|
||||
TextWrapping="Wrap" />
|
||||
<GroupBox
|
||||
Style="{StaticResource CompactGroupBox}"
|
||||
Header="等待参数"
|
||||
Visibility="{Binding SelectedNode.IsWaitDelay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel Margin="10,8,10,6">
|
||||
<TextBlock Style="{StaticResource LabelStyle}" Text="延时 (ms)" />
|
||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DelayMilliseconds, UpdateSourceTrigger=LostFocus}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
<Rectangle
|
||||
Grid.Column="3"
|
||||
Width="1"
|
||||
Fill="{StaticResource SeparatorBrush}" />
|
||||
|
||||
<Grid Grid.Column="4">
|
||||
<views:PipelineEditorView
|
||||
x:Name="InspectionModulePipelineEditor"
|
||||
Margin="0"
|
||||
Visibility="{Binding EditorVisibility}" />
|
||||
|
||||
<Border
|
||||
x:Name="InspectionModulePipelineEmptyState"
|
||||
x:Name="NodePropertyEmptyState"
|
||||
Margin="12"
|
||||
Padding="16"
|
||||
Background="#FAFAFA"
|
||||
BorderBrush="#E6E6E6"
|
||||
BorderThickness="1"
|
||||
CornerRadius="6"
|
||||
Visibility="{Binding EmptyStateVisibility}">
|
||||
CornerRadius="6">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Text="未选择检测模块"
|
||||
Text="未选择节点"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
FontFamily="{StaticResource UiFont}"
|
||||
FontSize="10"
|
||||
Foreground="#666666"
|
||||
Text="请选择一个检测模块节点后,在这里拖拽算子并配置参数。"
|
||||
Text="从左侧树中选择一个节点后,这里会显示对应的参数或检测流程。"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using Prism.Ioc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XplorePlane.Services;
|
||||
using XplorePlane.ViewModels.Cnc;
|
||||
@@ -16,7 +19,17 @@ namespace XplorePlane.Views.Cnc
|
||||
/// </summary>
|
||||
public partial class CncPageView : UserControl
|
||||
{
|
||||
private static readonly Brush SelectedNodeBackground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#E7F0F7"));
|
||||
private static readonly Brush SelectedNodeBorder = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#9CB9D1"));
|
||||
private static readonly Brush HoverNodeBackground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F6FAFC"));
|
||||
private static readonly Brush HoverNodeBorder = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#D7E4EE"));
|
||||
private static readonly Brush SelectedNodeForeground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#1F4E79"));
|
||||
private static readonly Brush DefaultNodeForeground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#202020"));
|
||||
private static readonly Brush TransparentBrush = Brushes.Transparent;
|
||||
|
||||
private CncInspectionModulePipelineViewModel _inspectionModulePipelineViewModel;
|
||||
private readonly Dictionary<TextBox, Label> _textDisplayLabels = new();
|
||||
private readonly Dictionary<CheckBox, Label> _checkDisplayLabels = new();
|
||||
|
||||
public CncPageView()
|
||||
{
|
||||
@@ -56,12 +69,18 @@ namespace XplorePlane.Views.Cnc
|
||||
logger);
|
||||
|
||||
InspectionModulePipelineEditor.DataContext = _inspectionModulePipelineViewModel;
|
||||
InspectionModulePipelineEmptyState.DataContext = _inspectionModulePipelineViewModel;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// keep page usable even if pipeline editor host setup fails
|
||||
}
|
||||
|
||||
CncTreeView.ContextMenuOpening -= CncTreeView_ContextMenuOpening;
|
||||
CncTreeView.ContextMenuOpening += CncTreeView_ContextMenuOpening;
|
||||
CncTreeView.LayoutUpdated -= CncTreeView_LayoutUpdated;
|
||||
CncTreeView.LayoutUpdated += CncTreeView_LayoutUpdated;
|
||||
UpdateNodeVisualState();
|
||||
UpdatePropertyEditorState();
|
||||
}
|
||||
|
||||
private void CncTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
@@ -70,6 +89,9 @@ namespace XplorePlane.Views.Cnc
|
||||
{
|
||||
viewModel.SelectedNode = e.NewValue as CncNodeViewModel;
|
||||
}
|
||||
|
||||
UpdateNodeVisualState();
|
||||
UpdatePropertyEditorState();
|
||||
}
|
||||
|
||||
private void CncTreeView_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||
@@ -83,6 +105,280 @@ namespace XplorePlane.Views.Cnc
|
||||
viewModel.DeleteNodeCommand.Execute();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
CncTreeView.ContextMenu = null;
|
||||
return;
|
||||
}
|
||||
|
||||
viewModel.SelectedNode = nodeVm;
|
||||
UpdateNodeVisualState();
|
||||
UpdatePropertyEditorState();
|
||||
|
||||
CncTreeView.ContextMenu = new ContextMenu
|
||||
{
|
||||
Items =
|
||||
{
|
||||
new MenuItem
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void CncTreeView_LayoutUpdated(object sender, EventArgs e)
|
||||
{
|
||||
HideInlineDeleteButtons();
|
||||
UpdateNodeVisualState();
|
||||
UpdatePropertyEditorState();
|
||||
}
|
||||
|
||||
private void HideInlineDeleteButtons()
|
||||
{
|
||||
foreach (var button in FindVisualDescendants<Button>(CncTreeView))
|
||||
{
|
||||
if (button.ToolTip is string)
|
||||
{
|
||||
button.Visibility = Visibility.Collapsed;
|
||||
button.IsHitTestVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (item.IsSelected)
|
||||
{
|
||||
card.Background = SelectedNodeBackground;
|
||||
card.BorderBrush = SelectedNodeBorder;
|
||||
ApplyNodeTextForeground(card, SelectedNodeForeground);
|
||||
}
|
||||
else if (card.IsMouseOver)
|
||||
{
|
||||
card.Background = HoverNodeBackground;
|
||||
card.BorderBrush = HoverNodeBorder;
|
||||
ApplyNodeTextForeground(card, DefaultNodeForeground);
|
||||
}
|
||||
else
|
||||
{
|
||||
card.Background = TransparentBrush;
|
||||
card.BorderBrush = TransparentBrush;
|
||||
ApplyNodeTextForeground(card, DefaultNodeForeground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePropertyEditorState()
|
||||
{
|
||||
if (DataContext is not CncEditorViewModel viewModel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var propertyEditorRoot = FindPropertyEditorRoot();
|
||||
if (propertyEditorRoot == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool isReadOnlyNode = viewModel.SelectedNode?.IsReadOnlyNodeProperties == true;
|
||||
bool showNodeProperties = viewModel.SelectedNode != null && !viewModel.SelectedNode.IsInspectionModule;
|
||||
|
||||
NodePropertyEditor.Visibility = showNodeProperties ? Visibility.Visible : Visibility.Collapsed;
|
||||
NodePropertyEmptyState.Visibility = viewModel.SelectedNode == null ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
foreach (var textBox in FindVisualDescendants<TextBox>(propertyEditorRoot))
|
||||
{
|
||||
var bindingExpression = textBox.GetBindingExpression(TextBox.TextProperty);
|
||||
string bindingPath = bindingExpression?.ParentBinding?.Path?.Path ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(bindingPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool alwaysDisplay = bindingPath is "SelectedNode.Index" or "SelectedNode.NodeTypeDisplay";
|
||||
var label = EnsureTextDisplayLabel(textBox, bindingPath);
|
||||
bool showLabel = alwaysDisplay || isReadOnlyNode;
|
||||
|
||||
textBox.IsReadOnly = showLabel;
|
||||
textBox.Visibility = showLabel ? Visibility.Collapsed : Visibility.Visible;
|
||||
label.Visibility = showLabel ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
foreach (var checkBox in FindVisualDescendants<CheckBox>(propertyEditorRoot))
|
||||
{
|
||||
var bindingExpression = checkBox.GetBindingExpression(ToggleButton.IsCheckedProperty);
|
||||
string bindingPath = bindingExpression?.ParentBinding?.Path?.Path ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(bindingPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var label = EnsureCheckDisplayLabel(checkBox, bindingPath);
|
||||
checkBox.IsEnabled = !isReadOnlyNode;
|
||||
checkBox.Visibility = isReadOnlyNode ? Visibility.Collapsed : Visibility.Visible;
|
||||
label.Visibility = isReadOnlyNode ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private Label EnsureTextDisplayLabel(TextBox textBox, string bindingPath)
|
||||
{
|
||||
if (_textDisplayLabels.TryGetValue(textBox, out var existingLabel))
|
||||
{
|
||||
return existingLabel;
|
||||
}
|
||||
|
||||
var label = new Label
|
||||
{
|
||||
Style = TryFindResource("DisplayValueLabel") as Style,
|
||||
Visibility = Visibility.Collapsed
|
||||
};
|
||||
label.SetBinding(ContentProperty, new Binding(bindingPath));
|
||||
|
||||
InsertCompanionControl(textBox, label);
|
||||
_textDisplayLabels[textBox] = label;
|
||||
return label;
|
||||
}
|
||||
|
||||
private Label EnsureCheckDisplayLabel(CheckBox checkBox, string bindingPath)
|
||||
{
|
||||
if (_checkDisplayLabels.TryGetValue(checkBox, out var existingLabel))
|
||||
{
|
||||
return existingLabel;
|
||||
}
|
||||
|
||||
var label = new Label
|
||||
{
|
||||
Style = TryFindResource("DisplayValueLabel") as Style,
|
||||
Visibility = Visibility.Collapsed
|
||||
};
|
||||
label.SetBinding(ContentProperty, new Binding(bindingPath)
|
||||
{
|
||||
Converter = TryFindResource("BoolToDisplayTextConverter") as IValueConverter
|
||||
});
|
||||
|
||||
InsertCompanionControl(checkBox, label);
|
||||
_checkDisplayLabels[checkBox] = label;
|
||||
return label;
|
||||
}
|
||||
|
||||
private static void InsertCompanionControl(Control sourceControl, Control companionControl)
|
||||
{
|
||||
if (VisualTreeHelper.GetParent(sourceControl) is not Panel panel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (panel.Children.Contains(companionControl))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int index = panel.Children.IndexOf(sourceControl);
|
||||
panel.Children.Insert(index + 1, companionControl);
|
||||
}
|
||||
|
||||
private static void ApplyNodeTextForeground(Border card, Brush foreground)
|
||||
{
|
||||
foreach (var textBlock in FindVisualDescendants<TextBlock>(card))
|
||||
{
|
||||
if (textBlock.Visibility == Visibility.Visible)
|
||||
{
|
||||
textBlock.Foreground = foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DependencyObject FindPropertyEditorRoot()
|
||||
{
|
||||
return NodePropertyEditor;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static T FindAncestor<T>(DependencyObject dependencyObject) where T : DependencyObject
|
||||
{
|
||||
var current = dependencyObject;
|
||||
while (current != null)
|
||||
{
|
||||
if (current is T match)
|
||||
{
|
||||
return match;
|
||||
}
|
||||
|
||||
current = VisualTreeHelper.GetParent(current);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static System.Collections.Generic.IEnumerable<T> FindVisualDescendants<T>(DependencyObject root) where T : DependencyObject
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
int childCount = VisualTreeHelper.GetChildrenCount(root);
|
||||
for (int i = 0; i < childCount; i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(root, i);
|
||||
if (child is T typed)
|
||||
{
|
||||
yield return typed;
|
||||
}
|
||||
|
||||
foreach (var descendant in FindVisualDescendants<T>(child))
|
||||
{
|
||||
yield return descendant;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class NullToVisibilityConverter : IValueConverter
|
||||
@@ -104,4 +400,30 @@ namespace XplorePlane.Views.Cnc
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class InverseBooleanToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value is true ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class BoolToDisplayTextConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value is true ? "\u662F" : "\u5426";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user