实现了多画布下,节点的复制粘贴功能

This commit is contained in:
fengjiayi
2025-05-27 18:32:40 +08:00
parent 7ad6041be6
commit 7848af0363
53 changed files with 1187 additions and 499 deletions

View File

@@ -26,17 +26,21 @@ namespace Serein.Workbench.Services
/// </summary>
private readonly IFlowEnvironment flowEnvironment;
private readonly IFlowEnvironmentEvent flowEnvironmentEvent;
private readonly UIContextOperation uIContextOperation;
/// <summary>
/// 转发流程运行环境各个事件的实现类
/// </summary>
/// <param name="flowEnvironment"></param>
/// <param name="flowEnvironmentEvent"></param>
/// <param name="uIContextOperation"></param>
public FlowEEForwardingService(IFlowEnvironment flowEnvironment,
IFlowEnvironmentEvent flowEnvironmentEvent)
IFlowEnvironmentEvent flowEnvironmentEvent,
UIContextOperation uIContextOperation)
{
this.flowEnvironment = flowEnvironment;
this.flowEnvironmentEvent = flowEnvironmentEvent;
this.uIContextOperation = uIContextOperation;
InitFlowEnvironmentEvent();
}
@@ -185,7 +189,10 @@ namespace Serein.Workbench.Services
/// <param name="value"></param>
private void FlowEnvironment_OnEnvOutEvent(InfoType type, string value)
{
//LogOutWindow.AppendText($"{DateTime.Now} [{type}] : {value}{Environment.NewLine}");
uIContextOperation.Invoke(() =>
{
OnEnvOut?.Invoke(type, value);
});
}
/// <summary>

View File

