From 5b0ba84fd66f0e1d1f5aadc1b806449cb39f811e Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Tue, 24 Dec 2024 22:23:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=8E=E8=8A=82=E7=82=B9Model=E8=A7=A3?= =?UTF-8?q?=E8=80=A6=E5=87=BA=E5=AE=B9=E5=99=A8=E6=8E=A5=E5=8F=A3=EF=BC=8C?= =?UTF-8?q?=E9=87=8D=E6=96=B0=E8=AE=BE=E8=AE=A1=E4=BA=86=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E7=9A=84=E4=BF=9D=E5=AD=98=E3=80=81=E5=8A=A0=E8=BD=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FlowStartTool/FlowEnv.cs | 2 +- Library/Api/IFlowEnvironment.cs | 100 ++- Library/Api/INodeContainer.cs | 31 + Library/FlowNode/NodeModelBaseData.cs | 28 +- Library/FlowNode/NodeModelBaseFunc.cs | 2 + Library/FlowNode/ParameterDetails.cs | 2 +- Library/FlowNode/SereinProjectData.cs | 9 +- .../Handle/WebSocketMsgHandleHelper.cs | 4 +- .../SerinExpressionEvaluator.cs | 10 +- NodeFlow/Env/EnvMsgTheme.cs | 5 + NodeFlow/Env/FlowEnvironment.cs | 333 +++------- NodeFlow/Env/FlowEnvironmentDecorator.cs | 24 + NodeFlow/Env/MsgControllerOfClient.cs | 6 + NodeFlow/Env/MsgControllerOfServer.cs | 16 + NodeFlow/Env/RemoteFlowEnvironment.cs | 578 +++++++++++------- NodeFlow/FlowFunc.cs | 54 +- NodeFlow/Model/SingleConditionNode.cs | 5 + NodeFlow/Model/SingleExpOpNode.cs | 5 + NodeFlow/Model/SingleGlobalDataNode.cs | 56 +- NodeFlow/Model/SingleScriptNode.cs | 7 + Serein.Script/Serein.Script.csproj | 3 + Serein.Script/TestExpression/Class1.cs | 70 +++ WorkBench/App.xaml.cs | 33 +- WorkBench/LogWindow.xaml.cs | 14 +- WorkBench/MainWindow.xaml.cs | 242 ++++---- WorkBench/Themes/MethodDetailsControl.xaml | 15 +- Workbench/Node/INodeContainerControl.cs | 33 + Workbench/Node/INodeJunction.cs | 3 + Workbench/Node/View/GlobalDataControl.xaml.cs | 33 +- .../GlobalDataNodeControlViewModel.cs | 16 +- 30 files changed, 979 insertions(+), 760 deletions(-) create mode 100644 Library/Api/INodeContainer.cs create mode 100644 Serein.Script/TestExpression/Class1.cs create mode 100644 Workbench/Node/INodeContainerControl.cs diff --git a/FlowStartTool/FlowEnv.cs b/FlowStartTool/FlowEnv.cs index fed72d1..7186fa9 100644 --- a/FlowStartTool/FlowEnv.cs +++ b/FlowStartTool/FlowEnv.cs @@ -41,7 +41,7 @@ namespace Serein.FlowStartTool // 获取环境输出 Env.OnEnvOut += (infoType, value) => { - Console.WriteLine($"{DateTime.UtcNow} [{infoType}] : {value}{Environment.NewLine}"); + Console.WriteLine($"{DateTime.Now} [{infoType}] : {value}{Environment.NewLine}"); }; await Env.StartRemoteServerAsync(7525); // 启动 web socket 监听远程请求 diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 0ba3861..f185196 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -53,6 +53,12 @@ namespace Serein.Library.Api /// public delegate void NodeCreateHandler(NodeCreateEventArgs eventArgs); + /// + /// 容器节点与子项节点的关系发生改变 + /// + /// + public delegate void NodeContainerChildChangeHandler(NodeContainerChildChangeEventArgs eventArgs); + /// /// 环境中流程起始节点发生了改变 /// @@ -279,34 +285,80 @@ namespace Serein.Library.Api /// /// 节点对象 /// 位置 - public NodeCreateEventArgs(object nodeModel, PositionOfUI position) + public NodeCreateEventArgs(NodeModelBase nodeModel, PositionOfUI position) { this.NodeModel = nodeModel; this.Position = position; } - /// - /// 区域子项节点添加事件参数 - /// - /// 节点对象 - /// 是否添加在区域中 - /// 区域Guid - public NodeCreateEventArgs(object nodeModel, bool isAddInRegion, string regeionGuid) - { - this.NodeModel = nodeModel; - this.RegeionGuid = regeionGuid; - this.IsAddInRegion = isAddInRegion; - } + ///// + ///// 区域子项节点添加事件参数 + ///// + ///// 节点对象 + ///// 是否添加在区域中 + ///// 区域Guid + //public NodeCreateEventArgs(object nodeModel, bool isAddInRegion, string regeionGuid) + //{ + // this.NodeModel = nodeModel; + // this.RegeionGuid = regeionGuid; + // this.IsAddInRegion = isAddInRegion; + //} /// /// 节点Model对象,目前需要手动转换对应的类型 /// public object NodeModel { get; private set; } public PositionOfUI Position { get; private set; } - public bool IsAddInRegion { get; private set; } + //public bool IsAddInRegion { get; private set; } public string RegeionGuid { get; private set; } } + /// + /// 节点父子关系改变 + /// + public class NodeContainerChildChangeEventArgs : FlowEventArgs + { + /// + /// 变更类型 + /// + public enum Type + { + /// + /// 放置 + /// + Place, + /// + /// 取出 + /// + TakeOut + } + /// + /// + /// + /// 子项节点 + /// 容器节点 + /// 类别 + public NodeContainerChildChangeEventArgs(string childNodeGuid, string containerNodeGuid, Type state) + { + ChildNodeGuid = childNodeGuid; + ContainerNodeGuid = containerNodeGuid; + State = state; + } + /// + /// 子节点,该数据为此次时间的主节点 + /// + public string ChildNodeGuid { get; private set; } + /// + /// 父节点 + /// + public string ContainerNodeGuid { get; private set; } + + /// + /// 改变类型 + /// + public Type State { get; private set; } + } + /// /// 环境中移除了一个节点 /// @@ -611,6 +663,11 @@ namespace Serein.Library.Api /// event NodeRemoveHandler OnNodeRemove; + /// + /// 节点父子关系发生改变事件 + /// + event NodeContainerChildChangeHandler OnNodeParentChildChange; + /// /// 起始节点变化事件 /// @@ -647,7 +704,7 @@ namespace Serein.Library.Api event NodeLocatedHandler OnNodeLocated; /// - /// 节点移动了(远程插件) + /// 节点移动了(远程环境) /// event NodeMovedHandler OnNodeMoved; @@ -811,13 +868,22 @@ namespace Serein.Library.Api Task LoadNodeInfosAsync(List nodeInfos); /// - /// 创建节点/区域/基础控件 + /// 创建节点 /// - /// 节点/区域/基础控件类型 + /// 控件类型 /// 节点在画布上的位置( /// 节点绑定的方法说明 Task CreateNodeAsync(NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null); + /// + /// 将节点放置在容器中/从容器中取出 + /// + /// 子节点(主要节点) + /// 父节点 + /// 是否组合(反之为分解节点组合关系) + /// + Task ChangeNodeContainerChild(string childNodeGuid,string parentNodeGuid,bool isAssembly); + /// /// 设置两个节点某个类型的方法调用关系为优先调用 /// diff --git a/Library/Api/INodeContainer.cs b/Library/Api/INodeContainer.cs new file mode 100644 index 0000000..5473605 --- /dev/null +++ b/Library/Api/INodeContainer.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Api +{ + /// + /// 约束具有容器功能的节点应该有什么方法 + /// + public interface INodeContainer + { + /// + /// 放置一个节点 + /// + /// + void PlaceNode(NodeModelBase nodeModel); + + /// + /// 取出一个节点 + /// + /// + void TakeOutNode(NodeModelBase nodeModel); + + /// + /// 取出所有节点(用于删除容器) + /// + void TakeOutAll(); + } +} diff --git a/Library/FlowNode/NodeModelBaseData.cs b/Library/FlowNode/NodeModelBaseData.cs index 99d0749..f7bb63a 100644 --- a/Library/FlowNode/NodeModelBaseData.cs +++ b/Library/FlowNode/NodeModelBaseData.cs @@ -12,7 +12,7 @@ namespace Serein.Library /// - /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 + /// 节点基类(数据) /// [NodeProperty(ValuePath = NodeValuePath.None)] public abstract partial class NodeModelBase : IDynamicFlowNode @@ -69,6 +69,16 @@ namespace Serein.Library public abstract partial class NodeModelBase : IDynamicFlowNode { + /// + /// 是否为基础节点 + /// + public virtual bool IsBase { get; } = false; + + /// + /// 可以放置多少个节点 + /// + public virtual int MaxChildrenCount { get; } = 0; + public NodeModelBase(IFlowEnvironment environment) { PreviousNodes = new Dictionary>(); @@ -78,21 +88,33 @@ namespace Serein.Library PreviousNodes[ctType] = new List(); SuccessorNodes[ctType] = new List(); } + ChildrenNode = new List(); DebugSetting = new NodeDebugSetting(this); this.Env = environment; } /// - /// 不同分支的父节点 + /// 不同分支的父节点(流程调用) /// public Dictionary> PreviousNodes { get; } /// - /// 不同分支的子节点 + /// 不同分支的子节点(流程调用) /// public Dictionary> SuccessorNodes { get; } + /// + /// 该节点的父级节点(容器) + /// + public NodeModelBase ParentNode { get; set; } = null; + + /// + /// 该节点的子项节点(容器) + /// + public List ChildrenNode { get; } + + } } diff --git a/Library/FlowNode/NodeModelBaseFunc.cs b/Library/FlowNode/NodeModelBaseFunc.cs index ef01b3a..2b75bf3 100644 --- a/Library/FlowNode/NodeModelBaseFunc.cs +++ b/Library/FlowNode/NodeModelBaseFunc.cs @@ -164,6 +164,8 @@ namespace Serein.Library IsProtectionParameter = this.MethodDetails.IsProtectionParameter, IsInterrupt = this.DebugSetting.IsInterrupt, IsEnable = this.DebugSetting.IsEnable, + ParentNodeGuid = ParentNode?.Guid, + ChildNodeGuids = ChildrenNode.Select(item => item.Guid).ToArray(), }; nodeInfo.Position.X = Math.Round(nodeInfo.Position.X, 1); nodeInfo.Position.Y = Math.Round(nodeInfo.Position.Y, 1); diff --git a/Library/FlowNode/ParameterDetails.cs b/Library/FlowNode/ParameterDetails.cs index 33601cb..ac6f27d 100644 --- a/Library/FlowNode/ParameterDetails.cs +++ b/Library/FlowNode/ParameterDetails.cs @@ -180,7 +180,7 @@ namespace Serein.Library public override string ToString() { - return $"[{this.Index}] {this.Name} : {this.ExplicitType.FullName} -> {this.DataType.FullName}"; + return $"[{this.Index}] {this.Name} : {this.ExplicitType?.FullName} -> {this.DataType?.FullName}"; } } diff --git a/Library/FlowNode/SereinProjectData.cs b/Library/FlowNode/SereinProjectData.cs index ba3054c..4b7cd9b 100644 --- a/Library/FlowNode/SereinProjectData.cs +++ b/Library/FlowNode/SereinProjectData.cs @@ -230,8 +230,15 @@ namespace Serein.Library /// public ParameterData[] ParameterData { get; set; } + /// - /// 如果是区域控件,则会存在子项。 + /// 父级节点Guid + /// + public string ParentNodeGuid{ get; set; } + + + /// + /// 如果是区域控件,则会存在子项,这里记录的是子项的Guid。 /// public string[] ChildNodeGuids { get; set; } diff --git a/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs b/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs index 56ff7e2..ce8679f 100644 --- a/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs +++ b/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs @@ -173,10 +173,10 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle SereinEnv.WriteLine(InfoType.INFO, $"add websocket handle model :"); - SereinEnv.WriteLine(InfoType.ERROR, $"theme key, data key : {themeKey}, {dataKey}"); + SereinEnv.WriteLine(InfoType.INFO, $"theme key, data key : {themeKey}, {dataKey}"); foreach (var config in configs) { - SereinEnv.WriteLine(InfoType.ERROR, $"theme value : {config.ThemeValue} "); + SereinEnv.WriteLine(InfoType.INFO, $"theme value : {config.ThemeValue} "); var result = handleModule.AddHandleConfigs(config); } diff --git a/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs b/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs index a072463..af35c07 100644 --- a/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs +++ b/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs @@ -101,11 +101,11 @@ namespace Serein.Library.Utils.SereinExpression { result = InvokeMethod(targetObJ, operand); } - //else if (operation.Equals("@set",StringComparison.OrdinalIgnoreCase)) - //{ - // isChange = true; - // result = SetMember(targetObJ, operand); - //} + else if (operation.Equals("@set",StringComparison.OrdinalIgnoreCase)) + { + isChange = true; + result = SetMember(targetObJ, operand); + } else { throw new NotSupportedException($"Operation {operation} is not supported."); diff --git a/NodeFlow/Env/EnvMsgTheme.cs b/NodeFlow/Env/EnvMsgTheme.cs index c1f8afc..a243aa3 100644 --- a/NodeFlow/Env/EnvMsgTheme.cs +++ b/NodeFlow/Env/EnvMsgTheme.cs @@ -88,5 +88,10 @@ /// public const string SetMonitor = nameof(SetMonitor); + /// + /// 增加/减少方法可选类型的参数个数 + /// + public const string ChangeParameter = nameof(ChangeParameter); + } } diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index bcf8b38..c7eb290 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -9,6 +9,7 @@ using Serein.Library.Utils; using Serein.Library.Utils.SereinExpression; using Serein.NodeFlow.Model; using Serein.NodeFlow.Tool; +using Serein.Script.Node; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; @@ -150,6 +151,11 @@ namespace Serein.NodeFlow.Env /// public event NodeRemoveHandler? OnNodeRemove; + /// + /// 节点父子关系发生改变事件 + /// + public event NodeContainerChildChangeHandler OnNodeParentChildChange; + /// /// 起始节点变化事件 /// @@ -575,147 +581,7 @@ namespace Serein.NodeFlow.Env LoadLibrary(dllFilePath); // 加载项目文件时加载对应的程序集 } -#if false - List<(NodeModelBase, string[])> regionChildNodes = new List<(NodeModelBase, string[])>(); - List<(NodeModelBase, PositionOfUI)> ordinaryNodes = new List<(NodeModelBase, PositionOfUI)>(); - // 加载节点 - foreach (NodeInfo? nodeInfo in projectData.Nodes) - { - 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))); - - } - - - - // 确定节点之间的连接关系 - Task.Run(async () => - { - await Task.Delay(777); - #region 方法调用关系 - foreach (var nodeInfo in projectData.Nodes) - { - if (!NodeModels.TryGetValue(nodeInfo.Guid, out NodeModelBase? fromNode)) - { - // 不存在对应的起始节点 - continue; - } - List<(ConnectionInvokeType connectionType, string[] guids)> allToNodes = [(ConnectionInvokeType.IsSucceed,nodeInfo.TrueNodes), - (ConnectionInvokeType.IsFail, nodeInfo.FalseNodes), - (ConnectionInvokeType.IsError, nodeInfo.ErrorNodes), - (ConnectionInvokeType.Upstream, nodeInfo.UpstreamNodes)]; - - List<(ConnectionInvokeType, NodeModelBase[])> fromNodes = allToNodes.Where(info => info.guids.Length > 0) - .Select(info => (info.connectionType, - info.guids.Where(guid => NodeModels.ContainsKey(guid)).Select(guid => NodeModels[guid]) - .ToArray())) - .ToList(); - // 遍历每种类型的节点分支(四种) - foreach ((ConnectionInvokeType connectionType, NodeModelBase[] toNodes) item in fromNodes) - { - // 遍历当前类型分支的节点(确认连接关系) - foreach (var toNode in item.toNodes) - { - _ = ConnectInvokeOfNode(fromNode, toNode, item.connectionType); // 加载时确定节点间的连接关系 - } - } - } - #endregion - #region 参数调用关系 - foreach (var toNode in NodeModels.Values) - { - for (var i = 0; i < toNode.MethodDetails.ParameterDetailss.Length; i++) - { - var pd = toNode.MethodDetails.ParameterDetailss[i]; - if (!string.IsNullOrEmpty(pd.ArgDataSourceNodeGuid) - && NodeModels.TryGetValue(pd.ArgDataSourceNodeGuid, out var fromNode)) - { - - await ConnectArgSourceOfNodeAsync(fromNode, toNode, pd.ArgDataSourceType, pd.Index); - } - } - } - #endregion - }); - - UIContextOperation?.Invoke(() => OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs())); -#endif - LoadNodeInfosAsync(projectData.Nodes.ToList()); + _ = LoadNodeInfosAsync(projectData.Nodes.ToList()); SetStartNode(projectData.StartNode); } @@ -899,6 +765,7 @@ namespace Serein.NodeFlow.Env //} } + /// /// 从节点信息集合批量加载节点控件 /// @@ -906,18 +773,17 @@ namespace Serein.NodeFlow.Env /// public Task LoadNodeInfosAsync(List nodeInfos) { - List<(NodeModelBase, string[])> regionChildNodes = new List<(NodeModelBase, string[])>(); - List<(NodeModelBase, PositionOfUI)> ordinaryNodes = new List<(NodeModelBase, PositionOfUI)>(); - // 加载节点 + List needPlaceNodeInfos = []; + #region 从NodeInfo创建NodeModel foreach (NodeInfo? nodeInfo in nodeInfos) { - NodeControlType controlType = FlowFunc.GetNodeControlType(nodeInfo); - if (controlType == NodeControlType.None) + if (!EnumHelper.TryConvertEnum(nodeInfo.Type, out var controlType)) { continue; } - MethodDetails? methodDetails = null; + #region 获取方法描述 + MethodDetails? methodDetails; if (controlType.IsBaseNode()) { // 加载基础节点 @@ -925,77 +791,50 @@ namespace Serein.NodeFlow.Env } else { + if (string.IsNullOrEmpty(nodeInfo.MethodName)) continue; // 加载方法节点 - if (string.IsNullOrEmpty(nodeInfo.MethodName)) - { - continue; - } FlowLibraryManagement.TryGetMethodDetails(nodeInfo.AssemblyName, nodeInfo.MethodName, out methodDetails); // 加载项目时尝试获取方法信息 - } + } + #endregion var nodeModel = FlowFunc.CreateNode(this, controlType, methodDetails); // 加载项目时创建节点 - nodeModel.LoadInfo(nodeInfo); // 创建节点model if (nodeModel is null) { nodeInfo.Guid = string.Empty; continue; } - + nodeModel.LoadInfo(nodeInfo); // 创建节点model TryAddNode(nodeModel); // 加载项目时将节点加载到环境中 - if (nodeInfo.ChildNodeGuids?.Length > 0) + if (!string.IsNullOrEmpty(nodeInfo.ParentNodeGuid) && + NodeModels.TryGetValue(nodeInfo.ParentNodeGuid, out var parentNode)) { - regionChildNodes.Add((nodeModel, nodeInfo.ChildNodeGuids)); - - UIContextOperation?.Invoke(() => OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, nodeInfo.Position))); - } - else - { - ordinaryNodes.Add((nodeModel, nodeInfo.Position)); + needPlaceNodeInfos.Add(nodeInfo); // 需要重新放置的节点 } + UIContextOperation?.Invoke(() => + OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, nodeInfo.Position))); // 添加到UI上 } + #endregion - - - // 加载区域子项 - foreach ((NodeModelBase region, string[] childNodeGuids) item in regionChildNodes) + #region 重新放置节点 + foreach (NodeInfo nodeInfo in needPlaceNodeInfos) { - foreach (var childNodeGuid in item.childNodeGuids) + if (NodeModels.TryGetValue(nodeInfo.Guid, out var childNode) && + NodeModels.TryGetValue(nodeInfo.ParentNodeGuid, out var parentNode)) { - NodeModels.TryGetValue(childNodeGuid, out NodeModelBase? childNode); - if (childNode is null) - { - // 节点尚未加载 - continue; - } - UIContextOperation?.Invoke(() => OnNodeCreate?.Invoke(new NodeCreateEventArgs(childNode, true, item.region.Guid))); - // 存在节点 + childNode.ParentNode = parentNode; + parentNode.ChildrenNode.Add(childNode); + UIContextOperation?.Invoke(() => OnNodeParentChildChange?.Invoke( + new NodeContainerChildChangeEventArgs(childNode.Guid, parentNode.Guid, + NodeContainerChildChangeEventArgs.Type.Place))); } } - // 加载节点 - 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))); + #endregion - } - - // 确定节点之间的连接关系 - Task.Run(async () => + _ = Task.Run(async () => { - await Task.Delay(400); - #region 方法调用关系 + await Task.Delay(100); + #region 确定节点之间的方法调用关系 foreach (var nodeInfo in nodeInfos) { if (!NodeModels.TryGetValue(nodeInfo.Guid, out NodeModelBase? fromNode)) @@ -1024,10 +863,11 @@ namespace Serein.NodeFlow.Env } } #endregion - #region 参数调用关系 + + #region 确定节点之间的参数调用关系 foreach (var toNode in NodeModels.Values) { - if(toNode.MethodDetails.ParameterDetailss == null) + if (toNode.MethodDetails.ParameterDetailss == null) { continue; } @@ -1038,13 +878,12 @@ namespace Serein.NodeFlow.Env && NodeModels.TryGetValue(pd.ArgDataSourceNodeGuid, out var fromNode)) { - await ConnectArgSourceOfNodeAsync(fromNode, toNode, pd.ArgDataSourceType, pd.Index); + _ = ConnectArgSourceOfNodeAsync(fromNode, toNode, pd.ArgDataSourceType, pd.Index); } } } #endregion }); - UIContextOperation?.Invoke(() => OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs())); return Task.CompletedTask; @@ -1056,7 +895,9 @@ namespace Serein.NodeFlow.Env /// /// /// 如果是表达式节点条件节点,该项为null - public Task CreateNodeAsync(NodeControlType nodeControlType, PositionOfUI position, MethodDetailsInfo? methodDetailsInfo = null) + public Task CreateNodeAsync(NodeControlType nodeControlType, + PositionOfUI position, + MethodDetailsInfo? methodDetailsInfo = null) { NodeModelBase? nodeModel; @@ -1092,8 +933,40 @@ namespace Serein.NodeFlow.Env } var nodeInfo = nodeModel.ToInfo(); return Task.FromResult(nodeInfo); -; + } + /// + /// 将节点放置在容器中/从容器中取出 + /// + /// 子节点(主要节点) + /// 父节点 + /// 是否组合(反之为分解节点组合关系) + /// + public async Task ChangeNodeContainerChild(string childNodeGuid, string parentNodeGuid, bool isPlace) + { + // 获取起始节点与目标节点 + var childNode = GuidToModel(childNodeGuid); + var parentNode = GuidToModel(parentNodeGuid); + if (childNode is null || parentNode is null || parentNode is not INodeContainer nodeContainer) return false; + + if (isPlace) + { + // 放置节点 + parentNode.ChildrenNode.Add(childNode); + childNode.ParentNode = parentNode; + nodeContainer.PlaceNode(childNode); + } + else + { + // 取出节点 + parentNode.ChildrenNode.Remove(childNode); + childNode.ParentNode = null; + nodeContainer.TakeOutNode(childNode); + } + + OnNodeParentChildChange?.Invoke(new NodeContainerChildChangeEventArgs(childNodeGuid, parentNodeGuid, + isPlace ? NodeContainerChildChangeEventArgs.Type.Place : NodeContainerChildChangeEventArgs.Type.TakeOut)); + return true; } /// @@ -1597,42 +1470,10 @@ namespace Serein.NodeFlow.Env /// true,增加参数;false,减少参数 /// 以哪个参数为模板进行拷贝,或删去某个参数(该参数必须为可选参数) /// - public async Task ChangeParameter(string nodeGuid, bool isAdd, int paramIndex) + public Task ChangeParameter(string nodeGuid, bool isAdd, int paramIndex) { var nodeModel = GuidToModel(nodeGuid); - if (nodeModel is null) return false; - - var argInfo = nodeModel.MethodDetails.ParameterDetailss - .Where(pd => !string.IsNullOrEmpty(pd.ArgDataSourceNodeGuid)) - .Select(pd => (pd.ArgDataSourceNodeGuid, pd.ArgDataSourceType, pd.Index)) - .ToArray(); - - #region 暂时移除连接关系 - foreach((var fromGuid, var type, var index) in argInfo) - { - await UIContextOperation.InvokeAsync(() => - OnNodeConnectChange?.Invoke( - new NodeConnectChangeEventArgs( - fromGuid, // 从哪个节点开始 - nodeModel.Guid, // 连接到那个节点 - JunctionOfConnectionType.Arg, - index, // 参数 - type, // 连接线的样式类型 - NodeConnectChangeEventArgs.ConnectChangeType.Remote // 是创建连接还是删除连接 - ))); // 通知UI - } - - - for (int i = 0; i < argInfo.Length; i++) - { - ParameterDetails? pd = nodeModel.MethodDetails.ParameterDetailss[i]; - var fromNode = GuidToModel(pd.ArgDataSourceNodeGuid); - if (fromNode is null) continue; - var argSourceGuid = pd.ArgDataSourceNodeGuid; - var argSourceType = pd.ArgDataSourceType; - } - #endregion - + if (nodeModel is null) return Task.FromResult(false); bool isPass; if (isAdd) { @@ -1642,23 +1483,7 @@ namespace Serein.NodeFlow.Env { isPass = nodeModel.MethodDetails.RemoveParamsArg(paramIndex); } - - await Task.Delay(200); - foreach ((var fromGuid, var type, var index) in argInfo) - { - await UIContextOperation.InvokeAsync(() => - OnNodeConnectChange?.Invoke( - new NodeConnectChangeEventArgs( - fromGuid, // 从哪个节点开始 - nodeModel.Guid, // 连接到那个节点 - JunctionOfConnectionType.Arg, - index, // 参数 - type, // 连接线的样式类型 - NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 - ))); // 通知UI - - } - return isPass; + return Task.FromResult(isPass); } diff --git a/NodeFlow/Env/FlowEnvironmentDecorator.cs b/NodeFlow/Env/FlowEnvironmentDecorator.cs index 47f63b4..5bb9237 100644 --- a/NodeFlow/Env/FlowEnvironmentDecorator.cs +++ b/NodeFlow/Env/FlowEnvironmentDecorator.cs @@ -121,6 +121,15 @@ namespace Serein.NodeFlow.Env remove { currentFlowEnvironment.OnNodeRemove -= value; } } + /// + /// 节点父子关系发生改变事件 + /// + public event NodeContainerChildChangeHandler OnNodeParentChildChange + { + add { currentFlowEnvironment.OnNodeParentChildChange += value; } + remove { currentFlowEnvironment.OnNodeParentChildChange -= value; } + } + public event StartNodeChangeHandler OnStartNodeChange { add { currentFlowEnvironment.OnStartNodeChange += value; } @@ -278,6 +287,21 @@ namespace Serein.NodeFlow.Env return result; } + /// + /// 将节点放置在容器中/从容器中取出 + /// + /// 子节点(主要节点) + /// 父节点 + /// 是否组合(反之为分解节点组合关系) + /// + public async Task ChangeNodeContainerChild(string childNodeGuid, string parentNodeGuid, bool isAssembly) + { + SetProjectLoadingFlag(false); + var result = await currentFlowEnvironment.ChangeNodeContainerChild(childNodeGuid, parentNodeGuid, isAssembly); // 装饰器调用 + SetProjectLoadingFlag(true); + return result; + + } diff --git a/NodeFlow/Env/MsgControllerOfClient.cs b/NodeFlow/Env/MsgControllerOfClient.cs index 1831e77..f236bcf 100644 --- a/NodeFlow/Env/MsgControllerOfClient.cs +++ b/NodeFlow/Env/MsgControllerOfClient.cs @@ -145,6 +145,12 @@ namespace Serein.NodeFlow.Env { _ = remoteFlowEnvironment.InvokeTriggerAsync(msgId, state); } + + [AutoSocketHandle(ThemeValue = EnvMsgTheme.ChangeParameter, IsReturnValue = false)] + public void ChangeParameter([UseMsgId] string msgId, bool state) + { + _ = remoteFlowEnvironment.InvokeTriggerAsync(msgId, state); + } diff --git a/NodeFlow/Env/MsgControllerOfServer.cs b/NodeFlow/Env/MsgControllerOfServer.cs index 76191d8..3e20fb4 100644 --- a/NodeFlow/Env/MsgControllerOfServer.cs +++ b/NodeFlow/Env/MsgControllerOfServer.cs @@ -578,6 +578,22 @@ namespace Serein.NodeFlow.Env await environment.NotificationNodeValueChangeAsync(nodeGuid, path, value); } + /// + /// 增加/减少节点入参可选类型参数的个数 + /// + /// + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.ChangeParameter)] + public async Task ChangeParameter(string nodeGuid, bool isAdd, int paramIndex) + { + var result = await environment.ChangeParameter(nodeGuid, isAdd, paramIndex); + return new + { + state = result + }; + } + } diff --git a/NodeFlow/Env/RemoteFlowEnvironment.cs b/NodeFlow/Env/RemoteFlowEnvironment.cs index 1db75cd..ef8a751 100644 --- a/NodeFlow/Env/RemoteFlowEnvironment.cs +++ b/NodeFlow/Env/RemoteFlowEnvironment.cs @@ -1,9 +1,12 @@ -using Serein.Library; +using Newtonsoft.Json.Linq; +using Serein.Library; using Serein.Library.Api; using Serein.Library.FlowNode; using Serein.Library.Utils; using Serein.NodeFlow.Tool; +using Serein.Script.Node; using System.Collections.Concurrent; +using System.IO; using System.Security.AccessControl; using System.Threading.Channels; @@ -52,6 +55,10 @@ namespace Serein.NodeFlow.Env public event NodeConnectChangeHandler OnNodeConnectChange; public event NodeCreateHandler OnNodeCreate; public event NodeRemoveHandler OnNodeRemove; + /// + /// 节点父子关系发生改变事件 + /// + public event NodeContainerChildChangeHandler OnNodeParentChildChange; public event StartNodeChangeHandler OnStartNodeChange; public event FlowRunCompleteHandler OnFlowRunComplete; public event MonitorObjectChangeHandler OnMonitorObjectChange; @@ -169,165 +176,179 @@ namespace Serein.NodeFlow.Env } #endregion - #region 加载节点数据,如果是区域控件,提前加载区域 - var projectData = flowEnvInfo.Project; - List<(NodeModelBase, string[])> regionChildNodes = new List<(NodeModelBase, string[])>(); - List<(NodeModelBase, PositionOfUI)> ordinaryNodes = new List<(NodeModelBase, PositionOfUI)>(); - // 加载节点 - foreach (var nodeInfo in projectData.Nodes) - { - var controlType = FlowFunc.GetNodeControlType(nodeInfo); - if (controlType == NodeControlType.None) - { - continue; - } - else - { - MethodDetails? methodDetails = null; - if (!string.IsNullOrEmpty(nodeInfo.MethodName)) - { - MethodDetailss.TryGetValue(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)); - } - } - } - #endregion - - #region 加载区域中的节点 - // 加载区域子项 - 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))); - } - } - #endregion - - #region 加载普通的节点 - // 加载节点 - 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; - //OnNodeCreate?.Invoke(new NodeCreateEventArgs(item.nodeModel, item.position)); - UIContextOperation?.Invoke(() => OnNodeCreate?.Invoke(new NodeCreateEventArgs(item.nodeModel, item.position))); - } - #endregion - - #region 确定节点之间的连接关系 - _ = Task.Run(async () => - { - await Task.Delay(500); - #region 连接节点的调用关系 - foreach (var nodeInfo in projectData.Nodes) - { - if (!NodeModels.TryGetValue(nodeInfo.Guid, out NodeModelBase? fromNode)) - { - // 不存在对应的起始节点 - continue; - } - - - List<(ConnectionInvokeType connectionType, string[] guids)> allToNodes = [(ConnectionInvokeType.IsSucceed,nodeInfo.TrueNodes), - (ConnectionInvokeType.IsFail, nodeInfo.FalseNodes), - (ConnectionInvokeType.IsError, nodeInfo.ErrorNodes), - (ConnectionInvokeType.Upstream, nodeInfo.UpstreamNodes)]; - - List<(ConnectionInvokeType, NodeModelBase[])> fromNodes = allToNodes.Where(info => info.guids.Length > 0) - .Select(info => (info.connectionType, - info.guids.Where(guid => NodeModels.ContainsKey(guid)).Select(guid => NodeModels[guid]) - .ToArray())) - .ToList(); - // 遍历每种类型的节点分支(四种) - foreach ((ConnectionInvokeType connectionType, NodeModelBase[] toNodes) item in fromNodes) - { - // 遍历当前类型分支的节点(确认连接关系) - foreach (var toNode in item.toNodes) - { - UIContextOperation?.Invoke(() => OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, - toNode.Guid, - JunctionOfConnectionType.Invoke, - item.connectionType, - NodeConnectChangeEventArgs.ConnectChangeType.Create))); // 通知UI连接节点 - } - } - } - #endregion - - #region 连接节点的传参关系 - foreach (var toNode in NodeModels.Values) - { - if(toNode.MethodDetails.ParameterDetailss is null) - { - continue; - } - for (var i = 0; i < toNode.MethodDetails.ParameterDetailss.Length; i++) - { - var pd = toNode.MethodDetails.ParameterDetailss[i]; - if (!string.IsNullOrEmpty(pd.ArgDataSourceNodeGuid) - && NodeModels.TryGetValue(pd.ArgDataSourceNodeGuid, out var fromNode)) - { - UIContextOperation?.Invoke(() => - OnNodeConnectChange?.Invoke( - new NodeConnectChangeEventArgs( - fromNode.Guid, // 从哪个节点开始 - toNode.Guid, // 连接到那个节点 - JunctionOfConnectionType.Arg, - (int)pd.Index, // 连接线的样式类型 - pd.ArgDataSourceType, - NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 - ))); // 通知UI - } - } - } - #endregion - }); - #endregion - - SetStartNode(projectData.StartNode); // 设置流程起点 + + _ = LoadNodeInfosAsync(flowEnvInfo.Project.Nodes.ToList()); + SetStartNode(flowEnvInfo.Project.StartNode); // 设置流程起点 UIContextOperation?.Invoke(() => { OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs()); // 加载完成 }); IsLoadingProject = false; + + #region 暂时注释 + /* #region 加载节点数据,如果是区域控件,提前加载区域 + var projectData = flowEnvInfo.Project; + List<(NodeModelBase, string[])> regionChildNodes = new List<(NodeModelBase, string[])>(); + List<(NodeModelBase, PositionOfUI)> ordinaryNodes = new List<(NodeModelBase, PositionOfUI)>(); + // 加载节点 + foreach (var nodeInfo in projectData.Nodes) + { + var controlType = FlowFunc.GetNodeControlType(nodeInfo); + if (controlType == NodeControlType.None) + { + continue; + } + else + { + MethodDetails? methodDetails = null; + if (!string.IsNullOrEmpty(nodeInfo.MethodName)) + { + MethodDetailss.TryGetValue(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)); + } + } + } + #endregion + + #region 加载区域中的节点 + // 加载区域子项 + //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))); + // } + //} + #endregion + + #region 加载普通的节点 + // 加载节点 + 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; + //OnNodeCreate?.Invoke(new NodeCreateEventArgs(item.nodeModel, item.position)); + UIContextOperation?.Invoke(() => OnNodeCreate?.Invoke(new NodeCreateEventArgs(item.nodeModel, item.position))); + } + #endregion + + #region 确定节点之间的连接关系 + _ = Task.Run(async () => + { + await Task.Delay(500); + #region 连接节点的调用关系 + foreach (var nodeInfo in projectData.Nodes) + { + if (!NodeModels.TryGetValue(nodeInfo.Guid, out NodeModelBase? fromNode)) + { + // 不存在对应的起始节点 + continue; + } + + + List<(ConnectionInvokeType connectionType, string[] guids)> allToNodes = [(ConnectionInvokeType.IsSucceed,nodeInfo.TrueNodes), + (ConnectionInvokeType.IsFail, nodeInfo.FalseNodes), + (ConnectionInvokeType.IsError, nodeInfo.ErrorNodes), + (ConnectionInvokeType.Upstream, nodeInfo.UpstreamNodes)]; + + List<(ConnectionInvokeType, NodeModelBase[])> fromNodes = allToNodes.Where(info => info.guids.Length > 0) + .Select(info => (info.connectionType, + info.guids.Where(guid => NodeModels.ContainsKey(guid)).Select(guid => NodeModels[guid]) + .ToArray())) + .ToList(); + // 遍历每种类型的节点分支(四种) + foreach ((ConnectionInvokeType connectionType, NodeModelBase[] toNodes) item in fromNodes) + { + // 遍历当前类型分支的节点(确认连接关系) + foreach (var toNode in item.toNodes) + { + UIContextOperation?.Invoke(() => OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, + toNode.Guid, + JunctionOfConnectionType.Invoke, + item.connectionType, + NodeConnectChangeEventArgs.ConnectChangeType.Create))); // 通知UI连接节点 + } + } + } + #endregion + + #region 连接节点的传参关系 + foreach (var toNode in NodeModels.Values) + { + if(toNode.MethodDetails.ParameterDetailss is null) + { + continue; + } + for (var i = 0; i < toNode.MethodDetails.ParameterDetailss.Length; i++) + { + var pd = toNode.MethodDetails.ParameterDetailss[i]; + if (!string.IsNullOrEmpty(pd.ArgDataSourceNodeGuid) + && NodeModels.TryGetValue(pd.ArgDataSourceNodeGuid, out var fromNode)) + { + UIContextOperation?.Invoke(() => + OnNodeConnectChange?.Invoke( + new NodeConnectChangeEventArgs( + fromNode.Guid, // 从哪个节点开始 + toNode.Guid, // 连接到那个节点 + JunctionOfConnectionType.Arg, + (int)pd.Index, // 连接线的样式类型 + pd.ArgDataSourceType, + NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 + ))); // 通知UI + } + } + } + #endregion + }); + #endregion*/ + #endregion + } + + + + + + + + + private bool TryAddNode(NodeModelBase nodeModel) { //nodeModel.Guid ??= Guid.NewGuid().ToString(); @@ -345,69 +366,7 @@ namespace Serein.NodeFlow.Env return true; } - private void ConnectNode(NodeModelBase fromNode, NodeModelBase toNode, ConnectionInvokeType connectionType) - { - if (fromNode is null || toNode is null || fromNode == toNode) - { - return; - } - - var ToExistOnFrom = true; - var FromExistInTo = true; - ConnectionInvokeType[] ct = [ConnectionInvokeType.IsSucceed, - ConnectionInvokeType.IsFail, - ConnectionInvokeType.IsError, - ConnectionInvokeType.Upstream]; - - - foreach (ConnectionInvokeType ctType in ct) - { - var FToTo = fromNode.SuccessorNodes[ctType].Where(it => it.Guid.Equals(toNode.Guid)).ToArray(); - var ToOnF = toNode.PreviousNodes[ctType].Where(it => it.Guid.Equals(fromNode.Guid)).ToArray(); - ToExistOnFrom = FToTo.Length > 0; - FromExistInTo = ToOnF.Length > 0; - if (ToExistOnFrom && FromExistInTo) - { - this.WriteLine(InfoType.ERROR, "起始节点已与目标节点存在连接"); - - //return; - } - else - { - // 检查是否可能存在异常 - if (!ToExistOnFrom && FromExistInTo) - { - this.WriteLine(InfoType.ERROR, "目标节点不是起始节点的子节点,起始节点却是目标节点的父节点"); - return; - } - else if (ToExistOnFrom && !FromExistInTo) - { - // - this.WriteLine(InfoType.ERROR, " 起始节点不是目标节点的父节点,目标节点却是起始节点的子节点"); - return; - } - else // if (!ToExistOnFrom && !FromExistInTo) - { - // 可以正常连接 - } - } - - - fromNode.SuccessorNodes[connectionType].Add(toNode); // 添加到起始节点的子分支 - toNode.PreviousNodes[connectionType].Add(fromNode); // 添加到目标节点的父分支 - OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, - toNode.Guid, - JunctionOfConnectionType.Invoke, - connectionType, - NodeConnectChangeEventArgs.ConnectChangeType.Create)); // 通知UI - } - - - - } - - - + public async Task GetEnvInfoAsync() { var envInfo = await msgClient.SendAndWaitDataAsync(EnvMsgTheme.GetEnvInfo); @@ -491,16 +450,16 @@ namespace Serein.NodeFlow.Env { nodeGuid }); - //UIContextOperation?.Invoke(() => OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(nodeGuid,nodeGuid))); + UIContextOperation?.Invoke(() => OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(nodeGuid,nodeGuid))); } public async Task InvokeNodeAsync(IDynamicContext context, string nodeGuid) { this.WriteLine(InfoType.INFO, "远程环境尚未实现接口 InvokeNodeAsync"); - _ = msgClient.SendAsync(EnvMsgTheme.SetStartNode, new - { - nodeGuid - }); + //_ = msgClient.SendAsync(EnvMsgTheme.InvokeNodeAsync, new + //{ + // nodeGuid + //}); return null; } @@ -716,7 +675,133 @@ namespace Serein.NodeFlow.Env /// public async Task LoadNodeInfosAsync(List nodeInfos) { - this.WriteLine(InfoType.WARN, "远程环境尚未实现的接口(重要,会尽快实现):LoadNodeInfoAsync"); + List needPlaceNodeInfos = []; + + #region 从NodeInfo创建NodeModel + foreach (NodeInfo? nodeInfo in nodeInfos) + { + if (!EnumHelper.TryConvertEnum(nodeInfo.Type, out var controlType)) + { + continue; + } + + #region 获取方法描述 + MethodDetails? methodDetails = null; + if (string.IsNullOrEmpty(nodeInfo.MethodName)) + { + methodDetails = new MethodDetails(); + } + else + { + if (string.IsNullOrEmpty(nodeInfo.MethodName)) + { + continue; + } + MethodDetailss.TryGetValue(nodeInfo.MethodName, out methodDetails);// 加载远程环境时尝试获取方法信息 + } + #endregion + + var nodeModel = FlowFunc.CreateNode(this, controlType, methodDetails); // 加载项目时创建节点 + if (nodeModel is null) + { + nodeInfo.Guid = string.Empty; + continue; + } + nodeModel.LoadInfo(nodeInfo); // 创建节点model + TryAddNode(nodeModel); // 加载项目时将节点加载到环境中 + if (!string.IsNullOrEmpty(nodeInfo.ParentNodeGuid) && + NodeModels.TryGetValue(nodeInfo.ParentNodeGuid, out var parentNode)) + { + needPlaceNodeInfos.Add(nodeInfo); // 需要重新放置的节点 + } + UIContextOperation?.Invoke(() => + OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, nodeInfo.Position))); // 添加到UI上 + } + #endregion + + #region 重新放置节点 + foreach (NodeInfo nodeInfo in needPlaceNodeInfos) + { + if (NodeModels.TryGetValue(nodeInfo.Guid, out var childNode) && + NodeModels.TryGetValue(nodeInfo.ParentNodeGuid, out var parentNode)) + { + childNode.ParentNode = parentNode; + parentNode.ChildrenNode.Add(childNode); + UIContextOperation?.Invoke(() => OnNodeParentChildChange?.Invoke( + new NodeContainerChildChangeEventArgs(childNode.Guid, parentNode.Guid, + NodeContainerChildChangeEventArgs.Type.Place))); + + } + } + #endregion + + _ = Task.Run(async () => + { + await Task.Delay(100); + #region 确定节点之间的方法调用关系 + foreach (var nodeInfo in nodeInfos) + { + if (!NodeModels.TryGetValue(nodeInfo.Guid, out NodeModelBase? fromNode)) + { + // 不存在对应的起始节点 + continue; + } + List<(ConnectionInvokeType connectionType, string[] guids)> allToNodes = [(ConnectionInvokeType.IsSucceed,nodeInfo.TrueNodes), + (ConnectionInvokeType.IsFail, nodeInfo.FalseNodes), + (ConnectionInvokeType.IsError, nodeInfo.ErrorNodes), + (ConnectionInvokeType.Upstream, nodeInfo.UpstreamNodes)]; + + List<(ConnectionInvokeType, NodeModelBase[])> fromNodes = allToNodes.Where(info => info.guids.Length > 0) + .Select(info => (info.connectionType, + info.guids.Where(guid => NodeModels.ContainsKey(guid)).Select(guid => NodeModels[guid]) + .ToArray())) + .ToList(); + // 遍历每种类型的节点分支(四种) + foreach ((ConnectionInvokeType connectionType, NodeModelBase[] toNodes) item in fromNodes) + { + // 遍历当前类型分支的节点(确认连接关系) + foreach (var toNode in item.toNodes) + { + UIContextOperation?.Invoke(() => OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, + toNode.Guid, + JunctionOfConnectionType.Invoke, + item.connectionType, + NodeConnectChangeEventArgs.ConnectChangeType.Create))); // 通知UI连接节点 + } + } + } + #endregion + + #region 确定节点之间的参数调用关系 + foreach (var toNode in NodeModels.Values) + { + if (toNode.MethodDetails.ParameterDetailss == null) + { + continue; + } + for (var i = 0; i < toNode.MethodDetails.ParameterDetailss.Length; i++) + { + var pd = toNode.MethodDetails.ParameterDetailss[i]; + if (!string.IsNullOrEmpty(pd.ArgDataSourceNodeGuid) + && NodeModels.TryGetValue(pd.ArgDataSourceNodeGuid, out var fromNode)) + { + + UIContextOperation?.Invoke(() => + OnNodeConnectChange?.Invoke( + new NodeConnectChangeEventArgs( + fromNode.Guid, // 从哪个节点开始 + toNode.Guid, // 连接到那个节点 + JunctionOfConnectionType.Arg, + (int)pd.Index, // 连接线的样式类型 + pd.ArgDataSourceType, + NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 + ))); // 通知UI + } + } + } + #endregion + }); + UIContextOperation?.Invoke(() => OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs())); } @@ -756,6 +841,21 @@ namespace Serein.NodeFlow.Env }); return nodeInfo; } + + /// + /// 将节点放置在容器中/从容器中取出 + /// + /// 子节点(主要节点) + /// 父节点 + /// 是否组合(反之为分解节点组合关系) + /// + public async Task ChangeNodeContainerChild(string childNodeGuid, string parentNodeGuid, bool isAssembly) + { + this.WriteLine(InfoType.WARN, "远程环境尚未实现的接口(重要,会尽快实现):ChangeNodeParentChild"); + return false; + } + + public async Task RemoveNodeAsync(string nodeGuid) { var result = await msgClient.SendAndWaitDataAsync(EnvMsgTheme.RemoveNode, new @@ -875,7 +975,6 @@ namespace Serein.NodeFlow.Env public void NodeLocated(string nodeGuid) { - //Console.WriteLine("远程环境尚未实现的接口:NodeLocated"); UIContextOperation?.Invoke(() => OnNodeLocated?.Invoke(new NodeLocatedEventArgs(nodeGuid))); } @@ -886,8 +985,7 @@ namespace Serein.NodeFlow.Env { return; } - this.WriteLine(InfoType.INFO, $"通知远程环境修改节点数据:{nodeGuid},name:{path},value:{value}"); - + //this.WriteLine(InfoType.INFO, $"通知远程环境修改节点数据:{nodeGuid},name:{path},value:{value}"); _ = msgClient.SendAsync(EnvMsgTheme.ValueNotification, new { nodeGuid = nodeGuid, @@ -906,8 +1004,32 @@ namespace Serein.NodeFlow.Env /// public async Task ChangeParameter(string nodeGuid, bool isAdd, int paramIndex) { - this.WriteLine(InfoType.INFO, "远程环境尚未实现的接口:ChangeParameter"); - return false; + if (IsLoadingProject || IsLoadingNode) + { + return false; + } + if (!NodeModels.TryGetValue(nodeGuid,out var nodeModel)) + { + return false; + } + //this.WriteLine(InfoType.INFO, $"通知远程环境修改节点可选数据:{nodeGuid},isAdd:{isAdd},paramIndex:{paramIndex}"); + var result = await msgClient.SendAndWaitDataAsync(EnvMsgTheme.ChangeParameter, new + { + nodeGuid = nodeGuid, + isAdd = isAdd, + paramIndex = paramIndex, + }); + if (result) { + if (isAdd) + { + nodeModel.MethodDetails.AddParamsArg(paramIndex); + } + else + { + nodeModel.MethodDetails.RemoveParamsArg(paramIndex); + } + } + return result; } #region 流程依赖类库的接口 diff --git a/NodeFlow/FlowFunc.cs b/NodeFlow/FlowFunc.cs index a17be7c..5c39c7f 100644 --- a/NodeFlow/FlowFunc.cs +++ b/NodeFlow/FlowFunc.cs @@ -71,36 +71,36 @@ namespace Serein.NodeFlow - /// - /// 从节点信息读取节点类型 - /// - /// - /// - /// - public static NodeControlType GetNodeControlType(NodeInfo nodeInfo) - { - if(!EnumHelper.TryConvertEnum(nodeInfo.Type, out var controlType)) - { - return NodeControlType.None; - } - return controlType; - // 创建控件实例 - //NodeControlType controlType = nodeInfo.Type switch - //{ - // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" => NodeControlType.Action,// 动作节点控件 - // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" => NodeControlType.Flipflop, // 触发器节点控件 + ///// + ///// 从节点信息读取节点类型 + ///// + ///// + ///// + ///// + //public static NodeControlType GetNodeControlType(NodeInfo nodeInfo) + //{ + // if(!EnumHelper.TryConvertEnum(nodeInfo.Type, out var controlType)) + // { + // return NodeControlType.None; + // } + // return controlType; + // // 创建控件实例 + // //NodeControlType controlType = nodeInfo.Type switch + // //{ + // // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" => NodeControlType.Action,// 动作节点控件 + // // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" => NodeControlType.Flipflop, // 触发器节点控件 - // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" => NodeControlType.ExpCondition,// 条件表达式控件 - // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => NodeControlType.ExpOp, // 操作表达式控件 + // // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" => NodeControlType.ExpCondition,// 条件表达式控件 + // // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => NodeControlType.ExpOp, // 操作表达式控件 - // $"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => NodeControlType.ConditionRegion, // 条件区域控件 + // // $"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => NodeControlType.ConditionRegion, // 条件区域控件 - // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleGlobalDataNode)}" => NodeControlType.GlobalData, // 数据节点 - // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleScriptNode)}" => NodeControlType.Script, // 数据节点 - // _ => NodeControlType.None, - //}; - //return controlType; - } + // // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleGlobalDataNode)}" => NodeControlType.GlobalData, // 数据节点 + // // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleScriptNode)}" => NodeControlType.Script, // 数据节点 + // // _ => NodeControlType.None, + // //}; + // //return controlType; + //} /// /// 程序集封装依赖 diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs index 590ccad..d5836e7 100644 --- a/NodeFlow/Model/SingleConditionNode.cs +++ b/NodeFlow/Model/SingleConditionNode.cs @@ -37,6 +37,11 @@ namespace Serein.NodeFlow.Model public partial class SingleConditionNode : NodeModelBase { + /// + /// 条件表达式节点是基础节点 + /// + public override bool IsBase => true; + /// /// 表达式参数索引 /// diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs index 34ac24f..2098862 100644 --- a/NodeFlow/Model/SingleExpOpNode.cs +++ b/NodeFlow/Model/SingleExpOpNode.cs @@ -26,6 +26,11 @@ namespace Serein.NodeFlow.Model public partial class SingleExpOpNode : NodeModelBase { + /// + /// 表达式节点是基础节点 + /// + public override bool IsBase => true; + /// /// 表达式参数索引 /// diff --git a/NodeFlow/Model/SingleGlobalDataNode.cs b/NodeFlow/Model/SingleGlobalDataNode.cs index 8641214..5b47ba3 100644 --- a/NodeFlow/Model/SingleGlobalDataNode.cs +++ b/NodeFlow/Model/SingleGlobalDataNode.cs @@ -31,8 +31,17 @@ namespace Serein.NodeFlow.Model /// /// 全局数据节点 /// - public partial class SingleGlobalDataNode : NodeModelBase + public partial class SingleGlobalDataNode : NodeModelBase, INodeContainer { + /// + /// 全局数据节点是基础节点 + /// + public override bool IsBase => true; + /// + /// 数据源只允许放置1个节点。 + /// + public override int MaxChildrenCount => 1; + public SingleGlobalDataNode(IFlowEnvironment environment) : base(environment) { } @@ -40,16 +49,33 @@ namespace Serein.NodeFlow.Model /// /// 数据来源的节点 /// - private string? DataNodeGuid; + private NodeModelBase? DataNode; + + + public void PlaceNode(NodeModelBase nodeModel) + { + _ = this.Env.RemoveNodeAsync(DataNode?.Guid); + DataNode = nodeModel; + } + + public void TakeOutAll() + { + DataNode = null; + } + + public void TakeOutNode(NodeModelBase nodeModel) + { + DataNode = null; + } /// /// 设置数据节点 /// /// - public void SetDataNode(NodeModelBase dataNode) - { - DataNodeGuid = dataNode.Guid; - } + //public void SetDataNode(NodeModelBase dataNode) + //{ + // DataNodeGuid = dataNode.Guid; + //} private void ChangeName(string newName) { @@ -73,7 +99,7 @@ namespace Serein.NodeFlow.Model SereinEnv.WriteLine(InfoType.ERROR, $"全局数据的KeyName不能为空[{this.Guid}]"); return null; } - if (DataNodeGuid == null) + if (DataNode is null) { context.NextOrientation = ConnectionInvokeType.IsError; SereinEnv.WriteLine(InfoType.ERROR, $"全局数据节点没有设置数据来源[{this.Guid}]"); @@ -82,7 +108,7 @@ namespace Serein.NodeFlow.Model try { - var result = await context.Env.InvokeNodeAsync(context, DataNodeGuid); + var result = await context.Env.InvokeNodeAsync(context, DataNode.Guid); SereinEnv.AddOrUpdateFlowGlobalData(KeyName, result); return result; } @@ -102,18 +128,9 @@ namespace Serein.NodeFlow.Model 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]; + nodeInfo.CustomData = data; return nodeInfo; } @@ -124,7 +141,6 @@ namespace Serein.NodeFlow.Model public override void LoadCustomData(NodeInfo nodeInfo) { KeyName = nodeInfo.CustomData?.KeyName; - DataNodeGuid = nodeInfo.CustomData?.DataNodeGuid; } /// @@ -133,7 +149,7 @@ namespace Serein.NodeFlow.Model public override void Remove() { // 移除数据节点 - _ = this.Env.RemoveNodeAsync(DataNodeGuid); + _ = this.Env.RemoveNodeAsync(DataNode?.Guid); } } diff --git a/NodeFlow/Model/SingleScriptNode.cs b/NodeFlow/Model/SingleScriptNode.cs index 1da8d5c..1a03c54 100644 --- a/NodeFlow/Model/SingleScriptNode.cs +++ b/NodeFlow/Model/SingleScriptNode.cs @@ -28,6 +28,13 @@ namespace Serein.NodeFlow.Model /// public partial class SingleScriptNode : NodeModelBase { + + /// + /// 脚本节点是基础节点 + /// + public override bool IsBase => true; + + private IScriptFlowApi ScriptFlowApi { get; } private ASTNode mainNode; diff --git a/Serein.Script/Serein.Script.csproj b/Serein.Script/Serein.Script.csproj index a2fa3f7..75b6308 100644 --- a/Serein.Script/Serein.Script.csproj +++ b/Serein.Script/Serein.Script.csproj @@ -7,8 +7,11 @@ + + + diff --git a/Serein.Script/TestExpression/Class1.cs b/Serein.Script/TestExpression/Class1.cs new file mode 100644 index 0000000..eed550d --- /dev/null +++ b/Serein.Script/TestExpression/Class1.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Script.TestExpression +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Text.RegularExpressions; + + public class ScriptParser + { + public static Expression> ParseWhereExpression(string lambdaText) + { + // 解析 lambda 表达式中的 item => item.StartsWith("张") + var match = Regex.Match(lambdaText, @"(?\w+)\s*=>\s*(?.*)"); + if (!match.Success) throw new Exception("Invalid lambda expression"); + + var paramName = match.Groups["param"].Value; + var expressionText = match.Groups["expression"].Value; + + // 创建 Lambda 参数表达式 + var param = Expression.Parameter(typeof(T), paramName); + + // 构建 StartsWith("张") 的表达式 + var startsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) }); + var constantValue = Expression.Constant("张"); + var methodCallExpression = Expression.Call( + Expression.Property(param, "StartsWith"), + startsWithMethod, + constantValue); + + return Expression.Lambda>(methodCallExpression, param); + } + + public static void Main() + { + // 假设你有一个List作为数据源 + var list = new List { "张三", "李四", "张五" }; + + // 模拟从文本中解析出来的脚本 + string script = "let list = GetList(); let newList = list.Where(item => item.StartsWith(\"张\")).ToList();"; + + // 解析Where表达式 + var whereExpression = ParseWhereExpression("item => item.StartsWith(\"张\")"); + + // 使用表达式执行LINQ查询 + var filteredList = list.AsQueryable().Where(whereExpression).ToList(); + + foreach (var item in filteredList) + { + Console.WriteLine(item); // 输出: 张三, 张五 + } + } + } + + internal class Class1 + { + public Class1() { + List list = new List(); + + var newList = list.Where(item => item.StartsWith("张")).ToList(); + } + } +} diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs index 7505e2f..465c9cf 100644 --- a/WorkBench/App.xaml.cs +++ b/WorkBench/App.xaml.cs @@ -6,17 +6,25 @@ using Serein.Library.Utils; using Serein.Library.Utils.SereinExpression; using Serein.NodeFlow.Model; using Serein.Script; +using SqlSugar; using System.Diagnostics; using System.IO; using System.Linq.Expressions; +using System.Reflection; using System.Windows; using System.Windows.Media.Animation; using System.Windows.Threading; +using Expression = System.Linq.Expressions.Expression; namespace Serein.Workbench { #if DEBUG - + public class People + { + public string Name { get; set; } + public int Id { get; set; } + public int Age { get; set; } + } #endif @@ -28,11 +36,9 @@ namespace Serein.Workbench { void LoadLocalProject() { - #if DEBUG - if (1 == 1) + if (1 == 11) { - // 这里是我自己的测试代码,你可以删除 string filePath; filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\Release\net8.0\PLCproject.dnf"; @@ -42,7 +48,6 @@ namespace Serein.Workbench App.FlowProjectData = JsonConvert.DeserializeObject(content); App.FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;// var dir = Path.GetDirectoryName(filePath); - //System.IO.Directory.SetCurrentDirectory(dir); } #endif } @@ -50,26 +55,8 @@ namespace Serein.Workbench public static SereinProjectData? FlowProjectData { get; set; } public static string FileDataPath { get; set; } = ""; - public App() - { - - } - - protected override void OnExit(ExitEventArgs e) - { - base.OnExit(e); - - // 强制关闭所有窗口 - foreach (Window window in Windows) - { - window.Close(); - } - } private void Application_Startup(object sender, StartupEventArgs e) { - Application.Current.Dispatcher.Invoke(() => { }); - - // 检查是否传入了参数 if (e.Args.Length == 1) { diff --git a/WorkBench/LogWindow.xaml.cs b/WorkBench/LogWindow.xaml.cs index 30e730a..495a979 100644 --- a/WorkBench/LogWindow.xaml.cs +++ b/WorkBench/LogWindow.xaml.cs @@ -18,11 +18,11 @@ namespace Serein.Workbench public partial class LogWindow : Window { private StringBuilder logBuffer = new StringBuilder(); - private int logUpdateInterval = 500; // 批量更新的时间间隔(毫秒) + private int logUpdateInterval = 200; // 批量更新的时间间隔(毫秒) private Timer logUpdateTimer; private const int MaxLines = 1000; // 最大显示的行数 private bool autoScroll = true; // 自动滚动标识 - private int flushThreshold = 1000; // 设置日志刷新阈值 + private int flushThreshold = 5; // 设置日志刷新阈值 private const int maxFlushSize = 1000; // 每次最大刷新字符数 public LogWindow() @@ -49,12 +49,12 @@ namespace Serein.Workbench // 异步写入日志到文件 // Task.Run(() => File.AppendAllText("log.txt", text)); - FlushLog(); + //FlushLog(); // 如果日志达到阈值,立即刷新 - //if (logBuffer.Length > flushThreshold) - //{ - // FlushLog(); - //} + if (logBuffer.Length > flushThreshold) + { + FlushLog(); + } } } diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index e7424ab..24cd794 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -209,6 +209,7 @@ namespace Serein.Workbench EnvDecorator.OnNodeConnectChange += FlowEnvironment_NodeConnectChangeEvemt; EnvDecorator.OnNodeCreate += FlowEnvironment_NodeCreateEvent; EnvDecorator.OnNodeRemove += FlowEnvironment_NodeRemoteEvent; + EnvDecorator.OnNodeParentChildChange += EnvDecorator_OnNodeParentChildChange; EnvDecorator.OnFlowRunComplete += FlowEnvironment_OnFlowRunComplete; @@ -223,6 +224,8 @@ namespace Serein.Workbench EnvDecorator.OnEnvOut += FlowEnvironment_OnEnvOut; } + + /// /// 移除环境事件 /// @@ -235,6 +238,7 @@ namespace Serein.Workbench EnvDecorator.OnNodeConnectChange -= FlowEnvironment_NodeConnectChangeEvemt; EnvDecorator.OnNodeCreate -= FlowEnvironment_NodeCreateEvent; EnvDecorator.OnNodeRemove -= FlowEnvironment_NodeRemoteEvent; + EnvDecorator.OnNodeParentChildChange -= EnvDecorator_OnNodeParentChildChange; EnvDecorator.OnFlowRunComplete -= FlowEnvironment_OnFlowRunComplete; @@ -313,7 +317,7 @@ namespace Serein.Workbench /// private void FlowEnvironment_OnEnvOut(InfoType type, string value) { - LogOutWindow.AppendText($"{DateTime.UtcNow} [{type}] : {value}{Environment.NewLine}"); + LogOutWindow.AppendText($"{DateTime.Now} [{type}] : {value}{Environment.NewLine}"); } @@ -588,9 +592,14 @@ namespace Serein.Workbench _ => throw new Exception("窗体事件 FlowEnvironment_NodeConnectChangeEvemt 创建/删除节点之间的参数传递关系 JunctionControlBase 枚举值错误 。非预期的枚举值。") // 应该不会触发 }; - if(IToJunction.ArgDataJunction.Length == 0) + if(IToJunction.ArgDataJunction.Length <= eventArgs.ArgIndex) { - + _ = Task.Run(async () => + { + await Task.Delay(1000); + FlowEnvironment_NodeConnectChangeEvemt(eventArgs); + }); + return; } JunctionControlBase endJunction = IToJunction.ArgDataJunction[eventArgs.ArgIndex]; LineType lineType = LineType.Bezier; @@ -689,16 +698,11 @@ namespace Serein.Workbench private void FlowEnvironment_NodeCreateEvent(NodeCreateEventArgs eventArgs) { if (eventArgs.NodeModel is not NodeModelBase nodeModelBase) - { - return; - } - - if(nodeModelBase is null) { SereinEnv.WriteLine(InfoType.WARN, "OnNodeCreateEvent事件接收到意外的返回值"); return; } - // MethodDetails methodDetailss = eventArgs.MethodDetailss; + PositionOfUI position = eventArgs.Position; if(!NodeMVVMManagement.TryGetType(nodeModelBase.ControlType, out var nodeMVVM)) @@ -713,36 +717,25 @@ namespace Serein.Workbench return; } - NodeControlBase nodeControl = CreateNodeControl(nodeMVVM.ControlType, nodeMVVM.ViewModelType, nodeModelBase); + NodeControlBase nodeControl = CreateNodeControl(nodeMVVM.ControlType, nodeMVVM.ViewModelType, nodeModelBase); // 创建控件 if (nodeControl is null) { return; } - NodeControls.TryAdd(nodeModelBase.Guid, nodeControl); - if (eventArgs.IsAddInRegion && NodeControls.TryGetValue(eventArgs.RegeionGuid, out NodeControlBase? regionControl)) + + NodeControls.TryAdd(nodeModelBase.Guid, nodeControl); // 添加到 + if (TryPlaceNodeInRegion(nodeControl, position, out var regionControl)) // 判断添加到区域容器 { - // 这里的条件是用于加载项目文件时,直接加载在区域中,而不用再判断控件 - if (regionControl is not null) - { - TryPlaceNodeInRegion(regionControl, nodeControl); - } - return; + // 通知运行环境调用加载节点子项的方法 + _ = EnvDecorator.ChangeNodeContainerChild(nodeControl.ViewModel.NodeModel.Guid, + regionControl.ViewModel.NodeModel.Guid, + true); } else { - // 这里是正常的编辑流程 - // 判断是否为区域 - if (TryPlaceNodeInRegion(nodeControl, position, out var targetNodeControl)) - { - // 需要将节点放置在区域中 - TryPlaceNodeInRegion(targetNodeControl, nodeControl); - } - else - { - // 并非区域,需要手动添加 - PlaceNodeOnCanvas(nodeControl, position.X, position.Y); // 将节点放置在画布上 - } + // 并非添加在容器中,直接放置节点 + PlaceNodeOnCanvas(nodeControl, position.X, position.Y); } @@ -761,6 +754,38 @@ namespace Serein.Workbench } + /// + /// 节点父子关系发生改变 + /// + /// + /// + private void EnvDecorator_OnNodeParentChildChange(NodeContainerChildChangeEventArgs eventArgs) + { + string childNodeGuid = eventArgs.ChildNodeGuid; + string containerNodeGuid = eventArgs.ContainerNodeGuid; + if (!TryGetControl(childNodeGuid, out var childNodeControl) + || !TryGetControl(containerNodeGuid, out var containerNodeControl)) + { + return; + } + if(containerNodeControl is not INodeContainerControl containerControl) + { + SereinEnv.WriteLine(InfoType.WARN, $"节点[{childNodeGuid}]无法放置在节点[{containerNodeGuid}],因为后者并不实现 INodeContainerControl 接口"); + return; + } + + if (eventArgs.State == NodeContainerChildChangeEventArgs.Type.Place) + { + FlowChartCanvas.Children.Remove(childNodeControl); + containerControl.PlaceNode(childNodeControl); // 放置 + } + else + { + containerControl.TakeOutNode(childNodeControl); // 取出 + } + + } + /// /// 设置了流程起始控件 /// @@ -1031,64 +1056,6 @@ namespace Serein.Workbench #endregion - #region 加载项目文件后触发事件相关方法 - - /// - /// 运行环节加载了项目文件,需要创建节点控件 - /// - /// - /// - /// - /// - //private NodeControlBase? CreateNodeControlOfNodeInfo(NodeInfo nodeInfo, MethodDetails methodDetailss) - //{ - // // 创建控件实例 - // NodeControlBase nodeControl = nodeInfo.Type switch - // { - // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" => - // CreateNodeControl(methodDetailss),// 动作节点控件 - // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" => - // CreateNodeControl(methodDetailss), // 触发器节点控件 - - // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" => - // CreateNodeControl(), // 条件表达式控件 - // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => - // CreateNodeControl(), // 操作表达式控件 - - // $"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => - // CreateNodeControl(), // 条件区域控件 - // _ => throw new NotImplementedException($"非预期的节点类型{nodeInfo.Type}"), - // }; - // return nodeControl; - //} - - /// - /// 加载文件时,添加节点到区域中 - /// - /// - /// - //private void AddNodeControlInRegeionControl(NodeControlBase regionControl, NodeInfo[] childNodes) - //{ - // foreach (var childNode in childNodes) - // { - // if (FlowEnvironment.TryGetMethodDetails(childNode.MethodName, out MethodDetails md)) - // { - // var childNodeControl = CreateNodeControlOfNodeInfo(childNode, md); - // if (childNodeControl is null) - // { - // Console.WriteLine($"无法为节点类型创建节点控件: {childNode.MethodName}\r\n"); - // continue; - // } - - // if (regionControl is ConditionRegionControl conditionRegion) - // { - // conditionRegion.AddCondition(childNodeControl); - // } - // } - // } - //} - - #endregion #region 节点控件的创建 @@ -1370,9 +1337,13 @@ namespace Serein.Workbench { if (sender is UserControl control) { - // 创建一个 DataObject 用于拖拽操作,并设置拖拽效果 - var dragData = new DataObject(MouseNodeType.CreateBaseNodeInCanvas, control.GetType()); - DragDrop.DoDragDrop(control, dragData, DragDropEffects.Move); + if(e.LeftButton == MouseButtonState.Pressed) + { + // 创建一个 DataObject 用于拖拽操作,并设置拖拽效果 + var dragData = new DataObject(MouseNodeType.CreateBaseNodeInCanvas, control.GetType()); + DragDrop.DoDragDrop(control, dragData, DragDropEffects.Move); + } + } } @@ -1434,7 +1405,9 @@ namespace Serein.Workbench /// /// 目标节点控件 /// - private bool TryPlaceNodeInRegion(NodeControlBase nodeControl, PositionOfUI position, out NodeControlBase targetNodeControl) + private bool TryPlaceNodeInRegion(NodeControlBase nodeControl, + PositionOfUI position, + out NodeControlBase targetNodeControl) { var point = new Point(position.X, position.Y); HitTestResult hitTestResult = VisualTreeHelper.HitTest(FlowChartCanvas, point); @@ -1452,9 +1425,10 @@ namespace Serein.Workbench return true; } } - // 准备放置全局数据控件 + else { + // 准备放置全局数据控件 GlobalDataControl? globalDataControl = GetParentOfType(hitElement); if (globalDataControl is not null) { @@ -1467,31 +1441,32 @@ namespace Serein.Workbench return false; } - /// - /// 将节点放在目标区域中 - /// - /// 区域容器 - /// 节点控件 - private void TryPlaceNodeInRegion(NodeControlBase regionControl, NodeControlBase nodeControl) - { - // 准备放置条件表达式控件 - if (nodeControl.ViewModel.NodeModel.ControlType == NodeControlType.ExpCondition) - { - if (regionControl is ConditionRegionControl conditionRegion) - { - conditionRegion.AddCondition(nodeControl); // 条件区域容器 - } - } - else if(regionControl.ViewModel.NodeModel.ControlType == NodeControlType.GlobalData) - { - if (regionControl is GlobalDataControl globalDataControl) - { - // 全局数据节点容器 - globalDataControl.SetDataNodeControl(nodeControl); - } - } + ///// + ///// 将节点放在目标区域中 + ///// + ///// 区域容器 + ///// 节点控件 + //private void TryPlaceNodeInRegion(NodeControlBase regionControl, NodeControlBase nodeControl) + //{ + // // 准备放置条件表达式控件 + // if (nodeControl.ViewModel.NodeModel.ControlType == NodeControlType.ExpCondition) + // { + // if (regionControl is ConditionRegionControl conditionRegion) + // { + // conditionRegion.AddCondition(nodeControl); // 条件区域容器 + // } + // } + // else if(regionControl.ViewModel.NodeModel.ControlType == NodeControlType.GlobalData) + // { + // if (regionControl is GlobalDataControl globalDataControl) + // { + // // 全局数据节点容器 + // globalDataControl.SetDataNodeControl(nodeControl); + // } + // } + //} + - } /// /// 拖动效果,根据拖放数据是否为指定类型设置拖放效果 @@ -2413,8 +2388,6 @@ namespace Serein.Workbench model.Guid = Guid.NewGuid().ToString(); } - // Convert.ChangeType(model, targetType); - var viewModel = Activator.CreateInstance(viewModelType, [model]); var controlObj = Activator.CreateInstance(controlType, [viewModel]); if (controlObj is NodeControlBase nodeControl) @@ -2516,9 +2489,8 @@ namespace Serein.Workbench // 获取主线程的 SynchronizationContext Action uiInvoke = (uiContext, action) => uiContext?.Post(state => action?.Invoke(), null); - - - Task.Run(async () => + SereinEnv.WriteLine(InfoType.INFO, "流程开始运行"); + _ = Task.Run(async () => { await EnvDecorator.StartAsync(); }); @@ -2752,32 +2724,34 @@ namespace Serein.Workbench /// private void CpoyNodeInfo() { + if(selectNodeControls.Count == 0) + { + return; + } // 处理复制操作 var dictSelection = selectNodeControls - .Select(control => control.ViewModel.NodeModel.ToInfo()) - .ToDictionary(kvp => kvp.Guid, kvp => kvp); + .Select(control => control.ViewModel.NodeModel).ToList(); + // 遍历当前已选节点 - foreach (var node in dictSelection.Values.ToArray()) + foreach (var node in dictSelection.ToArray()) { - if(node.ChildNodeGuids is null) + if(node.ChildrenNode.Count == 0) { continue; } - // 遍历这些节点的子节点,获得完整的已选节点信息 - foreach (var childNodeGuid in node.ChildNodeGuids) + // 遍历这些节点的子节点,添加过来 + foreach (var childNode in node.ChildrenNode) { - if(!dictSelection.ContainsKey(childNodeGuid) && NodeControls.TryGetValue(childNodeGuid,out var childNode)) - { - dictSelection.Add(childNodeGuid, childNode.ViewModel.NodeModel.ToInfo()); - } + dictSelection.Add(childNode); } } - + + var nodeInfos = dictSelection.Select(item => item.ToInfo()); JObject json = new JObject() { - ["nodes"] = JArray.FromObject(dictSelection.Values) + ["nodes"] = JArray.FromObject(nodeInfos) }; var jsonText = json.ToString(); diff --git a/WorkBench/Themes/MethodDetailsControl.xaml b/WorkBench/Themes/MethodDetailsControl.xaml index 39749d4..71259a4 100644 --- a/WorkBench/Themes/MethodDetailsControl.xaml +++ b/WorkBench/Themes/MethodDetailsControl.xaml @@ -37,14 +37,13 @@ - diff --git a/Workbench/Node/INodeContainerControl.cs b/Workbench/Node/INodeContainerControl.cs new file mode 100644 index 0000000..6ca5208 --- /dev/null +++ b/Workbench/Node/INodeContainerControl.cs @@ -0,0 +1,33 @@ +using Serein.Workbench.Node.View; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Workbench.Node +{ + + /// + /// 约束具有容器功能的节点控件应该有什么方法 + /// + public interface INodeContainerControl + { + /// + /// 放置一个节点 + /// + /// + void PlaceNode(NodeControlBase nodeControl); + + /// + /// 取出一个节点 + /// + /// + void TakeOutNode(NodeControlBase nodeControl); + + /// + /// 取出所有节点(用于删除容器) + /// + void TakeOutAll(); + } +} diff --git a/Workbench/Node/INodeJunction.cs b/Workbench/Node/INodeJunction.cs index f1edd1a..ae74d0d 100644 --- a/Workbench/Node/INodeJunction.cs +++ b/Workbench/Node/INodeJunction.cs @@ -8,6 +8,9 @@ using System.Windows; namespace Serein.Workbench.Node { + + + /// /// 约束一个节点应该有哪些控制点 /// diff --git a/Workbench/Node/View/GlobalDataControl.xaml.cs b/Workbench/Node/View/GlobalDataControl.xaml.cs index e14c8e2..8a8da40 100644 --- a/Workbench/Node/View/GlobalDataControl.xaml.cs +++ b/Workbench/Node/View/GlobalDataControl.xaml.cs @@ -20,10 +20,8 @@ namespace Serein.Workbench.Node.View /// /// UserControl1.xaml 的交互逻辑 /// - public partial class GlobalDataControl : NodeControlBase, INodeJunction + public partial class GlobalDataControl : NodeControlBase, INodeJunction, INodeContainerControl { - //private new GlobalDataNodeControlViewModel ViewModel => ViewModel; - public GlobalDataControl() : base() { // 窗体初始化需要 @@ -39,19 +37,6 @@ namespace Serein.Workbench.Node.View } - /// - /// 设置数据节点 - /// - /// - public void SetDataNodeControl(NodeControlBase nodeControl) - { - ((GlobalDataNodeControlViewModel)ViewModel).SetDataNode(nodeControl.ViewModel.NodeModel); - - GlobalDataPanel.Children.Clear(); - GlobalDataPanel.Children.Add(nodeControl); - } - - /// /// 入参控制点(可能有,可能没) /// @@ -73,5 +58,21 @@ namespace Serein.Workbench.Node.View JunctionControlBase[] INodeJunction.ArgDataJunction => throw new NotImplementedException(); + public void PlaceNode(NodeControlBase nodeControl) + { + GlobalDataPanel.Children.Clear(); + GlobalDataPanel.Children.Add(nodeControl); + } + + public void TakeOutNode(NodeControlBase nodeControl) + { + GlobalDataPanel.Children.Remove(nodeControl); + } + + public void TakeOutAll() + { + GlobalDataPanel.Children.Clear(); + } + } } diff --git a/Workbench/Node/ViewModel/GlobalDataNodeControlViewModel.cs b/Workbench/Node/ViewModel/GlobalDataNodeControlViewModel.cs index 300fbbf..a396cba 100644 --- a/Workbench/Node/ViewModel/GlobalDataNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/GlobalDataNodeControlViewModel.cs @@ -1,5 +1,6 @@ using Serein.Library; using Serein.NodeFlow.Model; +using Serein.Workbench.Node.View; using System; using System.Collections.Generic; using System.Linq; @@ -44,18 +45,7 @@ namespace Serein.Workbench.Node.ViewModel set { NodeModel.KeyName = value; OnPropertyChanged(); } } - - /// - /// 设置数据节点 - /// - /// - public void SetDataNode(NodeModelBase dataNode) - { - NodeModel.SetDataNode(dataNode); - } - - - - + + } }