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(); + } + } + +}