@@ -1,4 +1,6 @@
using Serein.Library;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serein.Library;
using Serein.Library.Api;
using Serein.Workbench.Api;
using Serein.Workbench.Node;
@@ -6,7 +8,10 @@ using Serein.Workbench.Node.View;
using Serein.Workbench.Node.ViewModel;
using Serein.Workbench.ViewModels;
using Serein.Workbench.Views;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Serein.Workbench.Services
{
@@ -34,7 +39,6 @@ namespace Serein.Workbench.Services
#endregion
#region
/// <summary>
/// 当前查看的画布
@@ -51,7 +55,6 @@ namespace Serein.Workbench.Services
/// </summary>
public NodeControlType CurrentNodeControlType { get; set; } = NodeControlType.None;
/// <summary>
/// 当前鼠标位置
/// </summary>
@@ -61,6 +64,12 @@ namespace Serein.Workbench.Services
/// 当前选中的节点
/// </summary>
public NodeControlBase? CurrentSelectNodeControl { get; set; }
/// <summary>
/// 连接数据
/// </summary>
public ConnectingData ConnectingData { get; } = new ConnectingData();
#endregion
/// <summary>
@@ -72,6 +81,11 @@ namespace Serein.Workbench.Services
/// </summary>
public NodeControlBase? ConnectionEndNode { get; set; }
/// <summary>
/// 当前所有画布
/// </summary>
public FlowCanvasView[] FlowCanvass => Canvass.Select(c => c.Value).ToArray();
/// <summary>
/// 记录流程画布
/// </summary>
@@ -134,7 +148,27 @@ namespace Serein.Workbench.Services
flowEEForwardingService.OnNodeTakeOut += FlowEEForwardingService_OnNodeTakeOut; ; // 节点从容器中取出
flowEEForwardingService.OnNodeConnectChange += FlowEEForwardingService_OnNodeConnectChange; // 节点连接状态改变事件
flowEEForwardingService.OnStartNodeChange += FlowEEForwardingService_OnStartNodeChange; // 画布起始节点改变
}
private void FlowEEForwardingService_OnStartNodeChange(StartNodeChangeEventArgs eventArgs)
{
string oldNodeGuid = eventArgs.OldNodeGuid;
string newNodeGuid = eventArgs.NewNodeGuid;
if (!TryGetControl(newNodeGuid, out var newStartNodeControl)) return;
if (!string.IsNullOrEmpty(oldNodeGuid))
{
if (!TryGetControl(oldNodeGuid, out var oldStartNodeControl)) return;
oldStartNodeControl.BorderBrush = Brushes.Black;
oldStartNodeControl.BorderThickness = new Thickness(0);
}
newStartNodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"));
newStartNodeControl.BorderThickness = new Thickness(2);
var node = newStartNodeControl?.ViewModel?.NodeModel;
}
private void FlowEEForwardingService_OnNodeConnectChange(NodeConnectChangeEventArgs e)
@@ -387,7 +421,167 @@ namespace Serein.Workbench.Services
return Canvass.TryGetValue(nodeGuid, out flowCanvas);
}
#region
/// <summary>
/// 从节点信息转换为Json文本数据
/// </summary>
public string CpoyNodeInfo(List<NodeModelBase> dictSelection)
{
// 遍历当前已选节点
foreach (var node in dictSelection.ToArray())
{
if (node.ChildrenNode.Count == 0)
{
continue;
}
// 遍历这些节点的子节点,添加过来
foreach (var childNode in node.ChildrenNode)
{
dictSelection.Add(childNode);
}
}
var nodeInfos = dictSelection.Select(item => item.ToInfo());
JObject json = new JObject()
{
["nodes"] = JArray.FromObject(nodeInfos)
};
var jsonText = json.ToString();
try
{
SereinEnv.WriteLine(InfoType.INFO, $"复制已选节点({dictSelection.Count}个)");
return jsonText;
}
catch (Exception ex)
{
SereinEnv.WriteLine(InfoType.ERROR, $"复制失败:{ex.Message}");
return string.Empty;
}
}
/// <summary>
/// 从Json中加载节点
/// </summary>
/// <param name="canvasGuid">需要加载在哪个画布上</param>
/// <param name="jsonText">文本内容</param>
/// <param name="positionOfUI">需要加载的位置</param>
public void PasteNodeInfo(string canvasGuid, string jsonText, PositionOfUI positionOfUI)
{
try
{
List<NodeInfo>? nodes = JsonConvert.DeserializeObject<List<NodeInfo>>(jsonText);
if (nodes is not null && nodes.Count != 0)
{
}
if (nodes is null || nodes.Count < 0)
{
return;
}
#region
Dictionary<string, string> guids = new Dictionary<string, string>(); // 记录 Guid
// 遍历当前节点
foreach (var node in nodes.ToArray())
{
if (NodeControls.ContainsKey(node.Guid) && !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 (NodeControls.ContainsKey(node.Guid) && !NodeControls.ContainsKey(node.Guid))
{
// 当前Guid并不重复跳过替换
continue;
}
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();
nodes.Add(newNodeInfo);
}
}
}
// Guid去重
StringBuilder sb = new StringBuilder(jsonText);
foreach (var kv in guids)
{
sb.Replace(kv.Key, kv.Value);
}
string result = sb.ToString();
/*var replacer = new GuidReplacer();
foreach (var kv in guids)
{
replacer.AddReplacement(kv.Key, kv.Value);
}
string result = replacer.Replace(jsonText);*/
//SereinEnv.WriteLine(InfoType.ERROR, result);
nodes = JsonConvert.DeserializeObject<List<NodeInfo>>(result);
if (nodes is null || nodes.Count < 0)
{
return;
}
#endregion
// 获取第一个节点的原始位置
var index0NodeX = nodes[0].Position.X;
var index0NodeY = nodes[0].Position.Y;
// 计算所有节点相对于第一个节点的偏移量
foreach (var node in nodes)
{
node.CanvasGuid = canvasGuid; // 替换画布Guid
var offsetX = node.Position.X - index0NodeX;
var offsetY = node.Position.Y - index0NodeY;
// 根据鼠标位置平移节点
node.Position = new PositionOfUI(positionOfUI.X + offsetX, positionOfUI.Y + offsetY);
}
_ = flowEnvironment.LoadNodeInfosAsync(nodes);
}
catch (Exception ex)
{
//SereinEnv.WriteLine(InfoType.ERROR, $"粘贴节点时发生异常:{ex}");
}
// SereinEnv.WriteLine(InfoType.INFO, $"剪贴板文本内容: {clipboardText}");
}
#endregion
#region

