mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-04-13 03:16:35 +08:00
新了复制节点粘贴节点的功能(选择需要复制的节点,Ctrl+C、Ctrl+V),以Json文本形式传递数据,所以可以跨进程复制粘贴
This commit is contained in:
@@ -802,6 +802,14 @@ namespace Serein.Library.Api
|
|||||||
JunctionType toNodeJunctionType,
|
JunctionType toNodeJunctionType,
|
||||||
ConnectionArgSourceType argSourceType,
|
ConnectionArgSourceType argSourceType,
|
||||||
int argIndex);
|
int argIndex);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从节点信息集合批量加载节点控件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="List<NodeInfo>">节点集合信息</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task LoadNodeInfosAsync(List<NodeInfo> nodeInfos);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建节点/区域/基础控件
|
/// 创建节点/区域/基础控件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -810,6 +818,9 @@ namespace Serein.Library.Api
|
|||||||
/// <param name="methodDetailsInfo">节点绑定的方法说明</param>
|
/// <param name="methodDetailsInfo">节点绑定的方法说明</param>
|
||||||
Task<NodeInfo> CreateNodeAsync(NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null);
|
Task<NodeInfo> CreateNodeAsync(NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 移除两个节点之间的方法调用关系
|
/// 移除两个节点之间的方法调用关系
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -615,8 +615,10 @@ namespace Serein.NodeFlow.Env
|
|||||||
{
|
{
|
||||||
ordinaryNodes.Add((nodeModel, nodeInfo.Position));
|
ordinaryNodes.Add((nodeModel, nodeInfo.Position));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 加载区域子项
|
// 加载区域子项
|
||||||
foreach ((NodeModelBase region, string[] childNodeGuids) item in regionChildNodes)
|
foreach ((NodeModelBase region, string[] childNodeGuids) item in regionChildNodes)
|
||||||
{
|
{
|
||||||
@@ -889,6 +891,99 @@ namespace Serein.NodeFlow.Env
|
|||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从节点信息集合批量加载节点控件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="List<NodeInfo>">节点信息</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task LoadNodeInfosAsync(List<NodeInfo> 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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 流程正在运行时创建节点
|
/// 流程正在运行时创建节点
|
||||||
|
|||||||
@@ -257,6 +257,19 @@ namespace Serein.NodeFlow.Env
|
|||||||
return (isConnect, remoteMsgUtil);
|
return (isConnect, remoteMsgUtil);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从节点信息集合批量加载节点控件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="List<NodeInfo>">节点信息</param>
|
||||||
|
/// <param name="position">需要加载的位置</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task LoadNodeInfosAsync(List<NodeInfo> nodeInfos)
|
||||||
|
{
|
||||||
|
SetProjectLoadingFlag(false);
|
||||||
|
await currentFlowEnvironment.LoadNodeInfosAsync(nodeInfos); // 装饰器调用
|
||||||
|
SetProjectLoadingFlag(true);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<NodeInfo> CreateNodeAsync(NodeControlType nodeBase, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null)
|
public async Task<NodeInfo> CreateNodeAsync(NodeControlType nodeBase, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null)
|
||||||
{
|
{
|
||||||
SetProjectLoadingFlag(false);
|
SetProjectLoadingFlag(false);
|
||||||
|
|||||||
@@ -696,6 +696,20 @@ namespace Serein.NodeFlow.Env
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从节点信息集合批量加载节点控件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="List<NodeInfo>">节点信息</param>
|
||||||
|
/// <param name="position">需要加载的位置</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task LoadNodeInfosAsync(List<NodeInfo> nodeInfos)
|
||||||
|
{
|
||||||
|
this.WriteLine(InfoType.INFO, "远程环境尚未实现的接口(重要,会尽快实现):LoadNodeInfoAsync");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建节点/区域/基础控件
|
/// 创建节点/区域/基础控件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Serein.Library;
|
using Serein.Library;
|
||||||
using Serein.Library.Api;
|
using Serein.Library.Api;
|
||||||
@@ -12,7 +13,9 @@ using Serein.Workbench.Node;
|
|||||||
using Serein.Workbench.Node.View;
|
using Serein.Workbench.Node.View;
|
||||||
using Serein.Workbench.Node.ViewModel;
|
using Serein.Workbench.Node.ViewModel;
|
||||||
using Serein.Workbench.Themes;
|
using Serein.Workbench.Themes;
|
||||||
|
using Serein.Workbench.Tool;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@@ -23,6 +26,7 @@ using System.Windows.Input;
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Animation;
|
using System.Windows.Media.Animation;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
|
using static Dm.net.buffer.ByteArrayBuffer;
|
||||||
using DataObject = System.Windows.DataObject;
|
using DataObject = System.Windows.DataObject;
|
||||||
|
|
||||||
namespace Serein.Workbench
|
namespace Serein.Workbench
|
||||||
@@ -1998,7 +2002,10 @@ namespace Serein.Workbench
|
|||||||
{
|
{
|
||||||
if (element is NodeControlBase control)
|
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键行为
|
e.Handled = true; // 禁止默认的Tab键行为
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region 复制粘贴选择的节点
|
||||||
|
if (Keyboard.Modifiers == ModifierKeys.Control)
|
||||||
|
{
|
||||||
|
#region 复制节点
|
||||||
|
if (e.Key == Key.C && selectNodeControls.Count > 0)
|
||||||
|
{
|
||||||
|
// 处理复制操作
|
||||||
|
List<NodeInfo> 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<string, string> guids = new Dictionary<string, string>(); // 记录 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<NodeInfo> nodes = JsonConvert.DeserializeObject<List<NodeInfo>>(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))
|
if (e.KeyStates == Keyboard.GetKeyStates(Key.Escape))
|
||||||
{
|
{
|
||||||
IsControlDragging = false;
|
IsControlDragging = false;
|
||||||
@@ -2709,6 +2860,8 @@ namespace Serein.Workbench
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 对象装箱测试
|
/// 对象装箱测试
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
68
Workbench/Tool/GuidReplacer.cs
Normal file
68
Workbench/Tool/GuidReplacer.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Serein.Workbench.Tool
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Guid替换工具类
|
||||||
|
/// </summary>
|
||||||
|
public class GuidReplacer
|
||||||
|
{
|
||||||
|
private class TrieNode
|
||||||
|
{
|
||||||
|
public Dictionary<char, TrieNode> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user