增加节点右键菜单功能,支持上方插入和下方插入
This commit is contained in:
@@ -36,6 +36,8 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
private string _programName;
|
private string _programName;
|
||||||
private string _programDisplayName = "新建检测程序.xp";
|
private string _programDisplayName = "新建检测程序.xp";
|
||||||
private Guid? _preferredSelectedNodeId;
|
private Guid? _preferredSelectedNodeId;
|
||||||
|
private Guid? _pendingInsertAnchorNodeId;
|
||||||
|
private bool _pendingInsertAfterAnchor;
|
||||||
|
|
||||||
public CncEditorViewModel(
|
public CncEditorViewModel(
|
||||||
ICncProgramService cncProgramService,
|
ICncProgramService cncProgramService,
|
||||||
@@ -69,6 +71,8 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
.ObservesProperty(() => SelectedNode);
|
.ObservesProperty(() => SelectedNode);
|
||||||
MoveNodeUpCommand = new DelegateCommand<CncNodeViewModel>(ExecuteMoveNodeUp);
|
MoveNodeUpCommand = new DelegateCommand<CncNodeViewModel>(ExecuteMoveNodeUp);
|
||||||
MoveNodeDownCommand = new DelegateCommand<CncNodeViewModel>(ExecuteMoveNodeDown);
|
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());
|
SaveProgramCommand = new DelegateCommand(async () => await ExecuteSaveProgramAsync());
|
||||||
LoadProgramCommand = new DelegateCommand(async () => await ExecuteLoadProgramAsync());
|
LoadProgramCommand = new DelegateCommand(async () => await ExecuteLoadProgramAsync());
|
||||||
@@ -144,6 +148,8 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
public DelegateCommand DeleteNodeCommand { get; }
|
public DelegateCommand DeleteNodeCommand { get; }
|
||||||
public DelegateCommand<CncNodeViewModel> MoveNodeUpCommand { get; }
|
public DelegateCommand<CncNodeViewModel> MoveNodeUpCommand { get; }
|
||||||
public DelegateCommand<CncNodeViewModel> MoveNodeDownCommand { get; }
|
public DelegateCommand<CncNodeViewModel> MoveNodeDownCommand { get; }
|
||||||
|
public DelegateCommand<CncNodeViewModel> PrepareInsertAboveCommand { get; }
|
||||||
|
public DelegateCommand<CncNodeViewModel> PrepareInsertBelowCommand { get; }
|
||||||
public DelegateCommand SaveProgramCommand { get; }
|
public DelegateCommand SaveProgramCommand { get; }
|
||||||
public DelegateCommand LoadProgramCommand { get; }
|
public DelegateCommand LoadProgramCommand { get; }
|
||||||
public DelegateCommand NewProgramCommand { get; }
|
public DelegateCommand NewProgramCommand { get; }
|
||||||
@@ -162,6 +168,7 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
int afterIndex = ResolveInsertAfterIndex(nodeType);
|
int afterIndex = ResolveInsertAfterIndex(nodeType);
|
||||||
_currentProgram = _cncProgramService.InsertNode(_currentProgram, afterIndex, node);
|
_currentProgram = _cncProgramService.InsertNode(_currentProgram, afterIndex, node);
|
||||||
_preferredSelectedNodeId = node.Id;
|
_preferredSelectedNodeId = node.Id;
|
||||||
|
ClearPendingInsertAnchor();
|
||||||
|
|
||||||
OnProgramEdited();
|
OnProgramEdited();
|
||||||
_logger.Info("Inserted node: Type={NodeType}", nodeType);
|
_logger.Info("Inserted node: Type={NodeType}", nodeType);
|
||||||
@@ -179,21 +186,24 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
int deletedIndex = SelectedNode.Index;
|
||||||
|
|
||||||
if (SelectedNode.IsSavePosition)
|
if (SelectedNode.IsSavePosition)
|
||||||
{
|
{
|
||||||
var nodes = _currentProgram.Nodes.ToList();
|
var nodes = _currentProgram.Nodes.ToList();
|
||||||
int startIndex = SelectedNode.Index;
|
int startIndex = deletedIndex;
|
||||||
int endIndex = GetSavePositionBlockEndIndex(startIndex);
|
int endIndex = GetSavePositionBlockEndIndex(startIndex);
|
||||||
nodes.RemoveRange(startIndex, endIndex - startIndex + 1);
|
nodes.RemoveRange(startIndex, endIndex - startIndex + 1);
|
||||||
_currentProgram = ReplaceProgramNodes(nodes);
|
_currentProgram = ReplaceProgramNodes(nodes);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_currentProgram = _cncProgramService.RemoveNode(_currentProgram, SelectedNode.Index);
|
_currentProgram = _cncProgramService.RemoveNode(_currentProgram, deletedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
OnProgramEdited();
|
OnProgramEdited();
|
||||||
_logger.Info("Deleted node at index: {Index}", SelectedNode.Index);
|
ClearPendingInsertAnchorIfMissing();
|
||||||
|
_logger.Info("Deleted node at index: {Index}", deletedIndex);
|
||||||
}
|
}
|
||||||
catch (ArgumentOutOfRangeException ex)
|
catch (ArgumentOutOfRangeException ex)
|
||||||
{
|
{
|
||||||
@@ -305,6 +315,7 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
ProgramName = _currentProgram.Name;
|
ProgramName = _currentProgram.Name;
|
||||||
ProgramDisplayName = Path.GetFileName(dlg.FileName);
|
ProgramDisplayName = Path.GetFileName(dlg.FileName);
|
||||||
IsModified = false;
|
IsModified = false;
|
||||||
|
ClearPendingInsertAnchor();
|
||||||
RefreshNodes();
|
RefreshNodes();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -320,6 +331,7 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
ProgramName = _currentProgram.Name;
|
ProgramName = _currentProgram.Name;
|
||||||
ProgramDisplayName = FormatProgramDisplayName(_currentProgram.Name);
|
ProgramDisplayName = FormatProgramDisplayName(_currentProgram.Name);
|
||||||
IsModified = false;
|
IsModified = false;
|
||||||
|
ClearPendingInsertAnchor();
|
||||||
RefreshNodes();
|
RefreshNodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,6 +473,11 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TryResolvePendingInsertAfterIndex(nodeType, out int pendingAfterIndex))
|
||||||
|
{
|
||||||
|
return pendingAfterIndex;
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsSavePositionChild(nodeType))
|
if (!IsSavePositionChild(nodeType))
|
||||||
{
|
{
|
||||||
return SelectedNode?.Index ?? _currentProgram.Nodes.Count - 1;
|
return SelectedNode?.Index ?? _currentProgram.Nodes.Count - 1;
|
||||||
@@ -475,6 +492,75 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
return GetSavePositionBlockEndIndex(savePositionIndex.Value);
|
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)
|
private void MoveSavePositionChild(CncNodeViewModel nodeVm, bool moveDown)
|
||||||
{
|
{
|
||||||
int? parentIndex = FindOwningSavePositionIndex(nodeVm.Index);
|
int? parentIndex = FindOwningSavePositionIndex(nodeVm.Index);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Windows.Media;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
@@ -16,6 +17,11 @@ namespace XplorePlane.Views.Cnc
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class CncPageView : UserControl
|
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 TransparentBrush = Brushes.Transparent;
|
||||||
private CncInspectionModulePipelineViewModel _inspectionModulePipelineViewModel;
|
private CncInspectionModulePipelineViewModel _inspectionModulePipelineViewModel;
|
||||||
|
|
||||||
public CncPageView()
|
public CncPageView()
|
||||||
@@ -62,6 +68,12 @@ namespace XplorePlane.Views.Cnc
|
|||||||
{
|
{
|
||||||
// keep page usable even if pipeline editor host setup fails
|
// 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CncTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
private void CncTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||||
@@ -70,6 +82,8 @@ namespace XplorePlane.Views.Cnc
|
|||||||
{
|
{
|
||||||
viewModel.SelectedNode = e.NewValue as CncNodeViewModel;
|
viewModel.SelectedNode = e.NewValue as CncNodeViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateNodeVisualState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CncTreeView_PreviewKeyDown(object sender, KeyEventArgs e)
|
private void CncTreeView_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||||
@@ -83,6 +97,148 @@ namespace XplorePlane.Views.Cnc
|
|||||||
viewModel.DeleteNodeCommand.Execute();
|
viewModel.DeleteNodeCommand.Execute();
|
||||||
e.Handled = true;
|
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();
|
||||||
|
|
||||||
|
CncTreeView.ContextMenu = new ContextMenu
|
||||||
|
{
|
||||||
|
Items =
|
||||||
|
{
|
||||||
|
new MenuItem
|
||||||
|
{
|
||||||
|
Header = "在上方插入位置",
|
||||||
|
Command = viewModel.PrepareInsertAboveCommand,
|
||||||
|
CommandParameter = nodeVm
|
||||||
|
},
|
||||||
|
new MenuItem
|
||||||
|
{
|
||||||
|
Header = "在下方插入位置",
|
||||||
|
Command = viewModel.PrepareInsertBelowCommand,
|
||||||
|
CommandParameter = nodeVm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CncTreeView_LayoutUpdated(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
HideInlineDeleteButtons();
|
||||||
|
UpdateNodeVisualState();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
else if (card.IsMouseOver)
|
||||||
|
{
|
||||||
|
card.Background = HoverNodeBackground;
|
||||||
|
card.BorderBrush = HoverNodeBorder;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
card.Background = TransparentBrush;
|
||||||
|
card.BorderBrush = TransparentBrush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
public class NullToVisibilityConverter : IValueConverter
|
||||||
|
|||||||
Reference in New Issue
Block a user