View File

@@ -6,19 +6,20 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using static System.Windows.Forms.AxHost;
namespace Serein.Workbench.Services
{
delegate void KeyDownEventHandler(Key key);
delegate void KeyUpEventHandler(Key key);
public delegate void KeyDownEventHandler(Key key);
public delegate void KeyUpEventHandler(Key key);
/// <summary>
/// 全局事件服务
/// 全局按键事件服务
/// </summary>
internal interface IKeyEventService
public interface IKeyEventService
{
event KeyDownEventHandler KeyDown;
event KeyUpEventHandler KeyUp;
event KeyDownEventHandler OnKeyDown;
event KeyUpEventHandler OnKeyUp;
/// <summary>
/// 获取某个按键状态
@@ -26,27 +27,34 @@ namespace Serein.Workbench.Services
/// <param name="key"></param>
/// <returns></returns>
bool GetKeyState(Key key);
/// <summary>
/// 设置某个按键的状态
/// 按下了某个键
/// </summary>
/// <param name="key"></param>
/// <param name="state"></param>
void SetKeyState(Key key, bool statestate);
void KeyDown(Key key);
/// <summary>
/// 抬起了某个键
/// </summary>
/// <param name="key"></param>
void KeyUp(Key key);
}
/// <summary>
/// 管理按键状态
/// </summary>
internal class KeyEventService : IKeyEventService
public class KeyEventService : IKeyEventService
{
/// <summary>
/// 按键按下
/// </summary>
public event KeyDownEventHandler KeyDown;
public event KeyDownEventHandler OnKeyDown;
/// <summary>
/// 按键松开
/// </summary>
public event KeyUpEventHandler KeyUp;
public event KeyUpEventHandler OnKeyUp;
public KeyEventService()
{
@@ -62,18 +70,23 @@ namespace Serein.Workbench.Services
{
return KeysState[(int)key];
}
public void SetKeyState(Key key, bool state)
public void KeyDown(Key key)
{
if (state)
{
KeyDown?.Invoke(key);
}
else
{
KeyUp?.Invoke(key);
}
//Debug.WriteLine($"按键事件:{key} - {state}");
KeysState[(int)key] = state;
KeysState[(int)key] = true;
OnKeyDown?.Invoke(key);
Debug.WriteLine($"按键按下事件:{key}");
}
public void KeyUp(Key key)
{
KeysState[(int)key] = false;
OnKeyUp?.Invoke(key);
Debug.WriteLine($"按键抬起事件:{key}");
}
}
}

View File

@@ -1,55 +0,0 @@
using Newtonsoft.Json;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.NodeFlow;
using Serein.NodeFlow.Env;
using Serein.Workbench.Api;
using Serein.Workbench.Avalonia.Api;
using Serein.Workbench.Node;
using Serein.Workbench.Node.View;
using Serein.Workbench.Node.ViewModel;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace Serein.Workbench.Services
{
/// <summary>
/// 节点操作相关服务
/// </summary>
internal class NodeControlService
{
public NodeControlService(IFlowEnvironment flowEnvironment,
IFlowEEForwardingService feefService)
{
/* this.flowEnvironment = flowEnvironment;
this.feefService = feefService;
feefService.OnNodeCreate += FeefService_OnNodeCreate; // 订阅运行环境创建节点事件
feefService.OnNodeConnectChange += FeefService_OnNodeConnectChange; // 订阅运行环境连接了节点事件
// 手动加载项目
_ = Task.Run(async delegate
{
await Task.Delay(1000);
var flowEnvironment = new FlowEnvironment();// App.GetService<IFlowEnvironment>();
var filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\debug\net8.0\project.dnf";
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
var projectData = JsonConvert.DeserializeObject<SereinProjectData>(content);
var projectDfilePath = System.IO.Path.GetDirectoryName(filePath)!;
flowEnvironment.LoadProject(new FlowEnvInfo { Project = projectData }, projectDfilePath);
}, CancellationToken.None);*/
}
}
}

View File

@@ -7,6 +7,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
@@ -71,9 +72,15 @@ namespace Serein.Workbench.Services
private void InitEvents()
{
flowEEForwardingService.OnProjectSaving += SaveProjectToLocalFile;
flowEEForwardingService.OnEnvOut += FlowEEForwardingService_OnEnvOut;
}
private void FlowEEForwardingService_OnEnvOut(InfoType type, string value)
{
LogWindow.Instance.AppendText($"{DateTime.Now} [{type}] : {value}{Environment.NewLine}");
}
/// <summary>
/// 预览了某个方法信息(待创建)
@@ -103,6 +110,8 @@ namespace Serein.Workbench.Services
private void SaveProjectToLocalFile(ProjectSavingEventArgs e)
{
var project = e.ProjectData;
#region
// 创建一个新的保存文件对话框
SaveFileDialog saveFileDialog = new()
{
@@ -128,8 +137,10 @@ namespace Serein.Workbench.Services
SereinEnv.WriteLine(InfoType.ERROR, "保存项目DLL时返回了意外的文件保存路径");
return;
}
#endregion
#region Dll输出到指定路径
Uri saveProjectFileUri = new Uri(savePath);
SereinEnv.WriteLine(InfoType.INFO, "项目文件保存路径:" + savePath);
for (int index = 0; index < project.Librarys.Length; index++)
@@ -180,12 +191,104 @@ namespace Serein.Workbench.Services
}
}
#endregion
#region
JObject projectJsonData = JObject.FromObject(project);
File.WriteAllText(savePath, projectJsonData.ToString());
#endregion
}
}
}
#region net运行时
/*
1. 扫描目录并计算哈希
string[] directories = new[] { "path1", "path2" };
var fileHashMap = new Dictionary<string, List<string>>(); // hash -> List<full paths>
foreach (var dir in directories)
{
foreach (var file in Directory.EnumerateFiles(dir, "*.*", SearchOption.AllDirectories))
{
using var stream = File.OpenRead(file);
using var sha = SHA256.Create();
var hash = Convert.ToHexString(sha.ComputeHash(stream));
if (!fileHashMap.ContainsKey(hash))
fileHashMap[hash] = new List<string>();
fileHashMap[hash].Add(file);
}
}
2. 将重复文件压缩并保存
string archiveDir = "compressed_output";
Directory.CreateDirectory(archiveDir);
var manifest = new List<FileRecord>();
foreach (var kvp in fileHashMap.Where(kvp => kvp.Value.Count > 1))
{
var hash = kvp.Key;
var originalFile = kvp.Value[0];
var archivePath = Path.Combine(archiveDir, $"{hash}.gz");
using (var input = File.OpenRead(originalFile))
using (var output = File.Create(archivePath))
using (var gzip = new GZipStream(output, CompressionLevel.Optimal))
{
input.CopyTo(gzip);
}
manifest.Add(new FileRecord
{
Hash = hash,
ArchiveFile = $"{hash}.gz",
OriginalPaths = kvp.Value
});
}
3. 生成清单文件JSON
public class FileRecord
{
public string Hash { get; set; }
public string ArchiveFile { get; set; }
public List<string> OriginalPaths { get; set; }
}
File.WriteAllText("manifest.json", JsonSerializer.Serialize(manifest, new JsonSerializerOptions { WriteIndented = true }));
4. 根据清单还原原始文件结构
var manifestJson = File.ReadAllText("manifest.json");
var manifest = JsonSerializer.Deserialize<List<FileRecord>>(manifestJson);
foreach (var record in manifest)
{
var archivePath = Path.Combine("compressed_output", record.ArchiveFile);
foreach (var path in record.OriginalPaths)
{
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
using var input = File.OpenRead(archivePath);
using var gzip = new GZipStream(input, CompressionMode.Decompress);
using var output = File.Create(path);
gzip.CopyTo(output);
}
}
*/
#endregion