MoveThumb.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Windows;
  4. using System.Windows.Controls.Primitives;
  5. using System.Windows.Input;
  6. using System.Windows.Media;
  7. using TeamAAS.FlowEditor.Models;
  8. namespace TeamAAS.FlowEditor.Controls
  9. {
  10. /// <summary>
  11. /// 节点拖拽手柄 - 处理节点选择和拖动
  12. /// </summary>
  13. public class MoveThumb : Thumb
  14. {
  15. private FlowCanvas _canvas;
  16. // 拖拽锚点:起点时鼠标在画布坐标系的位置 + 各节点的起始坐标。
  17. // 用 Mouse.GetPosition(画布) 实时计算位移,而不是累加 DragDelta——
  18. // 后者在 PerMonitorV2 + 高 DPI 下是物理像素,会导致节点移动速度 ≠ 鼠标(4K 200% 缩放下节点跑飞)。
  19. private Point _dragStartMouseOnCanvas;
  20. private readonly List<FlowNode> _dragNodes = new List<FlowNode>();
  21. private readonly List<double> _startXs = new List<double>();
  22. private readonly List<double> _startYs = new List<double>();
  23. private int _diagCounter; // TODO 拖拽诊断,问题定位后删除
  24. public MoveThumb()
  25. {
  26. DragStarted += OnDragStartedHandler;
  27. DragDelta += OnDragDeltaHandler;
  28. DragCompleted += OnDragCompletedHandler;
  29. }
  30. private FlowCanvas FindCanvas()
  31. {
  32. if (_canvas != null) return _canvas;
  33. DependencyObject parent = VisualTreeHelper.GetParent(this);
  34. while (parent != null && !(parent is FlowCanvas))
  35. parent = VisualTreeHelper.GetParent(parent);
  36. _canvas = parent as FlowCanvas;
  37. return _canvas;
  38. }
  39. protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
  40. {
  41. // 选择由 FlowCanvas.OnMouseLeftButtonDown 统一处理(含 Ctrl 多选)
  42. base.OnMouseLeftButtonDown(e);
  43. }
  44. private void OnDragStartedHandler(object sender, DragStartedEventArgs e)
  45. {
  46. var canvas = FindCanvas();
  47. var node = DataContext as FlowNode;
  48. if (canvas == null || node == null) return;
  49. _dragStartMouseOnCanvas = Mouse.GetPosition(canvas);
  50. _dragNodes.Clear();
  51. _startXs.Clear();
  52. _startYs.Clear();
  53. var selectedNodes = canvas.GetSelectedNodes();
  54. if (selectedNodes != null && selectedNodes.Count > 1 && selectedNodes.Contains(node))
  55. {
  56. foreach (var selNode in selectedNodes)
  57. {
  58. _dragNodes.Add(selNode);
  59. _startXs.Add(selNode.X);
  60. _startYs.Add(selNode.Y);
  61. }
  62. }
  63. else
  64. {
  65. _dragNodes.Add(node);
  66. _startXs.Add(node.X);
  67. _startYs.Add(node.Y);
  68. }
  69. // TODO 拖拽诊断,问题定位后删除
  70. AppLogger.Info($"[DragDiag] started node={node.NodeName} mouseOnCanvas=({_dragStartMouseOnCanvas.X:F1},{_dragStartMouseOnCanvas.Y:F1}) nodePos=({node.X:F1},{node.Y:F1})", "DragDiag");
  71. }
  72. private void OnDragDeltaHandler(object sender, DragDeltaEventArgs e)
  73. {
  74. var node = DataContext as FlowNode;
  75. if (node == null) return;
  76. var canvas = FindCanvas();
  77. // 流程运行中 / 只读监控模式禁止移动节点
  78. if (canvas != null && (canvas.IsRunning || canvas.IsReadOnly)) return;
  79. if (_dragNodes.Count == 0) return;
  80. // 鼠标相对拖拽起点的位移(画布本地坐标,DPI / 缩放换算均正确)
  81. var mouse = Mouse.GetPosition(canvas);
  82. double dx = mouse.X - _dragStartMouseOnCanvas.X;
  83. double dy = mouse.Y - _dragStartMouseOnCanvas.Y;
  84. // TODO 拖拽诊断,问题定位后删除
  85. if ((++_diagCounter % 10) == 0)
  86. AppLogger.Info($"[DragDiag] delta mouse=({mouse.X:F1},{mouse.Y:F1}) d=({dx:F1},{dy:F1}) node0=({_dragNodes[0].X:F1},{_dragNodes[0].Y:F1}) startPos=({_startXs[0]:F1},{_startYs[0]:F1})", "DragDiag");
  87. if (_dragNodes.Count > 1)
  88. {
  89. // 整组硬墙语义:先预判每个成员的目标位置,
  90. // 只要有一个成员越出画布边界、或与非同组成员的节点碰撞,
  91. // 整组本次一动不动(碰壁即停),之后把鼠标拖回自由方向才能继续移动。
  92. // 同组成员以相同位移整体平移、相对位置不变,彼此不可能相撞,故不算障碍。
  93. for (int i = 0; i < _dragNodes.Count; i++)
  94. {
  95. double sx = _startXs[i] + dx;
  96. double sy = _startYs[i] + dy;
  97. if (sx < 0 || sy < 0 || canvas.CheckNodeCollision(_dragNodes[i], Math.Max(0, sx), Math.Max(0, sy), _dragNodes))
  98. return;
  99. }
  100. for (int i = 0; i < _dragNodes.Count; i++)
  101. {
  102. _dragNodes[i].X = Math.Max(0, _startXs[i] + dx);
  103. _dragNodes[i].Y = Math.Max(0, _startYs[i] + dy);
  104. }
  105. canvas.UpdateCanvasSize();
  106. return;
  107. }
  108. // 单选拖拽
  109. double newX = Math.Max(0, _startXs[0] + dx);
  110. double newY = Math.Max(0, _startYs[0] + dy);
  111. // 节点碰撞检测
  112. if (canvas != null)
  113. {
  114. if (canvas.CheckNodeCollision(node, newX, newY))
  115. {
  116. // 尝试只移动X
  117. if (!canvas.CheckNodeCollision(node, newX, node.Y))
  118. node.X = newX;
  119. // 尝试只移动Y
  120. else if (!canvas.CheckNodeCollision(node, node.X, newY))
  121. node.Y = newY;
  122. return;
  123. }
  124. // 对齐引导线检测
  125. canvas.CheckAlignment(node, ref newX, ref newY);
  126. }
  127. node.X = newX;
  128. node.Y = newY;
  129. // 自动拉线预览
  130. canvas?.UpdateAutoConnectPreview(node);
  131. // 通知画布更新大小
  132. canvas?.UpdateCanvasSize();
  133. }
  134. private void OnDragCompletedHandler(object sender, DragCompletedEventArgs e)
  135. {
  136. var canvas = FindCanvas();
  137. if (canvas == null) return;
  138. canvas.ClearAlignLines();
  139. // 自动拉线:仅单节点拖拽时生效(多节点移动不触发)
  140. var node = DataContext as FlowNode;
  141. var selectedNodes = canvas.GetSelectedNodes();
  142. if (node != null && (selectedNodes == null || selectedNodes.Count <= 1))
  143. canvas.TryAutoConnect(node);
  144. }
  145. }
  146. }