diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index d356e40..21de154 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -22,6 +22,14 @@ namespace Serein.Library.Api /// public delegate void ProjectLoadedHandler(ProjectLoadedEventArgs eventArgs); + /// + /// 项目准备保存 + /// + /// + public delegate void ProjectSavingHandler(ProjectSavingEventArgs eventArgs); + + + /// /// 加载项目文件时成功加载了DLL文件 /// @@ -136,6 +144,13 @@ namespace Serein.Library.Api { } } + + public class ProjectSavingEventArgs : FlowEventArgs + { + public ProjectSavingEventArgs() + { + } + } public class LoadDllEventArgs : FlowEventArgs { @@ -259,11 +274,23 @@ namespace Serein.Library.Api public class NodeCreateEventArgs : FlowEventArgs { + /// + /// 节点添加事件参数 + /// + /// 节点对象 + /// 位置 public NodeCreateEventArgs(object nodeModel, PositionOfUI position) { this.NodeModel = nodeModel; this.Position = position; } + + /// + /// 区域子项节点添加事件参数 + /// + /// 节点对象 + /// 是否添加在区域中 + /// 区域Guid public NodeCreateEventArgs(object nodeModel, bool isAddInRegion, string regeionGuid) { this.NodeModel = nodeModel; @@ -564,6 +591,11 @@ namespace Serein.Library.Api /// event ProjectLoadedHandler OnProjectLoaded; + /// + /// 项目准备保存 + /// + event ProjectSavingHandler OnProjectSaving; + /// /// 节点连接属性改变事件 /// @@ -656,18 +688,24 @@ namespace Serein.Library.Api /// void StopRemoteServer(); - - /// - /// 保存当前项目 - /// - /// - Task GetProjectInfoAsync(); /// /// 加载项目文件 /// /// 包含项目信息的远程环境 /// void LoadProject(FlowEnvInfo flowEnvInfo, string filePath); + + /// + /// 保存项目 + /// + void SaveProject(); + + /// + /// 获取当前项目信息 + /// + /// + Task GetProjectInfoAsync(); + /// /// 加载远程环境 /// diff --git a/Library/Enums/NodeType.cs b/Library/Enums/NodeType.cs index 6c29aba..30872ad 100644 --- a/Library/Enums/NodeType.cs +++ b/Library/Enums/NodeType.cs @@ -93,6 +93,10 @@ namespace Serein.Library /// 条件节点区域 /// ConditionRegion, + /// + /// 全局数据 + /// + GlobalData, } } diff --git a/Library/FlowNode/NodeDebugSetting.cs b/Library/FlowNode/NodeDebugSetting.cs index 7df571d..e7e3edd 100644 --- a/Library/FlowNode/NodeDebugSetting.cs +++ b/Library/FlowNode/NodeDebugSetting.cs @@ -42,7 +42,7 @@ namespace Serein.Library /// /// 中断级别,暂时停止继续执行后继分支。 /// - [PropertyInfo(IsNotification = true, CustomCode = "NodeModel?.Env?.SetNodeInterruptAsync(NodeModel?.Guid, value);")] // CustomCode = "NodeModel?.Env?.SetNodeInterruptAsync(NodeModel?.Guid, value);" + [PropertyInfo(IsNotification = true, CustomCodeAtEnd = "NodeModel?.Env?.SetNodeInterruptAsync(NodeModel?.Guid, value);")] // CustomCode = "NodeModel?.Env?.SetNodeInterruptAsync(NodeModel?.Guid, value);" private bool _isInterrupt = false; /// diff --git a/Library/FlowNode/NodeModelBaseData.cs b/Library/FlowNode/NodeModelBaseData.cs index 4bacbcc..441a5db 100644 --- a/Library/FlowNode/NodeModelBaseData.cs +++ b/Library/FlowNode/NodeModelBaseData.cs @@ -72,7 +72,10 @@ namespace Serein.Library /// /// 实体节点创建完成后调用的方法,调用时间早于 LoadInfo() 方法 /// - public abstract void OnCreating(); + public virtual void OnCreating() + { + + } public NodeModelBase(IFlowEnvironment environment) { diff --git a/Library/FlowNode/NodeModelBaseFunc.cs b/Library/FlowNode/NodeModelBaseFunc.cs index 2ad1a29..10f6583 100644 --- a/Library/FlowNode/NodeModelBaseFunc.cs +++ b/Library/FlowNode/NodeModelBaseFunc.cs @@ -28,13 +28,55 @@ namespace Serein.Library /// public abstract partial class NodeModelBase : IDynamicFlowNode { + #region 节点移除相关 + /// + /// 移除该节点 + /// + public virtual void Remove() + { + + } + + #endregion + #region 导出/导入项目文件节点信息 /// - /// 获取节点参数 + /// 输出方法参数信息 /// /// - public abstract ParameterData[] GetParameterdatas(); + public virtual ParameterData[] SaveParameterInfo() + { + if(MethodDetails.ParameterDetailss == null) + { + return new ParameterData[0]; + } + if (MethodDetails.ParameterDetailss.Length > 0) + { + return MethodDetails.ParameterDetailss + .Select(it => new ParameterData + { + SourceNodeGuid = it.ArgDataSourceNodeGuid, + SourceType = it.ArgDataSourceType.ToString(), + State = it.IsExplicitData, + Value = it.DataValue, + }) + .ToArray(); + } + else + { + return new ParameterData[0]; + } + } + + /// + /// 保存自定义信息 + /// + /// + public virtual NodeInfo SaveCustomData(NodeInfo nodeInfo) + { + return nodeInfo; + } /// /// 导出为节点信息 @@ -43,16 +85,15 @@ namespace Serein.Library public virtual NodeInfo ToInfo() { // if (MethodDetails == null) return null; - + var trueNodes = SuccessorNodes[ConnectionInvokeType.IsSucceed].Select(item => item.Guid); // 真分支 var falseNodes = SuccessorNodes[ConnectionInvokeType.IsFail].Select(item => item.Guid);// 假分支 var errorNodes = SuccessorNodes[ConnectionInvokeType.IsError].Select(item => item.Guid);// 异常分支 var upstreamNodes = SuccessorNodes[ConnectionInvokeType.Upstream].Select(item => item.Guid);// 上游分支 - // 生成参数列表 - ParameterData[] parameterData = GetParameterdatas(); + ParameterData[] parameterData = SaveParameterInfo(); - return new NodeInfo + NodeInfo nodeInfo = new NodeInfo { Guid = Guid, AssemblyName = MethodDetails.AssemblyName, @@ -66,6 +107,17 @@ namespace Serein.Library ErrorNodes = errorNodes.ToArray(), Position = Position, }; + nodeInfo = SaveCustomData(nodeInfo); + return nodeInfo; + } + + /// + /// 加载自定义数据 + /// + /// + public virtual void LoadCustomData(NodeInfo nodeInfo) + { + return; } /// @@ -73,78 +125,53 @@ namespace Serein.Library /// /// /// - public virtual NodeModelBase LoadInfo(NodeInfo nodeInfo) + public virtual void LoadInfo(NodeInfo nodeInfo) { this.Guid = nodeInfo.Guid; - - if (nodeInfo.Position is null) + this.Position = nodeInfo.Position ?? new PositionOfUI(0, 0);// 加载位置信息 + var md = this.MethodDetails; // 当前节点的方法说明 + if (md != null) { - nodeInfo.Position = new PositionOfUI(0, 0); - } - this.Position = nodeInfo.Position;// 加载位置信息 - if (this.MethodDetails != null) - { - if(this.MethodDetails.ParameterDetailss is null) + if(md.ParameterDetailss == null) { - this.MethodDetails.ParameterDetailss = new ParameterDetails[nodeInfo.ParameterData.Length]; - this.MethodDetails.ParameterDetailss = nodeInfo.ParameterData.Select((pd,index) => - { - return new ParameterDetails() - { - Index = index, - NodeModel = this, - DataType = typeof(object), - ExplicitType = typeof(object), - Name = string.Empty, - ExplicitTypeName = "Value", - IsExplicitData = pd.State, - DataValue = pd.Value, - ArgDataSourceType = EnumHelper.ConvertEnum(pd.SourceType), - ArgDataSourceNodeGuid = pd.SourceNodeGuid, - }; - }).ToArray(); + md.ParameterDetailss = new ParameterDetails[0]; } - else - { - var md = this.MethodDetails; // 当前节点的方法说明 - var pds = md.ParameterDetailss; // 当前节点的入参描述数组 - if (nodeInfo.ParameterData.Length > pds.Length && md.HasParamsArg) - { - // 保存的参数信息项数量大于方法本身的方法入参数量(可能存在可变入参) - var length = nodeInfo.ParameterData.Length - pds.Length; // 需要扩容的长度 - this.MethodDetails.ParameterDetailss = ArrayHelper.Expansion(pds, length); // 扩容入参描述数组 - pds = this.MethodDetails.ParameterDetailss; - var startParmsPd = pds[md.ParamsArgIndex]; // 获取可变入参参数描述 - for(int i = md.ParamsArgIndex + 1; i <= md.ParamsArgIndex + length; i++) - { - pds[i] = startParmsPd.CloneOfModel(this); - pds[i].Index = pds[i-1].Index + 1; - pds[i].IsParams = true; - } - } - for (int i = 0; i < nodeInfo.ParameterData.Length; i++) - { - if(i >= pds.Length) - { - Env.WriteLine(InfoType.ERROR, $"保存的参数数量大于方法此时的入参参数数量:[{nodeInfo.Guid}][{nodeInfo.MethodName}]"); + LoadCustomData(nodeInfo); // 加载自定义数据 - break; - } - var pd = pds[i]; - ParameterData pdInfo = nodeInfo.ParameterData[i]; - pd.IsExplicitData = pdInfo.State; - pd.DataValue = pdInfo.Value; - pd.ArgDataSourceType = EnumHelper.ConvertEnum(pdInfo.SourceType); - pd.ArgDataSourceNodeGuid = pdInfo.SourceNodeGuid; - + var pds = md.ParameterDetailss; // 当前节点的入参描述数组 + #region 类库方法型节点加载参数 + if (nodeInfo.ParameterData.Length > pds.Length && md.HasParamsArg) + { + // 保存的参数信息项数量大于方法本身的方法入参数量(可能存在可变入参) + var length = nodeInfo.ParameterData.Length - pds.Length; // 需要扩容的长度 + this.MethodDetails.ParameterDetailss = ArrayHelper.Expansion(pds, length); // 扩容入参描述数组 + pds = md.ParameterDetailss; // 当前节点的入参描述数组 + var startParmsPd = pds[md.ParamsArgIndex]; // 获取可变入参参数描述 + for (int i = md.ParamsArgIndex + 1; i <= md.ParamsArgIndex + length; i++) + { + pds[i] = startParmsPd.CloneOfModel(this); + pds[i].Index = pds[i - 1].Index + 1; + pds[i].IsParams = true; } } - + for (int i = 0; i < nodeInfo.ParameterData.Length; i++) + { + if (i >= pds.Length) + { + Env.WriteLine(InfoType.ERROR, $"保存的参数数量大于方法此时的入参参数数量:[{nodeInfo.Guid}][{nodeInfo.MethodName}]"); + break; + } + var pd = pds[i]; + ParameterData pdInfo = nodeInfo.ParameterData[i]; + pd.IsExplicitData = pdInfo.State; + pd.DataValue = pdInfo.Value; + pd.ArgDataSourceType = EnumHelper.ConvertEnum(pdInfo.SourceType); + pd.ArgDataSourceNodeGuid = pdInfo.SourceNodeGuid; - + } + #endregion } - return this; } #endregion @@ -617,7 +644,6 @@ namespace Serein.Library } #endregion - } diff --git a/Library/FlowNode/SereinProjectData.cs b/Library/FlowNode/SereinProjectData.cs index a6cab50..e1d3a51 100644 --- a/Library/FlowNode/SereinProjectData.cs +++ b/Library/FlowNode/SereinProjectData.cs @@ -1,4 +1,5 @@ -using Serein.Library.Api; +using Newtonsoft.Json.Linq; +using Serein.Library.Api; using System; using System.Collections.Generic; using System.Drawing; @@ -244,6 +245,11 @@ namespace Serein.Library /// 是否选中(暂时无效) /// public bool IsSelect { get; set; } + + /// + /// 自定义数据 + /// + public dynamic CustomData { get; set; } } /// diff --git a/Library/SereinBaseFunction.cs b/Library/SereinBaseFunction.cs index 0064df3..5ca2937 100644 --- a/Library/SereinBaseFunction.cs +++ b/Library/SereinBaseFunction.cs @@ -137,7 +137,7 @@ namespace Serein.Library [NodeAction(NodeType.Action, "设置/更新全局数据")] private object SereinAddOrUpdateFlowGlobalData(string name,object data) { - SereinEnv.EnvGlobalData.AddOrUpdate(name, data,(k,o)=> data); + SereinEnv.AddOrUpdateFlowGlobalData(name, data); return data; } diff --git a/Library/Utils/ConvertHelper.cs b/Library/Utils/ConvertHelper.cs index 5323bc4..e8db44d 100644 --- a/Library/Utils/ConvertHelper.cs +++ b/Library/Utils/ConvertHelper.cs @@ -22,7 +22,7 @@ namespace Serein.Library.Utils return (TResult)data.ToConvert(type); } - public static object ToConvert(this object data,Type type) + public static object ToConvert(this object data, Type type) { if (type.IsValueType) { diff --git a/Library/Utils/SereinEnv.cs b/Library/Utils/SereinEnv.cs index 0051bb1..ff81f11 100644 --- a/Library/Utils/SereinEnv.cs +++ b/Library/Utils/SereinEnv.cs @@ -2,10 +2,12 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Data; using System.Diagnostics.Contracts; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Xml.Linq; namespace Serein.Library.Utils { @@ -13,14 +15,69 @@ namespace Serein.Library.Utils { private static IFlowEnvironment environment; + #region 全局数据(暂时使用静态全局变量) /// /// 记录全局数据 /// - public static ConcurrentDictionary EnvGlobalData { get; } = new ConcurrentDictionary(); + private static ConcurrentDictionary EnvGlobalData { get; } = new ConcurrentDictionary(); + + /// + /// 添加或更新全局数据 + /// + /// + /// + /// + public static void AddOrUpdateFlowGlobalData(string name, object data) + { + SereinEnv.EnvGlobalData.AddOrUpdate(name, data, (k, o) => data); + } + + /// + /// 更改某个数据的名称 + /// + /// 旧名称 + /// 新名称 + /// + public static bool ChangeNameFlowGlobalData(string oldName, string newName) + { + if (string.IsNullOrEmpty(oldName) || string.IsNullOrEmpty(newName)) + { + return false; + } + // 确保存在,然后尝试移除 + if (SereinEnv.EnvGlobalData.ContainsKey(oldName) + && SereinEnv.EnvGlobalData.TryRemove(oldName, out var data)) + { + SereinEnv.EnvGlobalData.AddOrUpdate(newName, data, (k, o) => data); + return true; + } + else + { + return false; + } + } + + /// + /// 获取全局数据 + /// + /// + /// + public static object GetFlowGlobalData(string name) + { + if (!string.IsNullOrEmpty(name) && SereinEnv.EnvGlobalData.TryGetValue(name, out var data)) + { + return data; + } + else + { + return null; + } + } /// /// 清空全局数据 /// - public static void ClearGlobalData() + /// + public static void ClearFlowGlobalData() { foreach (var nodeObj in EnvGlobalData.Values) { @@ -37,8 +94,12 @@ namespace Serein.Library.Utils } } EnvGlobalData.Clear(); + return; } + #endregion + + /// diff --git a/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs b/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs index 798de6a..9a44ebc 100644 --- a/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs +++ b/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs @@ -510,9 +510,7 @@ namespace Serein.Library.Utils.SereinExpression private static object GetGlobleData(object value, string expression) { var keyName = expression; - SereinEnv.EnvGlobalData.TryGetValue(keyName, out var data); - - return data; + return SereinEnv.GetFlowGlobalData(keyName); } } } diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index e02da7f..4930c24 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -58,6 +58,15 @@ namespace Serein.NodeFlow.Env }; this.UIContextOperation = uiContextOperation; // 为加载的类库提供在UI线程上执行某些操作的封装工具类 this.FlowLibraryManagement = new FlowLibraryManagement(this); // 实例化类库管理 + + #region 注册基本节点类型 + NodeMVVMManagement.RegisterModel(NodeControlType.Action, typeof(SingleActionNode)); // 动作节点 + NodeMVVMManagement.RegisterModel(NodeControlType.Flipflop, typeof(SingleFlipflopNode)); // 触发器节点 + NodeMVVMManagement.RegisterModel(NodeControlType.ExpOp, typeof(SingleExpOpNode)); // 表达式节点 + NodeMVVMManagement.RegisterModel(NodeControlType.ExpCondition, typeof(SingleConditionNode)); // 条件表达式节点 + NodeMVVMManagement.RegisterModel(NodeControlType.ConditionRegion, typeof(CompositeConditionNode)); // 条件区域 + NodeMVVMManagement.RegisterModel(NodeControlType.GlobalData, typeof(SingleGlobalDataNode)); // 全局数据节点 + #endregion } #region 远程管理 @@ -119,6 +128,11 @@ namespace Serein.NodeFlow.Env /// public event ProjectLoadedHandler? OnProjectLoaded; + /// + /// 项目准备保存 + /// + public event ProjectSavingHandler? OnProjectSaving; + /// /// 节点连接属性改变事件 /// @@ -347,10 +361,10 @@ namespace Serein.NodeFlow.Env { if (@class >= this.InfoClass) { - + OnEnvOut?.Invoke(type, message); } - Console.WriteLine($"{DateTime.UtcNow} [{type}] : {message}{Environment.NewLine}"); - OnEnvOut?.Invoke(type, message); + //Console.WriteLine($"{DateTime.UtcNow} [{type}] : {message}{Environment.NewLine}"); + } ///// @@ -506,7 +520,7 @@ namespace Serein.NodeFlow.Env // 获取所有的程序集对应的方法信息(程序集相关的数据) var libraryMdss = this.FlowLibraryManagement.GetAllLibraryMds().ToArray(); // 获取当前项目的信息(节点相关的数据) - var project = await GetProjectInfoAsync(); + var project = await GetProjectInfoAsync(); // 远程连接获取远程环境项目信息 SereinEnv.WriteLine(InfoType.INFO, "已将当前环境信息发送到远程客户端"); return new FlowEnvInfo { @@ -526,7 +540,13 @@ namespace Serein.NodeFlow.Env } - + /// + /// 保存项目 + /// + public void SaveProject() + { + OnProjectSaving?.Invoke(new ProjectSavingEventArgs()); + } /// /// 加载项目文件 @@ -746,6 +766,7 @@ namespace Serein.NodeFlow.Env Nodes = NodeModels.Values.Select(node => node.ToInfo()).Where(info => info is not null).ToArray(), StartNode = NodeModels.Values.FirstOrDefault(it => it.IsStart)?.Guid, }; + return Task.FromResult(projectData); } @@ -926,17 +947,14 @@ namespace Serein.NodeFlow.Env if (remoteNode is null) return false; - //if (remoteNode.IsStart) - //{ - // return; - //} if (remoteNode is SingleFlipflopNode flipflopNode) { flowStarter?.TerminateGlobalFlipflopRuning(flipflopNode); // 假设被移除的是全局触发器,尝试从启动器移除 } + remoteNode.Remove(); // 调用节点的移除方法 - // 遍历所有父节点,从那些父节点中的子节点集合移除该节点 + // 遍历所有前置节点,从那些前置节点中的后继节点集合移除该节点 foreach (var pnc in remoteNode.PreviousNodes) { var pCType = pnc.Key; // 连接类型 @@ -955,7 +973,7 @@ namespace Serein.NodeFlow.Env } } - // 遍历所有子节点,从那些子节点中的父节点集合移除该节点 + // 遍历所有后继节点,从那些后继节点中的前置节点集合移除该节点 foreach (var snc in remoteNode.SuccessorNodes) { var connectionType = snc.Key; // 连接类型 @@ -968,6 +986,8 @@ namespace Serein.NodeFlow.Env } } + + // 从集合中移除节点 NodeModels.Remove(nodeGuid); UIContextOperation?.Invoke(() => OnNodeRemove?.Invoke(new NodeRemoveEventArgs(nodeGuid))); @@ -1434,7 +1454,7 @@ namespace Serein.NodeFlow.Env isPass = nodeModel.MethodDetails.RemoveParamsArg(paramIndex); } - await Task.Delay(50); + await Task.Delay(200); foreach ((var fromGuid, var type, var index) in argInfo) { await UIContextOperation.InvokeAsync(() => @@ -1487,7 +1507,7 @@ namespace Serein.NodeFlow.Env /// public object AddOrUpdateGlobalData(string keyName, object data) { - SereinEnv.EnvGlobalData.AddOrUpdate(keyName, data, (k, o) => data); + SereinEnv.AddOrUpdateFlowGlobalData(keyName, data); return data; } @@ -1498,8 +1518,7 @@ namespace Serein.NodeFlow.Env /// public object? GetGlobalData(string keyName) { - SereinEnv.EnvGlobalData.TryGetValue(keyName, out var data); - return data; + return SereinEnv.GetFlowGlobalData(keyName); } diff --git a/NodeFlow/Env/FlowEnvironmentDecorator.cs b/NodeFlow/Env/FlowEnvironmentDecorator.cs index 704f607..d6f850a 100644 --- a/NodeFlow/Env/FlowEnvironmentDecorator.cs +++ b/NodeFlow/Env/FlowEnvironmentDecorator.cs @@ -86,12 +86,23 @@ namespace Serein.NodeFlow.Env add { currentFlowEnvironment.OnDllLoad += value; } remove { currentFlowEnvironment.OnDllLoad -= value; } } + public event ProjectLoadedHandler OnProjectLoaded { add { currentFlowEnvironment.OnProjectLoaded += value; } remove { currentFlowEnvironment.OnProjectLoaded -= value; } } + /// + /// 项目准备保存 + /// + public event ProjectSavingHandler? OnProjectSaving + { + add { currentFlowEnvironment.OnProjectSaving += value; } + remove { currentFlowEnvironment.OnProjectSaving -= value; } + } + + public event NodeConnectChangeHandler OnNodeConnectChange { add { currentFlowEnvironment.OnNodeConnectChange += value; } @@ -289,6 +300,14 @@ namespace Serein.NodeFlow.Env currentFlowEnvironment.LoadLibrary(dllPath); } + /// + /// 保存项目 + /// + public void SaveProject() + { + currentFlowEnvironment.SaveProject(); + } + public void LoadProject(FlowEnvInfo flowEnvInfo, string filePath) { if (flowEnvInfo is null) return; diff --git a/NodeFlow/Env/FlowFunc.cs b/NodeFlow/Env/FlowFunc.cs index 3f4f92c..283bf95 100644 --- a/NodeFlow/Env/FlowFunc.cs +++ b/NodeFlow/Env/FlowFunc.cs @@ -1,6 +1,8 @@ using Serein.Library; using Serein.Library.Api; +using Serein.Library.Utils; using Serein.NodeFlow.Model; +using System.Collections.Concurrent; namespace Serein.NodeFlow.Env { @@ -10,6 +12,8 @@ namespace Serein.NodeFlow.Env /// public static class FlowFunc { + + /// /// 判断是否为基础节点 /// @@ -17,13 +21,15 @@ namespace Serein.NodeFlow.Env public static bool IsBaseNode(this NodeControlType nodeControlType) { if(nodeControlType == NodeControlType.ExpCondition - || nodeControlType == NodeControlType.ExpOp) + || nodeControlType == NodeControlType.ExpOp + || nodeControlType == NodeControlType.GlobalData) { return true; } return false; } + /// /// 创建节点 /// @@ -35,24 +41,16 @@ namespace Serein.NodeFlow.Env public static NodeModelBase CreateNode(IFlowEnvironment env, NodeControlType nodeControlType, MethodDetails? methodDetails = null) { - // 确定创建的节点类型 - Type? nodeType = nodeControlType switch - { - NodeControlType.Action => typeof(SingleActionNode), - NodeControlType.Flipflop => typeof(SingleFlipflopNode), - NodeControlType.ExpOp => typeof(SingleExpOpNode), - NodeControlType.ExpCondition => typeof(SingleConditionNode), - NodeControlType.ConditionRegion => typeof(CompositeConditionNode), - _ => null - }; + // 尝试获取需要创建的节点类型 - if (nodeType is null) + if (!NodeMVVMManagement.TryGetType(nodeControlType, out var nodeMVVM) || nodeMVVM.ModelType == null) { - throw new Exception($"节点类型错误[{nodeControlType}]"); + throw new Exception($"无法创建{nodeControlType}节点,节点类型尚未注册。"); } + // 生成实例 - var nodeObj = Activator.CreateInstance(nodeType, env); + var nodeObj = Activator.CreateInstance(nodeMVVM.ModelType, env); if (nodeObj is not NodeModelBase nodeModel) { throw new Exception($"无法创建目标节点类型的实例[{nodeControlType}]"); @@ -90,6 +88,8 @@ namespace Serein.NodeFlow.Env $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => NodeControlType.ExpOp, // 操作表达式控件 $"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => NodeControlType.ConditionRegion, // 条件区域控件 + + $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleGlobalDataNode)}" => NodeControlType.GlobalData, // 数据节点 _ => NodeControlType.None, }; diff --git a/NodeFlow/Env/RemoteFlowEnvironment.cs b/NodeFlow/Env/RemoteFlowEnvironment.cs index 4cf6dd4..61c8bec 100644 --- a/NodeFlow/Env/RemoteFlowEnvironment.cs +++ b/NodeFlow/Env/RemoteFlowEnvironment.cs @@ -45,6 +45,10 @@ namespace Serein.NodeFlow.Env public event LoadDllHandler OnDllLoad; public event ProjectLoadedHandler OnProjectLoaded; + /// + /// 项目准备保存 + /// + public event ProjectSavingHandler? OnProjectSaving; public event NodeConnectChangeHandler OnNodeConnectChange; public event NodeCreateHandler OnNodeCreate; public event NodeRemoveHandler OnNodeRemove; @@ -128,6 +132,15 @@ namespace Serein.NodeFlow.Env return prjectInfo; } + /// + /// 保存项目 + /// + public void SaveProject() + { + OnProjectSaving?.Invoke(new ProjectSavingEventArgs()); + } + + /// /// 远程环境下加载项目 /// diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index 9c6c2d0..c64a6fd 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -241,7 +241,7 @@ namespace Serein.NodeFlow _flipFlopCts?.Dispose(); } // 通知所有流程上下文停止运行 TerminateAllGlobalFlipflop(); // 确保所有触发器不再运行 - SereinEnv.ClearGlobalData(); // 清空全局数据缓存 + SereinEnv.ClearFlowGlobalData(); // 清空全局数据缓存 NativeDllHelper.FreeLibrarys(); // 卸载所有已加载的 Native Dll env.FlowState = RunState.Completion; diff --git a/NodeFlow/Model/CompositeConditionNode.cs b/NodeFlow/Model/CompositeConditionNode.cs index 381467a..23cfe97 100644 --- a/NodeFlow/Model/CompositeConditionNode.cs +++ b/NodeFlow/Model/CompositeConditionNode.cs @@ -30,23 +30,6 @@ namespace Serein.NodeFlow.Model } - /// - /// 加载完成后调用的方法 - /// - public override void OnCreating() - { - SereinEnv.WriteLine(InfoType.WARN, "CompositeConditionNode 暂未实现 OnLoading"); - } - - public void AddNode(SingleConditionNode node) - { - if(ConditionNodes is null) - { - ConditionNodes = new List(); - } - ConditionNodes.Add(node); - MethodDetails ??= node.MethodDetails; - } /// /// 条件节点重写执行方法 @@ -79,49 +62,35 @@ namespace Serein.NodeFlow.Model return context.TransmissionData(this); // 条件区域透传上一节点的数据 } - } - - - public override ParameterData[] GetParameterdatas() + /// + /// 设置区域子项 + /// + /// + /// + public override NodeInfo SaveCustomData(NodeInfo nodeInfo) { - return []; + nodeInfo.ChildNodeGuids = ConditionNodes.Select(node => node.Guid).ToArray(); + return nodeInfo; } - public override NodeInfo ToInfo() + /// + /// 添加条件子项 + /// + /// + public void AddNode(SingleConditionNode node) { - //if (MethodDetails == null) return null; - - //var trueNodes = SucceedBranch.Select(item => item.Guid); // 真分支 - //var falseNodes = FailBranch.Select(item => item.Guid);// 假分支 - //var upstreamNodes = UpstreamBranch.Select(item => item.Guid);// 上游分支 - //var errorNodes = ErrorBranch.Select(item => item.Guid);// 异常分支 - var trueNodes = SuccessorNodes[ConnectionInvokeType.IsSucceed].Select(item => item.Guid); // 真分支 - var falseNodes = SuccessorNodes[ConnectionInvokeType.IsFail].Select(item => item.Guid);// 假分支 - var errorNodes = SuccessorNodes[ConnectionInvokeType.IsError].Select(item => item.Guid);// 异常分支 - var upstreamNodes = SuccessorNodes[ConnectionInvokeType.Upstream].Select(item => item.Guid);// 上游分支 - - // 生成参数列表 - ParameterData[] parameterData = GetParameterdatas(); - - return new NodeInfo + if (ConditionNodes is null) { - Guid = Guid, - AssemblyName = MethodDetails.AssemblyName, - MethodName = MethodDetails.MethodName, - Label = MethodDetails?.MethodAnotherName, - Type = this.GetType().ToString(), - TrueNodes = trueNodes.ToArray(), - FalseNodes = falseNodes.ToArray(), - UpstreamNodes = upstreamNodes.ToArray(), - ParameterData = parameterData.ToArray(), - ErrorNodes = errorNodes.ToArray(), - ChildNodeGuids = ConditionNodes.Select(node => node.Guid).ToArray(), - Position = Position, - }; + ConditionNodes = new List(); + } + ConditionNodes.Add(node); + MethodDetails ??= node.MethodDetails; } + + } } diff --git a/NodeFlow/Model/SingleActionNode.cs b/NodeFlow/Model/SingleActionNode.cs index 473614d..22bdd1c 100644 --- a/NodeFlow/Model/SingleActionNode.cs +++ b/NodeFlow/Model/SingleActionNode.cs @@ -14,32 +14,6 @@ namespace Serein.NodeFlow.Model } - /// - /// 加载完成后调用的方法 - /// - public override void OnCreating() - { - } - - public override ParameterData[] GetParameterdatas() - { - if (base.MethodDetails.ParameterDetailss.Length > 0) - { - return MethodDetails.ParameterDetailss - .Select(it => new ParameterData - { - SourceNodeGuid = it.ArgDataSourceNodeGuid, - SourceType = it.ArgDataSourceType.ToString(), - State = it.IsExplicitData, - Value = it.DataValue, - }) - .ToArray(); - } - else - { - return []; - } - } } diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs index 6badcab..c71038f 100644 --- a/NodeFlow/Model/SingleConditionNode.cs +++ b/NodeFlow/Model/SingleConditionNode.cs @@ -2,7 +2,10 @@ using Serein.Library.Api; using Serein.Library.Utils; using Serein.Library.Utils.SereinExpression; +using System; using System.ComponentModel; +using System.Dynamic; +using System.Linq.Expressions; namespace Serein.NodeFlow.Model { @@ -12,38 +15,90 @@ namespace Serein.NodeFlow.Model [NodeProperty(ValuePath = NodeValuePath.Node)] public partial class SingleConditionNode : NodeModelBase { - /// /// 是否为自定义参数 /// [PropertyInfo(IsNotification = true)] - private bool _isCustomData; + private bool _isExplicitData; /// /// 自定义参数值 /// [PropertyInfo(IsNotification = true)] - private string? _customData; + private string? _explicitData; /// /// 条件表达式 /// [PropertyInfo(IsNotification = true)] private string _expression; + } public partial class SingleConditionNode : NodeModelBase { + /// + /// 表达式参数索引 + /// + private const int INDEX_EXPRESSION = 0; + + public SingleConditionNode(IFlowEnvironment environment):base(environment) { - this.IsCustomData = false; - this.CustomData = null; + this.IsExplicitData = false; + this.ExplicitData = string.Empty; this.Expression = "PASS"; } - + public override void OnCreating() + { + // 这里的这个参数是为了方便使用入参控制点,参数无意义 + var pd = new ParameterDetails[1]; + pd[INDEX_EXPRESSION] = new ParameterDetails + { + Index = INDEX_EXPRESSION, + Name = nameof(Expression), + IsExplicitData = false, + DataValue = string.Empty, + DataType = typeof(string), + ExplicitType = typeof(string), + ArgDataSourceNodeGuid = string.Empty, + ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData, + NodeModel = this, + Convertor = null, + ExplicitTypeName = "Value", + Items = null, + }; + this.MethodDetails.ParameterDetailss = [..pd]; + } + /// + /// 导出方法信息 + /// + /// + /// + public override NodeInfo SaveCustomData(NodeInfo nodeInfo) + { + dynamic data = new ExpandoObject(); + data.Expression = Expression ?? ""; + data.ExplicitData = ExplicitData ?? ""; + data.IsExplicitData = IsExplicitData; + nodeInfo.CustomData = data; + return nodeInfo; + } + + /// + /// 加载自定义数据 + /// + /// + public override void LoadCustomData(NodeInfo nodeInfo) + { + this.Expression = nodeInfo.CustomData?.Expression ?? ""; + this.ExplicitData = nodeInfo.CustomData?.ExplicitData ?? ""; + this.IsExplicitData = nodeInfo.CustomData?.IsExplicitData ?? false; + } + /// /// 重写节点的方法执行 /// @@ -54,54 +109,45 @@ namespace Serein.NodeFlow.Model // 接收上一节点参数or自定义参数内容 object? parameter; object? result = null; - if (!IsCustomData) // 是否使用自定义参数 + + if (!IsExplicitData) { - - var pd = MethodDetails.ParameterDetailss[0]; - - if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) + // 使用自动取参 + var pd = MethodDetails.ParameterDetailss[INDEX_EXPRESSION]; + if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) { - // 使用自定义节点的参数 - result = context.GetFlowData(pd.ArgDataSourceNodeGuid); + result = context.GetFlowData(pd.ArgDataSourceNodeGuid); // 使用自定义节点的参数 } else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) { - // 立刻调用目标节点,然后使用其返回值 - result = await Env.InvokeNodeAsync(context, pd.ArgDataSourceNodeGuid); + result = await Env.InvokeNodeAsync(context, pd.ArgDataSourceNodeGuid); // 立刻调用目标节点,然后使用其返回值 } else { - // 条件节点透传上一节点的数据 - result = context.TransmissionData(this); + result = context.TransmissionData(this); // 条件节点透传上一节点的数据 } - - // 使用上一节点的参数 - parameter = result; + parameter = result; // 使用上一节点的参数 } else { - - var getObjExp = CustomData?.ToString(); - if (string.IsNullOrEmpty(getObjExp) || getObjExp.Length < 4 || !getObjExp[..4].Equals("@get", StringComparison.CurrentCultureIgnoreCase)) + var exp = ExplicitData?.ToString(); + if (!string.IsNullOrEmpty(exp) && exp.StartsWith('@')) { - // 使用自定义的参数 - parameter = CustomData; + parameter = result; // 表达式获取上一节点数据 + if (parameter is not null) + { + parameter = SerinExpressionEvaluator.Evaluate(exp, parameter, out _); + } } else { - // 表达式获取上一节点数据 - parameter = result; - if (parameter is not null) - { - parameter = SerinExpressionEvaluator.Evaluate(getObjExp, parameter, out _); - } + parameter = ExplicitData; // 使用自定义的参数 } } try { - var isPass = SereinConditionParser.To(parameter, Expression); context.NextOrientation = isPass ? ConnectionInvokeType.IsSucceed : ConnectionInvokeType.IsFail; } @@ -117,105 +163,6 @@ namespace Serein.NodeFlow.Model - public override ParameterData[] GetParameterdatas() - { - var pd1 = MethodDetails.ParameterDetailss[0]; - var pd2 = MethodDetails.ParameterDetailss[1]; - var pd3 = MethodDetails.ParameterDetailss[2]; - return [ - new ParameterData // 保存表达式 - { - Value = Expression , - SourceNodeGuid = pd1.ArgDataSourceNodeGuid, - SourceType = pd1.ArgDataSourceType.ToString(), - }, - new ParameterData // 保存自定义参数 - { - Value = CustomData?.ToString() , - SourceNodeGuid = pd2.ArgDataSourceNodeGuid, - SourceType = pd2.ArgDataSourceType.ToString(), - }, - new ParameterData // 参数来源状态 - { - Value = IsCustomData.ToString() , - SourceNodeGuid = pd3.ArgDataSourceNodeGuid, - SourceType = pd3.ArgDataSourceType.ToString(), - }]; - } - - public override void OnCreating() - { - // 自定义节点初始化默认的参数实体 - var tmpParameterDetails = new ParameterDetails[3]; - for (int index = 0; index <= 2; index++) - { - tmpParameterDetails[index] = new ParameterDetails - { - Index = index, - IsExplicitData = false, - DataValue = string.Empty, - ArgDataSourceNodeGuid = string.Empty, - ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData, - NodeModel = this, - Convertor = null, - ExplicitTypeName = "Value", - Items = Array.Empty(), - }; - } - - var pd1 = tmpParameterDetails[0]; // 表达式 - var pd2 = tmpParameterDetails[1]; // 自定义参数 - var pd3 = tmpParameterDetails[2]; // 参数来源 - - // 表达式 - pd1.Name = nameof(Expression); - pd1.DataType = typeof(string); - pd1.ExplicitType = typeof(string); - - // 自定义参数 - pd2.Name = nameof(CustomData); - pd2.DataType = typeof(string); - pd2.ExplicitType = typeof(string); - - // 参数来源 - pd3.Name = nameof(IsCustomData); - pd3.DataType = typeof(bool); - pd3.ExplicitType = typeof(bool); - - //this.MethodDetails.ParameterDetailss = new ParameterDetails[2] { pd1, pd2 }; - this.MethodDetails.ParameterDetailss = [..tmpParameterDetails]; - } - - - - - public override NodeModelBase LoadInfo(NodeInfo nodeInfo) - { - this.Guid = nodeInfo.Guid; - this.Position = nodeInfo.Position;// 加载位置信息 - - var pdInfo1 = nodeInfo.ParameterData[0]; - this.Expression = pdInfo1.Value; // 加载表达式 - - var pdInfo2 = nodeInfo.ParameterData[1]; - this.CustomData = pdInfo2.Value; // 加载自定义参数信息 - - var pdInfo3 = nodeInfo.ParameterData[2]; - bool.TryParse(pdInfo3.Value,out var @bool); // 参数来源状态 - this.IsCustomData = @bool; - - for (int i = 0; i < nodeInfo.ParameterData.Length; i++) - { - var pd = this.MethodDetails.ParameterDetailss[i]; // 本节点的参数信息 - ParameterData? pdInfo = nodeInfo.ParameterData[i]; // 项目文件的保存信息 - - pd.ArgDataSourceNodeGuid = pdInfo.SourceNodeGuid; - pd.ArgDataSourceType = EnumHelper.ConvertEnum(pdInfo.SourceType); - } - return this; - } - - } diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs index cbf5de0..7e499e1 100644 --- a/NodeFlow/Model/SingleExpOpNode.cs +++ b/NodeFlow/Model/SingleExpOpNode.cs @@ -2,6 +2,7 @@ using Serein.Library.Api; using Serein.Library.Utils; using Serein.Library.Utils.SereinExpression; +using System.Dynamic; using System.Reactive; using System.Reflection.Metadata; @@ -25,6 +26,11 @@ namespace Serein.NodeFlow.Model public partial class SingleExpOpNode : NodeModelBase { + /// + /// 表达式参数索引 + /// + private const int INDEX_EXPRESSION = 0; + public SingleExpOpNode(IFlowEnvironment environment) : base(environment) { @@ -35,23 +41,46 @@ namespace Serein.NodeFlow.Model /// public override void OnCreating() { - var pd = new ParameterDetails + // 这里的这个参数是为了方便使用入参控制点,参数无意义 + var pd = new ParameterDetails[1]; + pd[INDEX_EXPRESSION] = new ParameterDetails { - Index = 0, + Index = INDEX_EXPRESSION, Name = nameof(Expression), - DataType = typeof(string), - ExplicitType = typeof(string), IsExplicitData = false, DataValue = string.Empty, + DataType = typeof(string), + ExplicitType = typeof(string), ArgDataSourceNodeGuid = string.Empty, ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData, NodeModel = this, Convertor = null, ExplicitTypeName = "Value", - Items = Array.Empty(), + Items = null, }; + this.MethodDetails.ParameterDetailss = [.. pd]; + } - this.MethodDetails.ParameterDetailss = new ParameterDetails[] { pd }; + /// + /// 导出方法信息 + /// + /// + /// + public override NodeInfo SaveCustomData(NodeInfo nodeInfo) + { + dynamic data = new ExpandoObject(); + data.Expression = Expression ?? ""; + nodeInfo.CustomData = data; + return nodeInfo; + } + + /// + /// 加载自定义数据 + /// + /// + public override void LoadCustomData(NodeInfo nodeInfo) + { + this.Expression = nodeInfo.CustomData?.Expression ?? ""; } @@ -76,8 +105,6 @@ namespace Serein.NodeFlow.Model parameter = context.TransmissionData(this); } - - try { var newData = SerinExpressionEvaluator.Evaluate(Expression, parameter, out bool isChange); @@ -103,33 +130,5 @@ namespace Serein.NodeFlow.Model } - public override ParameterData[] GetParameterdatas() - { - return [new ParameterData { - Value = Expression, - SourceNodeGuid = this.MethodDetails.ParameterDetailss[0].ArgDataSourceNodeGuid, - SourceType = this.MethodDetails.ParameterDetailss[0].ArgDataSourceType.ToString(), - }]; - } - - - - public override NodeModelBase LoadInfo(NodeInfo nodeInfo) - { - var node = this; - node.Guid = nodeInfo.Guid; - this.Position = nodeInfo.Position;// 加载位置信息 - - var pdInfo1 = nodeInfo.ParameterData[0]; - node.Expression = pdInfo1.Value; // 加载表达式 - - for (int i = 0; i < nodeInfo.ParameterData.Length; i++) - { - ParameterData? pd = nodeInfo.ParameterData[i]; - node.MethodDetails.ParameterDetailss[i].ArgDataSourceNodeGuid = pd.SourceNodeGuid; - node.MethodDetails.ParameterDetailss[i].ArgDataSourceType = EnumHelper.ConvertEnum(pd.SourceType); - } - return this; - } } } diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs index 0f3042a..3358f5e 100644 --- a/NodeFlow/Model/SingleFlipflopNode.cs +++ b/NodeFlow/Model/SingleFlipflopNode.cs @@ -16,13 +16,6 @@ namespace Serein.NodeFlow.Model } - /// - /// 加载完成后调用的方法 - /// - public override void OnCreating() - { - } - /// /// 执行触发器进行等待触发 @@ -60,59 +53,7 @@ namespace Serein.NodeFlow.Model throw new FlipflopException(base.MethodDetails.MethodName + "触发器超时触发。Guid" + base.Guid); } return dynamicFlipflopContext.Value; - - /*try - { - - - - } - catch (FlipflopException ex) - { - if(ex.Type == FlipflopException.CancelClass.CancelFlow) - { - throw; - } - SereinEnv.WriteLine(InfoType.ERROR, $"触发器[{this.MethodDetails.MethodName}]异常:" + ex); - context.NextOrientation = ConnectionInvokeType.None; - context.ExceptionOfRuning = ex; - return null; - } - catch (Exception ex) - { - SereinEnv.WriteLine(InfoType.ERROR, $"触发器[{this.MethodDetails.MethodName}]异常:" + ex); - context.NextOrientation = ConnectionInvokeType.IsError; - context.ExceptionOfRuning = ex; - return null; - } - finally - { - // flipflopTask?.Dispose(); - }*/ } - /// - /// 获取触发器参数 - /// - /// - public override ParameterData[] GetParameterdatas() - { - if (base.MethodDetails.ParameterDetailss.Length > 0) - { - return MethodDetails.ParameterDetailss - .Select(it => new ParameterData - { - SourceNodeGuid = it.ArgDataSourceNodeGuid, - SourceType = it.ArgDataSourceType.ToString(), - State = it.IsExplicitData, - Value = it.DataValue - }) - .ToArray(); - } - else - { - return []; - } - } } } diff --git a/NodeFlow/Model/SingleGlobalDataNode.cs b/NodeFlow/Model/SingleGlobalDataNode.cs index 86ee5da..d07bcd4 100644 --- a/NodeFlow/Model/SingleGlobalDataNode.cs +++ b/NodeFlow/Model/SingleGlobalDataNode.cs @@ -1,30 +1,140 @@ -using Serein.Library; +using Newtonsoft.Json.Linq; +using Serein.Library; using Serein.Library.Api; +using Serein.Library.Utils; using System; using System.Collections.Generic; +using System.Dynamic; using System.Linq; +using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; namespace Serein.NodeFlow.Model { + + + /// + /// Expression Operation - 表达式操作 + /// + [NodeProperty(ValuePath = NodeValuePath.Node)] + public partial class SingleGlobalDataNode : NodeModelBase + { + /// + /// 表达式 + /// + [PropertyInfo(IsNotification = true, CustomCodeAtStart = "ChangeName(value);")] + private string _keyName; + + } + /// /// 全局数据节点 /// - public class SingleGlobalDataNode : NodeModelBase + public partial class SingleGlobalDataNode : NodeModelBase { public SingleGlobalDataNode(IFlowEnvironment environment) : base(environment) { } - public override ParameterData[] GetParameterdatas() + /// + /// 数据节点 + /// + private string? DataNodeGuid; + + /// + /// 设置数据节点 + /// + /// + public void SetDataNode(NodeModelBase dataNode) { - throw new NotImplementedException(); + DataNodeGuid = dataNode.Guid; } - public override void OnCreating() + private void ChangeName(string newName) { - throw new NotImplementedException(); + if(SereinEnv.GetFlowGlobalData(_keyName) == null) + { + return; + } + SereinEnv.ChangeNameFlowGlobalData(_keyName, newName); } + + /// + /// 设置全局数据 + /// + /// + /// + public override async Task ExecutingAsync(IDynamicContext context) + { + if (string.IsNullOrEmpty(KeyName)) + { + context.NextOrientation = ConnectionInvokeType.IsError; + SereinEnv.WriteLine(InfoType.ERROR, $"全局数据的KeyName不能为空[{this.Guid}]"); + return null; + } + if (DataNodeGuid == null) + { + context.NextOrientation = ConnectionInvokeType.IsError; + SereinEnv.WriteLine(InfoType.ERROR, $"全局数据节点没有数据[{this.Guid}]"); + return null; + } + + try + { + var result = await context.Env.InvokeNodeAsync(context, DataNodeGuid); + SereinEnv.AddOrUpdateFlowGlobalData(KeyName, result); + return result; + } + catch (Exception ex) + { + context.NextOrientation = ConnectionInvokeType.IsError; + context.ExceptionOfRuning = ex; + return null; + } + } + + /// + /// 保存全局变量的数据 + /// + /// + /// + public override NodeInfo SaveCustomData(NodeInfo nodeInfo) + { + dynamic data = new ExpandoObject(); + nodeInfo.CustomData = data; + + data.KeyName = KeyName; // 变量名称 + + if (string.IsNullOrEmpty(DataNodeGuid)) + { + return nodeInfo; + } + + data.DataNodeGuid = DataNodeGuid; // 数据节点Guid + + nodeInfo.ChildNodeGuids = [DataNodeGuid]; + return nodeInfo; + } + + /// + /// 加载全局变量的数据 + /// + /// + public override void LoadCustomData(NodeInfo nodeInfo) + { + KeyName = nodeInfo.CustomData?.KeyName; + DataNodeGuid = nodeInfo.CustomData?.DataNodeGuid; + } + + /// + /// 需要移除数据节点 + /// + public override void Remove() + { + // 移除数据节点 + _ = this.Env.RemoveNodeAsync(DataNodeGuid); + } + } } diff --git a/NodeFlow/MoveNodeData.cs b/NodeFlow/MoveNodeData.cs deleted file mode 100644 index 72f2bf5..0000000 --- a/NodeFlow/MoveNodeData.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Serein.NodeFlow -{ - - -} diff --git a/NodeFlow/NodeMVVMManagement.cs b/NodeFlow/NodeMVVMManagement.cs new file mode 100644 index 0000000..a5a327a --- /dev/null +++ b/NodeFlow/NodeMVVMManagement.cs @@ -0,0 +1,110 @@ +using Serein.Library; +using Serein.Library.Utils; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow +{ + /// + /// 节点类型 + /// + public class NodeMVVM + { + /// + /// 节点类型 + /// + public required NodeControlType NodeType { get; set; } + + /// + /// 节点Model类型 + /// + public required Type ModelType { get; set; } + + /// + /// 节点视图控件类型 + /// + public Type? ControlType { get; set; } + + /// + /// 节点视图VM类型 + /// + public Type? ViewModelType { get; set; } + + public override string ToString() + { + return $"$[{NodeType}]类型信息 : ModelType->{ModelType};ControlType->{ControlType};ViewModelType->{ViewModelType}"; + } + } + + /// + /// 节点 数据、视图、VM 管理 + /// + public static class NodeMVVMManagement + { + /// + /// 节点对应的控件类型 + /// + private static ConcurrentDictionary FlowNodeTypes { get; } = []; + + /// + /// 注册 Model 类型 + /// + /// + /// + public static bool RegisterModel(NodeControlType type, Type modelType) + { + if(FlowNodeTypes.TryGetValue(type,out var nodeMVVM)) + { + SereinEnv.WriteLine(InfoType.WARN, $"无法为节点[{type}]注册Model类型[{modelType}],已经注册的类型为{nodeMVVM}。"); + return false; + } + nodeMVVM = new NodeMVVM + { + NodeType = type, + ModelType = modelType + }; + return FlowNodeTypes.TryAdd(type, nodeMVVM); + } + + /// + /// 注册 UI 类型 + /// + /// + /// + /// + public static bool RegisterUI(NodeControlType type, Type controlType,Type viewModelType) + { + if (!FlowNodeTypes.TryGetValue(type, out var nodeMVVM)) + { + SereinEnv.WriteLine(InfoType.WARN, $"无法为节点[{type}]注册UI类型[{controlType}][{viewModelType}],当前类型尚未注册。"); + return false; + } + nodeMVVM.ControlType = controlType; + nodeMVVM.ViewModelType = viewModelType; + return true; + } + + /// + /// 获取相应的类型 + /// + /// + /// + /// + public static bool TryGetType(NodeControlType type, out NodeMVVM nodeMVVM) + { + if( FlowNodeTypes.TryGetValue(type, out nodeMVVM)) + { + return nodeMVVM != null; + } + else + { + + return false; + } + } + } +} diff --git a/NodeFlow/Tool/FlowLibrary.cs b/NodeFlow/Tool/FlowLibrary.cs index d5da55d..e27ed58 100644 --- a/NodeFlow/Tool/FlowLibrary.cs +++ b/NodeFlow/Tool/FlowLibrary.cs @@ -64,6 +64,10 @@ namespace Serein.NodeFlow } + /// + /// 转为依赖信息 + /// + /// public NodeLibraryInfo ToInfo() { return new NodeLibraryInfo @@ -72,6 +76,8 @@ namespace Serein.NodeFlow FileName = Path.GetFileName(assembly.Location), FilePath = assembly.Location, }; + + } diff --git a/Serein.Library.MyGenerator/Attribute.cs b/Serein.Library.MyGenerator/Attribute.cs index b61d09c..bca2463 100644 --- a/Serein.Library.MyGenerator/Attribute.cs +++ b/Serein.Library.MyGenerator/Attribute.cs @@ -69,9 +69,14 @@ namespace Serein.Library public bool IsProtection = false; /// - /// 自定义代码 + /// 自定义代码(属性变更前) /// - public string CustomCode = null; + public string CustomCodeAtStart = null; + + /// + /// 自定义代码(属性变更后) + /// + public string CustomCodeAtEnd = null; } diff --git a/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs b/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs index b938212..f66c057 100644 --- a/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs +++ b/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs @@ -175,6 +175,12 @@ namespace Serein.Library.NodeGenerator sb.AppendLine(" {"); sb.AppendLine($" if ({fieldName} {(isProtection ? "== default" : "!= value")})"); // 非保护的Setter sb.AppendLine(" {"); + if (attributeInfo.Search(nameof(PropertyInfo), nameof(PropertyInfo.CustomCodeAtStart), value => !string.IsNullOrEmpty(value))) // 自定义代码 + { + var customCode = attributeInfo[nameof(PropertyInfo)][nameof(PropertyInfo.CustomCodeAtStart)] as string; + customCode = customCode.Trim().Substring(1, customCode.Length - 2); + sb.AddCode(5, $"{customCode} // 添加的自定义代码"); + } sb.AppendLine($" SetProperty<{fieldType}>(ref {fieldName}, value); // 通知UI属性发生改变了"); if (attributeInfo.Search(nameof(PropertyInfo), nameof(PropertyInfo.IsPrint), value => bool.Parse(value))) // 是否打印 { @@ -211,9 +217,9 @@ namespace Serein.Library.NodeGenerator } } - if (attributeInfo.Search(nameof(PropertyInfo), nameof(PropertyInfo.CustomCode), value => !string.IsNullOrEmpty(value))) // 是否打印 + if (attributeInfo.Search(nameof(PropertyInfo), nameof(PropertyInfo.CustomCodeAtEnd), value => !string.IsNullOrEmpty(value))) // 自定义代码 { - var customCode = attributeInfo[nameof(PropertyInfo)][nameof(PropertyInfo.CustomCode)] as string; + var customCode = attributeInfo[nameof(PropertyInfo)][nameof(PropertyInfo.CustomCodeAtEnd)] as string; customCode = customCode.Trim().Substring(1, customCode.Length - 2); sb.AddCode(5, $"{customCode} // 添加的自定义代码"); } diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs index 661a93a..2857cab 100644 --- a/WorkBench/App.xaml.cs +++ b/WorkBench/App.xaml.cs @@ -16,13 +16,14 @@ namespace Serein.Workbench { #if DEBUG - if (1 == 1 ) + if (1 == 1) { string filePath; filePath = @"F:\临时\project\linux\project.dnf"; filePath = @"F:\临时\project\linux\http\project.dnf"; filePath = @"F:\临时\project\yolo flow\project.dnf"; filePath = @"F:\临时\project\data\project.dnf"; + filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\Release\net8.0\project.dnf"; filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\Release\net8.0\PLCproject.dnf"; string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容 App.FlowProjectData = JsonConvert.DeserializeObject(content); diff --git a/WorkBench/MainWindow.xaml b/WorkBench/MainWindow.xaml index 2dd6968..1f49ba4 100644 --- a/WorkBench/MainWindow.xaml +++ b/WorkBench/MainWindow.xaml @@ -28,8 +28,10 @@ - + + + @@ -82,6 +84,7 @@ + @@ -103,6 +106,7 @@ + + > - /// 流程接口 + /// 流程环境装饰器,方便在本地与远程环境下切换 /// - private IFlowEnvironment EnvDecorator { get; } + private IFlowEnvironment EnvDecorator => ViewModel.FlowEnvironment; private MainWindowViewModel ViewModel { get; set; } + + /// + /// 节点对应的控件类型 + /// + // private Dictionary NodeUITypes { get; } = []; + /// /// 存储所有与节点有关的控件 - /// 任何情景下都尽量避免直接操作 ViewModel 中的 NodeModel 节点, + /// 任何情景下都应避免直接操作 ViewModel 中的 NodeModel 节点, /// 而是应该调用 FlowEnvironment 提供接口进行操作, /// 因为 Workbench 应该更加关注UI视觉效果,而非直接干扰流程环境运行的逻辑。 /// 之所以暴露 NodeModel 属性,因为有些场景下不可避免的需要直接获取节点的属性。 @@ -150,13 +158,20 @@ namespace Serein.Workbench ViewModel = new MainWindowViewModel(this); this.DataContext = ViewModel; InitializeComponent(); - EnvDecorator = ViewModel.FlowEnvironment; - ViewObjectViewer.FlowEnvironment = EnvDecorator; - IOCObjectViewer.FlowEnvironment = EnvDecorator; - IOCObjectViewer.SelectObj += ViewObjectViewer.LoadObjectInformation; + ViewObjectViewer.FlowEnvironment = EnvDecorator; // 设置 节点树视图 的环境为装饰器 + IOCObjectViewer.FlowEnvironment = EnvDecorator; // 设置 IOC容器视图 的环境为装饰器 + IOCObjectViewer.SelectObj += ViewObjectViewer.LoadObjectInformation; // 使选择 IOC容器视图 的某项(对象)时,可以在 数据视图 呈现数据 + + #region 为 NodeControlType 枚举 不同项添加对应的 Control类型 、 ViewModel类型 + NodeMVVMManagement.RegisterUI(NodeControlType.Action, typeof(ActionNodeControl), typeof(ActionNodeControlViewModel)); + NodeMVVMManagement.RegisterUI(NodeControlType.Flipflop, typeof(FlipflopNodeControl), typeof(FlipflopNodeControlViewModel)); + NodeMVVMManagement.RegisterUI(NodeControlType.ExpOp, typeof(ExpOpNodeControl), typeof(ExpOpNodeControlViewModel)); + NodeMVVMManagement.RegisterUI(NodeControlType.ExpCondition, typeof(ConditionNodeControl), typeof(ConditionNodeControlViewModel)); + NodeMVVMManagement.RegisterUI(NodeControlType.ConditionRegion, typeof(ConditionRegionControl), typeof(ConditionRegionNodeControlViewModel)); + NodeMVVMManagement.RegisterUI(NodeControlType.GlobalData, typeof(GlobalDataControl), typeof(GlobalDataNodeControlViewModel)); + #endregion - #region 缩放平移容器 canvasTransformGroup = new TransformGroup(); @@ -174,7 +189,6 @@ namespace Serein.Workbench EnvDecorator.LoadProject(new FlowEnvInfo { Project = App.FlowProjectData }, App.FileDataPath); // 加载项目 } - } @@ -185,6 +199,7 @@ namespace Serein.Workbench private void InitFlowEnvironmentEvent() { EnvDecorator.OnDllLoad += FlowEnvironment_DllLoadEvent; + EnvDecorator.OnProjectSaving += EnvDecorator_OnProjectSaving; EnvDecorator.OnProjectLoaded += FlowEnvironment_OnProjectLoaded; EnvDecorator.OnStartNodeChange += FlowEnvironment_StartNodeChangeEvent; EnvDecorator.OnNodeConnectChange += FlowEnvironment_NodeConnectChangeEvemt; @@ -202,7 +217,6 @@ namespace Serein.Workbench EnvDecorator.OnNodeLocated += FlowEnvironment_OnNodeLocate; EnvDecorator.OnNodeMoved += FlowEnvironment_OnNodeMoved; EnvDecorator.OnEnvOut += FlowEnvironment_OnEnvOut; - // this.EnvDecorator.SetConsoleOut(); // 设置输出 } /// @@ -211,6 +225,7 @@ namespace Serein.Workbench private void ResetFlowEnvironmentEvent() { EnvDecorator.OnDllLoad -= FlowEnvironment_DllLoadEvent; + EnvDecorator.OnProjectSaving -= EnvDecorator_OnProjectSaving; EnvDecorator.OnProjectLoaded -= FlowEnvironment_OnProjectLoaded; EnvDecorator.OnStartNodeChange -= FlowEnvironment_StartNodeChangeEvent; EnvDecorator.OnNodeConnectChange -= FlowEnvironment_NodeConnectChangeEvemt; @@ -290,6 +305,99 @@ namespace Serein.Workbench LogOutWindow.AppendText($"{DateTime.UtcNow} [{type}] : {value}{Environment.NewLine}"); } + + /// + /// 需要保存项目 + /// + /// + /// + private void EnvDecorator_OnProjectSaving(ProjectSavingEventArgs eventArgs) + { + var projectData = EnvDecorator.GetProjectInfoAsync() + .GetAwaiter().GetResult(); // 保存项目 + + projectData.Basic = new Basic + { + Canvas = new FlowCanvas + { + Height = FlowChartCanvas.Height, + Width = FlowChartCanvas.Width, + ViewX = translateTransform.X, + ViewY = translateTransform.Y, + ScaleX = scaleTransform.ScaleX, + ScaleY = scaleTransform.ScaleY, + }, + Versions = "1", + }; + + // 创建一个新的保存文件对话框 + SaveFileDialog saveFileDialog = new() + { + Filter = "DynamicNodeFlow Files (*.dnf)|*.dnf", + DefaultExt = "dnf", + FileName = "project.dnf" + // FileName = System.IO.Path.GetFileName(App.FileDataPath) + }; + + // 显示保存文件对话框 + bool? result = saveFileDialog.ShowDialog(); + // 如果用户选择了文件并点击了保存按钮 + if (result == false) + { + SereinEnv.WriteLine(InfoType.ERROR, "取消保存文件"); + return; + } + + var savePath = saveFileDialog.FileName; + string? librarySavePath = System.IO.Path.GetDirectoryName(savePath); + if (string.IsNullOrEmpty(librarySavePath)) + { + SereinEnv.WriteLine(InfoType.ERROR, "保存项目DLL时返回了意外的文件保存路径"); + return; + } + + + Uri saveProjectFileUri = new Uri(savePath); + SereinEnv.WriteLine(InfoType.INFO, "项目文件保存路径:" + savePath); + for (int index = 0; index < projectData.Librarys.Length; index++) + { + NodeLibraryInfo? library = projectData.Librarys[index]; + string sourceFile = new Uri(library.FilePath).LocalPath; // 源文件夹 + string targetPath = System.IO.Path.Combine(librarySavePath, library.FileName); // 目标文件夹 + SereinEnv.WriteLine(InfoType.INFO, $"源路径 : {sourceFile}"); + SereinEnv.WriteLine(InfoType.INFO, $"目标路径 : {targetPath}"); + + try + { + File.Copy(sourceFile, targetPath, true); + } + catch (IOException ex) + { + + SereinEnv.WriteLine(InfoType.ERROR, ex.Message); + } + var dirName = System.IO.Path.GetDirectoryName(targetPath); + if (!string.IsNullOrEmpty(dirName)) + { + var tmpUri2 = new Uri(targetPath); + var relativePath = saveProjectFileUri.MakeRelativeUri(tmpUri2).ToString(); // 转为类库的相对文件路径 + + + + + //string relativePath = System.IO.Path.GetRelativePath(savePath, targetPath); + projectData.Librarys[index].FilePath = relativePath; + } + + } + + JObject projectJsonData = JObject.FromObject(projectData); + File.WriteAllText(savePath, projectJsonData.ToString()); + + + } + + /// /// 加载完成 /// @@ -572,16 +680,20 @@ namespace Serein.Workbench // MethodDetails methodDetailss = eventArgs.MethodDetailss; PositionOfUI position = eventArgs.Position; - // 创建对应控件 - NodeControlBase? nodeControl = nodeModelBase.ControlType switch + if(!NodeMVVMManagement.TryGetType(nodeModelBase.ControlType, out var nodeMVVM)) { - NodeControlType.Action => CreateNodeControl(nodeModelBase), //typeof(ActionNodeControl), - NodeControlType.Flipflop => CreateNodeControl(nodeModelBase), - NodeControlType.ExpCondition => CreateNodeControl(nodeModelBase), - NodeControlType.ExpOp => CreateNodeControl(nodeModelBase), - NodeControlType.ConditionRegion => CreateNodeControl(nodeModelBase), - _ => null, - }; + SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModelBase.ControlType}节点,节点类型尚未注册。"); + return; + } + if(nodeMVVM.ControlType == null + || nodeMVVM.ViewModelType == null) + { + SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModelBase.ControlType}节点,UI类型尚未注册(请通过 NodeMVVMManagement.RegisterUI() 方法进行注册)。"); + return; + } + + NodeControlBase nodeControl = CreateNodeControl(nodeMVVM.ControlType, nodeMVVM.ViewModelType, nodeModelBase); + if (nodeControl is null) { return; @@ -589,6 +701,7 @@ namespace Serein.Workbench NodeControls.TryAdd(nodeModelBase.Guid, nodeControl); if (eventArgs.IsAddInRegion && NodeControls.TryGetValue(eventArgs.RegeionGuid, out NodeControlBase? regionControl)) { + // 这里的条件是用于加载项目文件时,直接加载在区域中,而不用再判断控件 if (regionControl is not null) { TryPlaceNodeInRegion(regionControl, nodeControl); @@ -597,8 +710,16 @@ namespace Serein.Workbench } else { - if (!TryPlaceNodeInRegion(nodeControl, position)) // 判断是否为区域,如果是,将节点放置在区域中 + // 这里是正常的编辑流程 + // 判断是否为区域 + if (TryPlaceNodeInRegion(nodeControl, position, out var targetNodeControl)) { + // 需要将节点放置在区域中 + TryPlaceNodeInRegion(targetNodeControl, nodeControl); + } + else + { + // 并非区域,需要手动添加 PlaceNodeOnCanvas(nodeControl, position.X, position.Y); // 将节点放置在画布上 } } @@ -940,7 +1061,7 @@ namespace Serein.Workbench // } // } //} - + #endregion #region 节点控件的创建 @@ -950,7 +1071,8 @@ namespace Serein.Workbench /// 创建了节点,添加到画布。配置默认事件 /// /// - /// + /// + /// private void PlaceNodeOnCanvas(NodeControlBase nodeControl, double x, double y) { // 添加控件到画布 @@ -974,35 +1096,6 @@ namespace Serein.Workbench } - /// - /// 开始创建连接 True线 操作,设置起始块和绘制连接线。 - /// - //private void StartConnection(NodeControlBase startNodeControl, ConnectionInvokeType connectionType) - //{ - // var tf = Connections.FirstOrDefault(it => it.Start.MyNode.Guid == startNodeControl.ViewModel.NodeModel.Guid)?.Type; - // IsConnecting = true; - // currentConnectionType = connectionType; - // startConnectNodeControl = startNodeControl; - - // // 确保起点和终点位置的正确顺序 - // currentLine = new Line - // { - // Stroke = connectionType == ConnectionInvokeType.IsSucceed ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")) - // : connectionType == ConnectionInvokeType.IsFail ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")) - // : connectionType == ConnectionInvokeType.IsError ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B")) - // : new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4")), - // StrokeDashArray = new DoubleCollection([2]), - // StrokeThickness = 2, - // X1 = Canvas.GetLeft(startConnectNodeControl) + startConnectNodeControl.ActualWidth / 2, - // Y1 = Canvas.GetTop(startConnectNodeControl) + startConnectNodeControl.ActualHeight / 2, - // X2 = Canvas.GetLeft(startConnectNodeControl) + startConnectNodeControl.ActualWidth / 2, // 初始时终点与起点重合 - // Y2 = Canvas.GetTop(startConnectNodeControl) + startConnectNodeControl.ActualHeight / 2, - // }; - - // FlowChartCanvas.Children.Add(currentLine); - // this.KeyDown += MainWindow_KeyDown; - //} - #endregion #region 配置右键菜单 @@ -1294,6 +1387,7 @@ namespace Serein.Workbench Type when typeof(ConditionRegionControl).IsAssignableFrom(droppedType) => NodeControlType.ConditionRegion, // 条件区域 Type when typeof(ConditionNodeControl).IsAssignableFrom(droppedType) => NodeControlType.ExpCondition, Type when typeof(ExpOpNodeControl).IsAssignableFrom(droppedType) => NodeControlType.ExpOp, + Type when typeof(GlobalDataControl).IsAssignableFrom(droppedType) => NodeControlType.GlobalData, _ => NodeControlType.None, }; if (nodeControlType != NodeControlType.None) @@ -1314,12 +1408,13 @@ namespace Serein.Workbench } /// - /// 判断是否为区域,如果是,将节点放置在区域中 + /// 尝试判断是否为区域,如果是,将节点放置在区域中 /// /// /// + /// 目标节点控件 /// - private bool TryPlaceNodeInRegion(NodeControlBase nodeControl, PositionOfUI position) + private bool TryPlaceNodeInRegion(NodeControlBase nodeControl, PositionOfUI position, out NodeControlBase targetNodeControl) { var point = new Point(position.X, position.Y); HitTestResult hitTestResult = VisualTreeHelper.HitTest(FlowChartCanvas, point); @@ -1331,14 +1426,24 @@ namespace Serein.Workbench ConditionRegionControl? conditionRegion = GetParentOfType(hitElement); if (conditionRegion is not null) { - TryPlaceNodeInRegion(conditionRegion, nodeControl); + targetNodeControl = conditionRegion; //// 如果存在条件区域容器 //conditionRegion.AddCondition(nodeControl); return true; } - + } + // 准备放置全局数据控件 + else + { + GlobalDataControl? globalDataControl = GetParentOfType(hitElement); + if (globalDataControl is not null) + { + targetNodeControl = globalDataControl; + return true; + } } } + targetNodeControl = null; return false; } @@ -1352,13 +1457,20 @@ namespace Serein.Workbench // 准备放置条件表达式控件 if (nodeControl.ViewModel.NodeModel.ControlType == NodeControlType.ExpCondition) { - ConditionRegionControl? conditionRegion = regionControl as ConditionRegionControl; - if (conditionRegion is not null) + if (regionControl is ConditionRegionControl conditionRegion) { - // 如果存在条件区域容器 - conditionRegion.AddCondition(nodeControl); + conditionRegion.AddCondition(nodeControl); // 条件区域容器 } } + else if(regionControl.ViewModel.NodeModel.ControlType == NodeControlType.GlobalData) + { + if (regionControl is GlobalDataControl globalDataControl) + { + // 全局数据节点容器 + globalDataControl.SetDataNodeControl(nodeControl); + } + } + } /// @@ -2252,28 +2364,38 @@ namespace Serein.Workbench #region 静态方法:创建节点,创建菜单子项,获取区域 - - private static TNodeControl CreateNodeControl(NodeModelBase model) - where TNodeControl : NodeControlBase - where TNodeViewModel : NodeControlViewModelBase + /// + /// 创建节点控件 + /// + /// 节点控件视图控件类型 + /// 节点控件ViewModel类型 + /// 节点Model实例 + /// + /// 无法创建节点控件 + private static NodeControlBase CreateNodeControl(Type controlType, Type viewModelType, NodeModelBase model) { - - if (model is null) + if ((controlType is null) + || viewModelType is null + || model is null) { throw new Exception("无法创建节点控件"); } + if (typeof(NodeControlBase).IsSubclassOf(controlType) || typeof(NodeControlViewModelBase).IsSubclassOf(viewModelType)) + { + throw new Exception("无法创建节点控件"); + } + if (string.IsNullOrEmpty(model.Guid)) { model.Guid = Guid.NewGuid().ToString(); } - var viewModel = Activator.CreateInstance(typeof(TNodeViewModel), [model]); - var controlObj = Activator.CreateInstance(typeof(TNodeControl), [viewModel]); - if (controlObj is TNodeControl nodeControl) + + // Convert.ChangeType(model, targetType); + + var viewModel = Activator.CreateInstance(viewModelType, [model]); + var controlObj = Activator.CreateInstance(controlType, [viewModel]); + if (controlObj is NodeControlBase nodeControl) { - - //nodeControl.ExecuteJunctionControl = new NodeExecuteJunctionControl(this); - - return nodeControl; } else @@ -2282,39 +2404,6 @@ namespace Serein.Workbench } } - //private static TControl CreateNodeControl(MethodDetails? methodDetails = null) - // where TNode : NodeModelBase - // where TControl : NodeControlBase - // where TViewModel : NodeControlViewModelBase - //{ - - // var nodeObj = Activator.CreateInstance(typeof(TNode)); - // var nodeBase = nodeObj as NodeModelBase ?? throw new Exception("无法创建节点控件"); - - - // if (string.IsNullOrEmpty(nodeBase.Guid)) - // { - // nodeBase.Guid = Guid.NewGuid().ToString(); - // } - // if (methodDetails != null) - // { - // var md = methodDetails.Clone(nodeBase); // 首先创建属于节点的方法信息,然后创建属于节点的参数信息 - // nodeBase.DisplayName = md.MethodTips; - // nodeBase.MethodDetails = md; - // } - - // var viewModel = Activator.CreateInstance(typeof(TViewModel), [nodeObj]); - // var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]); - // if (controlObj is TControl control) - // { - // return control; - // } - // else - // { - // throw new Exception("无法创建节点控件"); - // } - //} - /// /// 创建菜单子项 @@ -2462,94 +2551,9 @@ namespace Serein.Workbench /// private async void ButtonSaveFile_Click(object sender, RoutedEventArgs e) { - var projectData = await EnvDecorator.GetProjectInfoAsync(); - - projectData.Basic = new Basic - { - Canvas = new FlowCanvas - { - Height = FlowChartCanvas.Height, - Width = FlowChartCanvas.Width, - ViewX = translateTransform.X, - ViewY = translateTransform.Y, - ScaleX = scaleTransform.ScaleX, - ScaleY = scaleTransform.ScaleY, - }, - Versions = "1", - }; - - //foreach (var node in projectData.Nodes) - //{ - // if (NodeControls.TryGetValue(node.Guid, out var nodeControl)) - // { - // Point positionRelativeToParent = nodeControl.TranslatePoint(new Point(0, 0), FlowChartCanvas); - // node.Position = new PositionOfUI(positionRelativeToParent.X, positionRelativeToParent.Y); - // } - //} - if (!SaveContentToFile(out string savePath, out Action? savaProjectFile)) - { - SereinEnv.WriteLine(InfoType.ERROR, "保存项目DLL时返回了意外的文件保存路径"); - return; - } - - string? librarySavePath = System.IO.Path.GetDirectoryName(savePath); - if (string.IsNullOrEmpty(librarySavePath)) - { - SereinEnv.WriteLine(InfoType.ERROR, "保存项目DLL时返回了意外的文件保存路径"); - return; - } - SereinEnv.WriteLine(InfoType.INFO, "项目文件保存路径:" + savePath); - for (int index = 0; index < projectData.Librarys.Length; index++) - { - NodeLibraryInfo? library = projectData.Librarys[index]; - try - { - string targetPath = System.IO.Path.Combine(librarySavePath, library.FilePath); // 目标文件夹 -#if WINDOWS - string sourceFile = library.FilePath; // 源文件夹 -#else - string sourceFile = new Uri(library.Path).LocalPath; -#endif - // 复制文件到目标目录 - File.Copy(sourceFile, targetPath, true); - - // 获取相对路径 - string relativePath = System.IO.Path.GetRelativePath(savePath, targetPath); - projectData.Librarys[index].FilePath = relativePath; - } - catch (Exception ex) - { - SereinEnv.WriteLine(InfoType.ERROR, ex.Message); - } - } - - JObject projectJsonData = JObject.FromObject(projectData); - savaProjectFile?.Invoke(savePath, projectJsonData.ToString()); - } - public static bool SaveContentToFile(out string savePath, out Action? savaProjectFile) - { - // 创建一个新的保存文件对话框 - SaveFileDialog saveFileDialog = new() - { - Filter = "DynamicNodeFlow Files (*.dnf)|*.dnf", - DefaultExt = "dnf", - FileName = "project.dnf" - }; - - // 显示保存文件对话框 - bool? result = saveFileDialog.ShowDialog(); - - // 如果用户选择了文件并点击了保存按钮 - if (result == true) - { - savePath = saveFileDialog.FileName; - savaProjectFile = File.WriteAllText; - return true; - } - savePath = string.Empty; - savaProjectFile = null; - return false; + EnvDecorator.SaveProject(); } + /// /// 打开本地项目文件 diff --git a/WorkBench/Node/View/ActionNodeControl.xaml b/WorkBench/Node/View/ActionNodeControl.xaml index 7d3064d..4b6698e 100644 --- a/WorkBench/Node/View/ActionNodeControl.xaml +++ b/WorkBench/Node/View/ActionNodeControl.xaml @@ -19,7 +19,7 @@ - + @@ -47,9 +47,9 @@ - - - + + + @@ -61,11 +61,10 @@ - + - - + diff --git a/WorkBench/Node/View/ConditionNodeControl.xaml b/WorkBench/Node/View/ConditionNodeControl.xaml index 73b59e2..f6f8c07 100644 --- a/WorkBench/Node/View/ConditionNodeControl.xaml +++ b/WorkBench/Node/View/ConditionNodeControl.xaml @@ -56,14 +56,15 @@ - - + +