diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs
index 21de154..1f4989d 100644
--- a/Library/Api/IFlowEnvironment.cs
+++ b/Library/Api/IFlowEnvironment.cs
@@ -802,6 +802,14 @@ namespace Serein.Library.Api
JunctionType toNodeJunctionType,
ConnectionArgSourceType argSourceType,
int argIndex);
+
+ ///
+ /// 从节点信息集合批量加载节点控件
+ ///
+ /// 节点集合信息
+ ///
+ Task LoadNodeInfosAsync(List nodeInfos);
+
///
/// 创建节点/区域/基础控件
///
@@ -810,6 +818,9 @@ namespace Serein.Library.Api
/// 节点绑定的方法说明
Task CreateNodeAsync(NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null);
+
+
+
///
/// 移除两个节点之间的方法调用关系
///
diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs
index 4930c24..bfa4f56 100644
--- a/NodeFlow/Env/FlowEnvironment.cs
+++ b/NodeFlow/Env/FlowEnvironment.cs
@@ -615,8 +615,10 @@ namespace Serein.NodeFlow.Env
{
ordinaryNodes.Add((nodeModel, nodeInfo.Position));
}
-
}
+
+
+
// 加载区域子项
foreach ((NodeModelBase region, string[] childNodeGuids) item in regionChildNodes)
{
@@ -889,6 +891,99 @@ namespace Serein.NodeFlow.Env
//}
}
+ ///
+ /// 从节点信息集合批量加载节点控件
+ ///
+ /// 节点信息
+ ///
+ public Task LoadNodeInfosAsync(List nodeInfos)
+ {
+ List<(NodeModelBase, string[])> regionChildNodes = new List<(NodeModelBase, string[])>();
+ List<(NodeModelBase, PositionOfUI)> ordinaryNodes = new List<(NodeModelBase, PositionOfUI)>();
+ // 加载节点
+ foreach (NodeInfo? nodeInfo in nodeInfos)
+ {
+ NodeControlType controlType = FlowFunc.GetNodeControlType(nodeInfo);
+ if (controlType == NodeControlType.None)
+ {
+ continue;
+ }
+ MethodDetails? methodDetails = null;
+
+ if (controlType.IsBaseNode())
+ {
+ // 加载基础节点
+ methodDetails = new MethodDetails();
+ }
+ else
+ {
+ // 加载方法节点
+ if (string.IsNullOrEmpty(nodeInfo.AssemblyName) && string.IsNullOrEmpty(nodeInfo.MethodName))
+ {
+ continue;
+ }
+ FlowLibraryManagement.TryGetMethodDetails(nodeInfo.AssemblyName, nodeInfo.MethodName, out methodDetails); // 加载项目时尝试获取方法信息
+ }
+
+ var nodeModel = FlowFunc.CreateNode(this, controlType, methodDetails); // 加载项目时创建节点
+ nodeModel.LoadInfo(nodeInfo); // 创建节点model
+ if (nodeModel is null)
+ {
+ nodeInfo.Guid = string.Empty;
+ continue;
+ }
+
+ TryAddNode(nodeModel); // 加载项目时将节点加载到环境中
+ if (nodeInfo.ChildNodeGuids?.Length > 0)
+ {
+ regionChildNodes.Add((nodeModel, nodeInfo.ChildNodeGuids));
+
+ UIContextOperation?.Invoke(() => OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, nodeInfo.Position)));
+ }
+ else
+ {
+ ordinaryNodes.Add((nodeModel, nodeInfo.Position));
+ }
+ }
+
+
+
+ // 加载区域子项
+ foreach ((NodeModelBase region, string[] childNodeGuids) item in regionChildNodes)
+ {
+ foreach (var childNodeGuid in item.childNodeGuids)
+ {
+ NodeModels.TryGetValue(childNodeGuid, out NodeModelBase? childNode);
+ if (childNode is null)
+ {
+ // 节点尚未加载
+ continue;
+ }
+ UIContextOperation?.Invoke(() => OnNodeCreate?.Invoke(new NodeCreateEventArgs(childNode, true, item.region.Guid)));
+ // 存在节点
+
+ }
+ }
+ // 加载节点
+ foreach ((NodeModelBase nodeModel, PositionOfUI position) item in ordinaryNodes)
+ {
+ bool IsContinue = false;
+ foreach ((NodeModelBase region, string[] childNodeGuids) item2 in regionChildNodes)
+ {
+ foreach (var childNodeGuid in item2.childNodeGuids)
+ {
+ if (item.nodeModel.Guid.Equals(childNodeGuid))
+ {
+ IsContinue = true;
+ }
+ }
+ }
+ if (IsContinue) continue;
+ UIContextOperation?.Invoke(() => OnNodeCreate?.Invoke(new NodeCreateEventArgs(item.nodeModel, item.position)));
+
+ }
+ return Task.CompletedTask;
+ }
///
/// 流程正在运行时创建节点
diff --git a/NodeFlow/Env/FlowEnvironmentDecorator.cs b/NodeFlow/Env/FlowEnvironmentDecorator.cs
index d6f850a..da4ad6c 100644
--- a/NodeFlow/Env/FlowEnvironmentDecorator.cs
+++ b/NodeFlow/Env/FlowEnvironmentDecorator.cs
@@ -257,6 +257,19 @@ namespace Serein.NodeFlow.Env
return (isConnect, remoteMsgUtil);
}
+ ///
+ /// 从节点信息集合批量加载节点控件
+ ///
+ /// 节点信息
+ /// 需要加载的位置
+ ///
+ public async Task LoadNodeInfosAsync(List nodeInfos)
+ {
+ SetProjectLoadingFlag(false);
+ await currentFlowEnvironment.LoadNodeInfosAsync(nodeInfos); // 装饰器调用
+ SetProjectLoadingFlag(true);
+ }
+
public async Task CreateNodeAsync(NodeControlType nodeBase, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null)
{
SetProjectLoadingFlag(false);
diff --git a/NodeFlow/Env/RemoteFlowEnvironment.cs b/NodeFlow/Env/RemoteFlowEnvironment.cs
index 61c8bec..95c3bc2 100644
--- a/NodeFlow/Env/RemoteFlowEnvironment.cs
+++ b/NodeFlow/Env/RemoteFlowEnvironment.cs
@@ -696,6 +696,20 @@ namespace Serein.NodeFlow.Env
return result;
}
+
+ ///
+ /// 从节点信息集合批量加载节点控件
+ ///
+ /// 节点信息
+ /// 需要加载的位置
+ ///
+ public async Task LoadNodeInfosAsync(List nodeInfos)
+ {
+ this.WriteLine(InfoType.INFO, "远程环境尚未实现的接口(重要,会尽快实现):LoadNodeInfoAsync");
+ }
+
+
+
///
/// 创建节点/区域/基础控件
///
diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs
index 8c5e99c..dea17de 100644
--- a/WorkBench/MainWindow.xaml.cs
+++ b/WorkBench/MainWindow.xaml.cs
@@ -1,4 +1,5 @@
using Microsoft.Win32;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serein.Library;
using Serein.Library.Api;
@@ -12,7 +13,9 @@ using Serein.Workbench.Node;
using Serein.Workbench.Node.View;
using Serein.Workbench.Node.ViewModel;
using Serein.Workbench.Themes;
+using Serein.Workbench.Tool;
using System;
+using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
@@ -23,6 +26,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
+using static Dm.net.buffer.ByteArrayBuffer;
using DataObject = System.Windows.DataObject;
namespace Serein.Workbench
@@ -1998,7 +2002,10 @@ namespace Serein.Workbench
{
if (element is NodeControlBase control)
{
- selectNodeControls.Add(control);
+ if (!selectNodeControls.Contains(control))
+ {
+ selectNodeControls.Add(control);
+ }
}
}
}
@@ -2659,6 +2666,150 @@ namespace Serein.Workbench
e.Handled = true; // 禁止默认的Tab键行为
}
+ #region 复制粘贴选择的节点
+ if (Keyboard.Modifiers == ModifierKeys.Control)
+ {
+ #region 复制节点
+ if (e.Key == Key.C && selectNodeControls.Count > 0)
+ {
+ // 处理复制操作
+ List selectNodeInfos = selectNodeControls.Select(control => control.ViewModel.NodeModel.ToInfo()).ToList();
+
+ /*foreach (var node in selectNodeInfos.ToArray())
+ {
+ // 遍历这些节点的子节点,获得完整的已选节点信息
+ foreach (var childNodeGuid in node.ChildNodeGuids)
+ {
+ if (!string.IsNullOrEmpty(childNodeGuid)
+ && NodeControls.TryGetValue(childNodeGuid, out var nodeControl))
+ {
+
+ var newNodeInfo = nodeControl.ViewModel.NodeModel.ToInfo();
+ selectNodeInfos.Add(newNodeInfo);
+ }
+ }
+ }*/
+
+
+ Dictionary guids = new Dictionary(); // 记录 Guid
+ // 遍历当前已选节点
+ foreach (var node in selectNodeInfos.ToArray())
+ {
+ if (!guids.ContainsKey(node.Guid))
+ {
+ // 如果是没出现过的Guid,则记录并新增对应的映射。
+ guids.TryAdd(node.Guid, Guid.NewGuid().ToString());
+ }
+ else
+ {
+ // 出现过的Guid,说明重复添加了。应该不会走到这。
+ continue;
+ }
+
+ if(node.ChildNodeGuids is null)
+ {
+ continue; // 跳过没有子节点的节点
+ }
+
+ // 遍历这些节点的子节点,获得完整的已选节点信息
+ foreach (var childNodeGuid in node.ChildNodeGuids)
+ {
+ if (!guids.ContainsKey(childNodeGuid))
+ {
+ // 如果是没出现过的Guid,则记录并新增对应的映射。
+ guids.TryAdd(node.Guid, Guid.NewGuid().ToString());
+ }
+
+ if (!string.IsNullOrEmpty(childNodeGuid)
+ && NodeControls.TryGetValue(childNodeGuid, out var nodeControl))
+ {
+
+ var newNodeInfo = nodeControl.ViewModel.NodeModel.ToInfo();
+ selectNodeInfos.Add(newNodeInfo);
+ }
+ }
+ }
+
+ var replacer = new GuidReplacer();
+ foreach(var kv in guids)
+ {
+ replacer.AddReplacement(kv.Key, kv.Value);
+ }
+
+ JObject json = new JObject()
+ {
+ ["nodes"] = JArray.FromObject(selectNodeInfos)
+ };
+ var jsonText = json.ToString();
+
+ string result = replacer.Replace(jsonText);
+
+ try
+ {
+ Clipboard.SetDataObject(result, true); // 持久性设置
+ SereinEnv.WriteLine(InfoType.INFO, $"复制已选节点({selectNodeInfos.Count}个)");
+ }
+ catch (Exception ex)
+ {
+ SereinEnv.WriteLine(InfoType.ERROR, $"复制失败:{ex.Message}");
+ }
+
+ //SereinEnv.WriteLine(InfoType.INFO, json.ToString());
+ e.Handled = true;
+ }
+ #endregion
+
+ #region 粘贴节点
+ else if (e.Key == Key.V)
+ {
+
+ if (Clipboard.ContainsText())
+ {
+ string clipboardText = Clipboard.GetText(TextDataFormat.Text);
+
+ List nodes = JsonConvert.DeserializeObject>(JObject.Parse(clipboardText)["nodes"].ToString());
+ if (nodes is not null && nodes.Count >= 0)
+ {
+ Point mousePosition = Mouse.GetPosition(FlowChartCanvas);
+ PositionOfUI positionOfUI = new PositionOfUI(mousePosition.X, mousePosition.Y); // 坐标数据
+ SereinEnv.WriteLine(InfoType.INFO, $"粘贴节点({nodes.Count}个)");
+ // 获取第一个节点的原始位置
+ var index0NodeX = nodes[0].Position.X;
+ var index0NodeY = nodes[0].Position.Y;
+
+ // 计算所有节点相对于第一个节点的偏移量
+ foreach (var node in nodes)
+ {
+
+ var offsetX = node.Position.X - index0NodeX;
+ var offsetY = node.Position.Y - index0NodeY;
+
+ // 根据鼠标位置平移节点
+ node.Position = new PositionOfUI(positionOfUI.X + offsetX, positionOfUI.Y + offsetY);
+ }
+
+ _ = EnvDecorator.LoadNodeInfosAsync(nodes);
+ }
+
+
+ //SereinEnv.WriteLine(InfoType.INFO, $"剪贴板文本内容: {clipboardText}");
+ }
+ else if (Clipboard.ContainsImage())
+ {
+ var image = Clipboard.GetImage();
+ }
+ else
+ {
+ SereinEnv.WriteLine(InfoType.INFO, "剪贴板中没有可识别的数据。");
+ }
+ e.Handled = true;
+ }
+ #endregion
+
+ return;
+ }
+ #endregion
+
if (e.KeyStates == Keyboard.GetKeyStates(Key.Escape))
{
IsControlDragging = false;
@@ -2709,6 +2860,8 @@ namespace Serein.Workbench
}
+
+
///
/// 对象装箱测试
///
diff --git a/Workbench/Tool/GuidReplacer.cs b/Workbench/Tool/GuidReplacer.cs
new file mode 100644
index 0000000..eb2cf77
--- /dev/null
+++ b/Workbench/Tool/GuidReplacer.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Serein.Workbench.Tool
+{
+ ///
+ /// Guid替换工具类
+ ///
+ public class GuidReplacer
+ {
+ private class TrieNode
+ {
+ public Dictionary Children = new();
+ public string Replacement; // 替换后的值
+ }
+
+ private readonly TrieNode _root = new();
+
+ // 构建字典树
+ public void AddReplacement(string guid, string replacement)
+ {
+ var current = _root;
+ foreach (var c in guid)
+ {
+ if (!current.Children.ContainsKey(c))
+ {
+ current.Children[c] = new TrieNode();
+ }
+ current = current.Children[c];
+ }
+ current.Replacement = replacement;
+ }
+
+ // 替换逻辑
+ public string Replace(string input)
+ {
+ var result = new StringBuilder();
+ var current = _root;
+ int i = 0;
+
+ while (i < input.Length)
+ {
+ if (current.Children.ContainsKey(input[i]))
+ {
+ current = current.Children[input[i]];
+ i++;
+
+ if (current.Replacement != null) // 找到匹配
+ {
+ result.Append(current.Replacement);
+ current = _root; // 回到根节点
+ }
+ }
+ else
+ {
+ result.Append(input[i]);
+ current = _root; // 未匹配,回到根节点
+ i++;
+ }
+ }
+ return result.ToString();
+ }
+ }
+
+}