From 6f26d303e4454bc4df31e30b8b6778c32ee64b17 Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Thu, 24 Oct 2024 23:32:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BA=86=E6=8B=96=E6=8B=BD?= =?UTF-8?q?=E5=BC=8F=E8=AE=BE=E7=BD=AE=E6=96=B9=E6=B3=95=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F=E3=80=81=E6=96=B9=E6=B3=95=E5=85=A5=E5=8F=82?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E6=9D=A5=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library.Core/NodeFlow/DynamicContext.cs | 5 + Library.Framework/NodeFlow/DynamicContext.cs | 6 + Library/Api/IDynamicContext.cs | 8 + Library/Api/IFlowEnvironment.cs | 60 +- Library/Enums/ConnectionArgSourceType.cs | 28 + ...nectionType.cs => ConnectionInvokeType.cs} | 9 +- Library/Enums/JunctionOfConnectionType.cs | 27 + Library/Enums/JunctionType.cs | 6 +- Library/FlowNode/JunctionModel.cs | 56 ++ Library/FlowNode/NodeModelBaseData.cs | 17 +- Library/FlowNode/NodeModelBaseFunc.cs | 249 +++++--- Library/FlowNode/ParameterDetails.cs | 37 +- Library/NodeStaticConfig.cs | 10 +- Library/Utils/ObjectConvertHelper.cs | 82 +++ Library/Utils/SereinIoc.cs | 6 +- NodeFlow/Env/FlowEnvironment.cs | 230 +++++++- NodeFlow/Env/FlowEnvironmentDecorator.cs | 18 +- NodeFlow/Env/FlowFunc.cs | 20 +- NodeFlow/Env/MsgControllerOfServer.cs | 6 +- NodeFlow/Env/RemoteFlowEnvironment.cs | 47 +- NodeFlow/FlowStarter.cs | 4 +- NodeFlow/Model/CompositeActionNode.cs | 8 +- NodeFlow/Model/CompositeConditionNode.cs | 20 +- NodeFlow/Model/SingleConditionNode.cs | 6 +- NodeFlow/Model/SingleExpOpNode.cs | 4 +- NodeFlow/Model/SingleFlipflopNode.cs | 8 +- .../ParameterDetailsPropertyGenerator.cs | 1 + WorkBench/MainWindow.xaml.cs | 387 ++++++++----- WorkBench/Node/View/ActionNodeControl.xaml | 45 +- WorkBench/Node/View/ActionNodeControl.xaml.cs | 77 ++- WorkBench/Themes/MethodDetailsControl.xaml | 91 +-- .../Themes/NodeTreeItemViewControl.xaml.cs | 32 +- Workbench/Node/INodeJunction.cs | 34 ++ Workbench/Node/Junction/BezierLine.cs | 222 +++++++ Workbench/Node/Junction/JunctionCode.cs | 93 --- .../Node/Junction/JunctionControlBase.cs | 134 +++++ Workbench/Node/Junction/JunctionData.cs | 95 ++- .../Node/Junction/View/ArgJunctionControl.cs | 58 +- .../Junction/View/ExecuteJunctionControl.cs | 85 ++- .../Junction/View/NextStepJunctionControl.cs | 61 +- .../Junction/View/ResultJunctionControl.cs | 56 +- Workbench/Node/NodeControlBase.cs | 49 ++ Workbench/Node/View/ConnectionControl.cs | 548 +++++++++++++----- 43 files changed, 2282 insertions(+), 763 deletions(-) create mode 100644 Library/Enums/ConnectionArgSourceType.cs rename Library/Enums/{ConnectionType.cs => ConnectionInvokeType.cs} (88%) create mode 100644 Library/Enums/JunctionOfConnectionType.cs create mode 100644 Library/FlowNode/JunctionModel.cs create mode 100644 Library/Utils/ObjectConvertHelper.cs create mode 100644 Workbench/Node/INodeJunction.cs create mode 100644 Workbench/Node/Junction/BezierLine.cs delete mode 100644 Workbench/Node/Junction/JunctionCode.cs create mode 100644 Workbench/Node/Junction/JunctionControlBase.cs diff --git a/Library.Core/NodeFlow/DynamicContext.cs b/Library.Core/NodeFlow/DynamicContext.cs index 96aa34c..7315912 100644 --- a/Library.Core/NodeFlow/DynamicContext.cs +++ b/Library.Core/NodeFlow/DynamicContext.cs @@ -30,6 +30,11 @@ namespace Serein.Library.Core.NodeFlow /// public RunState RunState { get; set; } = RunState.NoStart; + /// + /// 当前节点执行完成后,设置该属性,让运行环境判断接下来要执行哪个分支的节点。 + /// + public ConnectionInvokeType NextOrientation { get; set; } + /// /// 每个上下文分别存放节点的当前数据 /// diff --git a/Library.Framework/NodeFlow/DynamicContext.cs b/Library.Framework/NodeFlow/DynamicContext.cs index 33a7020..711a87b 100644 --- a/Library.Framework/NodeFlow/DynamicContext.cs +++ b/Library.Framework/NodeFlow/DynamicContext.cs @@ -28,6 +28,12 @@ namespace Serein.Library.Framework.NodeFlow /// 运行状态 /// public RunState RunState { get; set; } = RunState.NoStart; + + /// + /// 当前节点执行完成后,设置该属性,让运行环境判断接下来要执行哪个分支的节点。 + /// + public ConnectionInvokeType NextOrientation { get; set; } + /// /// 每个上下文分别存放节点的当前数据 /// diff --git a/Library/Api/IDynamicContext.cs b/Library/Api/IDynamicContext.cs index f9ab472..925356a 100644 --- a/Library/Api/IDynamicContext.cs +++ b/Library/Api/IDynamicContext.cs @@ -15,8 +15,16 @@ namespace Serein.Library.Api /// IFlowEnvironment Env { get; } + /// + /// 是否正在运行 + /// RunState RunState { get; } + /// + /// 下一个要执行的节点 + /// + ConnectionInvokeType NextOrientation { get; set; } + /// /// 获取节点的数据(当前节点需要获取上一节点数据时,需要从 运行时上一节点 的Guid 通过这个方法进行获取 /// diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index fce88a0..c89a419 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -1,4 +1,5 @@  + using Serein.Library.Utils; using System; using System.Collections.Generic; @@ -175,12 +176,33 @@ namespace Serein.Library.Api /// Remote, } - public NodeConnectChangeEventArgs(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType, ConnectChangeType changeType) + public NodeConnectChangeEventArgs(string fromNodeGuid, + string toNodeGuid, + JunctionOfConnectionType junctionOfConnectionType, // 指示需要创建什么类型的连接线 + ConnectionInvokeType connectionInvokeType, // 节点调用的方法类型(true/false/error/cancel ) + ConnectChangeType changeType) // 需要创建连接线还是删除连接线 { this.FromNodeGuid = fromNodeGuid; this.ToNodeGuid = toNodeGuid; - this.ConnectionType = connectionType; + this.ConnectionInvokeType = connectionInvokeType; this.ChangeType = changeType; + this.JunctionOfConnectionType = junctionOfConnectionType; + } + + public NodeConnectChangeEventArgs(string fromNodeGuid, + string toNodeGuid, + JunctionOfConnectionType junctionOfConnectionType, // 指示需要创建什么类型的连接线 + int argIndex, + ConnectionArgSourceType connectionArgSourceType, // 节点对应的方法入参所需参数来源 + ConnectChangeType changeType) // 需要创建连接线还是删除连接线 + { + this.FromNodeGuid = fromNodeGuid; + this.ToNodeGuid = toNodeGuid; + this.ChangeType = changeType; + this.ArgIndex = argIndex; + this.ConnectionArgSourceType = connectionArgSourceType; + this.JunctionOfConnectionType = junctionOfConnectionType; + } /// /// 连接关系中始节点的Guid @@ -193,11 +215,22 @@ namespace Serein.Library.Api /// /// 连接类型 /// - public ConnectionType ConnectionType { get; protected set; } + public ConnectionInvokeType ConnectionInvokeType { get; protected set; } /// /// 表示此次需要在两个节点之间创建连接关系,或是移除连接关系 /// public ConnectChangeType ChangeType { get; protected set; } + /// + /// 指示需要创建什么类型的连接线 + /// + public JunctionOfConnectionType JunctionOfConnectionType { get; protected set; } + /// + /// 节点对应的方法入参所需参数来源 + /// + public ConnectionArgSourceType ConnectionArgSourceType { get; protected set; } + public int ArgIndex { get; protected set; } + + } @@ -639,6 +672,13 @@ namespace Serein.Library.Api /// Task StartAsyncInSelectNode(string startNodeGuid); + /// + /// 立刻调用某个节点,并获取其返回值 + /// + /// 节点Guid + /// + Task InvokeNodeAsync(string nodeGuid); + /// /// 结束运行 /// @@ -663,8 +703,16 @@ namespace Serein.Library.Api /// /// 起始节点Guid /// 目标节点Guid - /// 连接类型 - Task ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, JunctionType toNodeJunctionType, ConnectionType connectionType); + /// 起始节点控制点 + /// 目标节点控制点 + /// 决定了方法执行后的后继行为 + /// 决定了方法入参来源 + Task ConnectNodeAsync(string fromNodeGuid, + string toNodeGuid, + JunctionType fromNodeJunctionType, + JunctionType toNodeJunctionType, + ConnectionInvokeType connectionType, + int argIndex); /// /// 创建节点/区域/基础控件 @@ -680,7 +728,7 @@ namespace Serein.Library.Api /// 起始节点 /// 目标节点 /// 连接类型 - Task RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType); + Task RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType); /// /// 移除节点/区域/基础控件 diff --git a/Library/Enums/ConnectionArgSourceType.cs b/Library/Enums/ConnectionArgSourceType.cs new file mode 100644 index 0000000..c985604 --- /dev/null +++ b/Library/Enums/ConnectionArgSourceType.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library +{ + + /// + /// 节点对应方法的入参来源 + /// + public enum ConnectionArgSourceType + { + /// + /// (连接自身)从上一节点获取数据 + /// + GetPreviousNodeData, + /// + /// 从指定节点获取数据 + /// + GetOtherNodeData, + /// + /// 立刻执行某个节点获取其数据 + /// + GetOtherNodeDataOfInvoke, + } +} diff --git a/Library/Enums/ConnectionType.cs b/Library/Enums/ConnectionInvokeType.cs similarity index 88% rename from Library/Enums/ConnectionType.cs rename to Library/Enums/ConnectionInvokeType.cs index 9412f99..568fe8d 100644 --- a/Library/Enums/ConnectionType.cs +++ b/Library/Enums/ConnectionInvokeType.cs @@ -8,7 +8,7 @@ namespace Serein.Library /// /// 表示了两个节点之间的连接关系,同时表示节点运行完成后,所会执行的下一个节点类型。 /// - public enum ConnectionType + public enum ConnectionInvokeType { /// /// 将不会继续执行 @@ -30,11 +30,8 @@ namespace Serein.Library /// 异常发生分支(当前节点对应的方法执行时出现非预期的异常) /// IsError, - /// - /// 无视 - /// - // IsIgnore, } - + + } diff --git a/Library/Enums/JunctionOfConnectionType.cs b/Library/Enums/JunctionOfConnectionType.cs new file mode 100644 index 0000000..c2790ff --- /dev/null +++ b/Library/Enums/JunctionOfConnectionType.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library +{ + /// + /// 连接的控制点类型枚举 + /// + public enum JunctionOfConnectionType + { + /// + /// 没有关系,用于处理非预期连接的情况需要的返回值 + /// + None, + /// + /// 表示方法执行顺序关系 + /// + Invoke, + /// + /// 表示参数获取来源关系 + /// + Arg + } +} diff --git a/Library/Enums/JunctionType.cs b/Library/Enums/JunctionType.cs index 83941fb..805d930 100644 --- a/Library/Enums/JunctionType.cs +++ b/Library/Enums/JunctionType.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Serein.Library { /// - /// 连接点类型 + /// 控制点类型 /// public enum JunctionType { @@ -28,4 +28,8 @@ namespace Serein.Library /// NextStep, } + + + + } diff --git a/Library/FlowNode/JunctionModel.cs b/Library/FlowNode/JunctionModel.cs new file mode 100644 index 0000000..6fbf28e --- /dev/null +++ b/Library/FlowNode/JunctionModel.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.FlowNode +{ + + + /* + * 有1个Execute + * 有1个NextStep + * 有0~65535个入参 ushort + * 有1个ReturnData(void方法返回null) + * + * Execute: // 执行这个方法 + * 只接受 NextStep 的连接 + * ArgData: + * 互相之间不能连接,只能接受 Execute、ReturnData 的连接 + * Execute:表示从 Execute所在节点 获取数据 + * ReturnData: 表示从对应节点获取数据 + * ReturnData: + * 只能发起主动连接,且只能连接到 ArgData + * NextStep + * 只能连接连接 Execute + * + */ + + /// + /// 依附于节点的连接点 + /// + public class JunctionModel + { + public JunctionModel(NodeModelBase NodeModel, JunctionType JunctionType) + { + Guid = System.Guid.NewGuid().ToString(); + this.NodeModel = NodeModel; + this.JunctionType = JunctionType; + } + /// + /// 用于标识连接点 + /// + public string Guid { get; } + + /// + /// 标识连接点的类型 + /// + public JunctionType JunctionType { get; } + + /// + /// 连接点依附的节点 + /// + public NodeModelBase NodeModel { get; } + } +} diff --git a/Library/FlowNode/NodeModelBaseData.cs b/Library/FlowNode/NodeModelBaseData.cs index 5021a68..061d4dd 100644 --- a/Library/FlowNode/NodeModelBaseData.cs +++ b/Library/FlowNode/NodeModelBaseData.cs @@ -3,6 +3,7 @@ using Serein.Library.NodeGenerator; using System; using System.Collections.Generic; using System.ComponentModel; +using System.Net.Mime; using System.Threading; namespace Serein.Library @@ -71,8 +72,8 @@ namespace Serein.Library /// /// 当前节点执行完毕后需要执行的下一个分支的类别 /// - [PropertyInfo] - private ConnectionType _nextOrientation = ConnectionType.None; + //[PropertyInfo] + //private ConnectionInvokeType _nextOrientation = ConnectionInvokeType.None; /// /// 运行时的异常信息(仅在 FlowState 为 Error 时存在对应值) @@ -91,9 +92,9 @@ namespace Serein.Library { public NodeModelBase(IFlowEnvironment environment) { - PreviousNodes = new Dictionary>(); - SuccessorNodes = new Dictionary>(); - foreach (ConnectionType ctType in NodeStaticConfig.ConnectionTypes) + PreviousNodes = new Dictionary>(); + SuccessorNodes = new Dictionary>(); + foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes) { PreviousNodes[ctType] = new List(); SuccessorNodes[ctType] = new List(); @@ -102,15 +103,17 @@ namespace Serein.Library this.Env = environment; } + + /// /// 不同分支的父节点 /// - public Dictionary> PreviousNodes { get; } + public Dictionary> PreviousNodes { get; } /// /// 不同分支的子节点 /// - public Dictionary> SuccessorNodes { get; } + public Dictionary> SuccessorNodes { get; } /// diff --git a/Library/FlowNode/NodeModelBaseFunc.cs b/Library/FlowNode/NodeModelBaseFunc.cs index 0a3ae20..9893174 100644 --- a/Library/FlowNode/NodeModelBaseFunc.cs +++ b/Library/FlowNode/NodeModelBaseFunc.cs @@ -6,6 +6,7 @@ using Serein.Library.Utils.SereinExpression; using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel.Design; using System.Linq; using System.Linq.Expressions; using System.Net.Http.Headers; @@ -24,22 +25,6 @@ namespace Serein.Library /// public abstract partial class NodeModelBase : IDynamicFlowNode { - - - #region 调试中断 - - - /// - /// 不再中断 - /// - public void CancelInterrupt() - { - this.DebugSetting.InterruptClass = InterruptClass.None; - DebugSetting.CancelInterruptCallback?.Invoke(); - } - - #endregion - #region 导出/导入项目文件节点信息 /// @@ -56,10 +41,10 @@ namespace Serein.Library { // if (MethodDetails == null) return null; - var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支 - var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支 - var errorNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 异常分支 - var upstreamNodes = SuccessorNodes[ConnectionType.Upstream].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(); @@ -86,7 +71,7 @@ namespace Serein.Library /// public virtual NodeModelBase LoadInfo(NodeInfo nodeInfo) { - this.Guid = nodeInfo.Guid; + this.Guid = nodeInfo.Guid; if (nodeInfo.Position is null) { @@ -104,6 +89,19 @@ namespace Serein.Library } return this; } + #endregion + + #region 调试中断 + + + /// + /// 不再中断 + /// + public void CancelInterrupt() + { + this.DebugSetting.InterruptClass = InterruptClass.None; + DebugSetting.CancelInterruptCallback?.Invoke(); + } #endregion @@ -137,8 +135,6 @@ namespace Serein.Library return false; } - - /// /// 开始执行 /// @@ -163,7 +159,7 @@ namespace Serein.Library var currentNode = stack.Pop(); // 筛选出上游分支 - var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream].ToArray(); + var upstreamNodes = currentNode.SuccessorNodes[ConnectionInvokeType.Upstream].ToArray(); for (int index = 0; index < upstreamNodes.Length; index++) { NodeModelBase upstreamNode = upstreamNodes[index]; @@ -176,7 +172,7 @@ namespace Serein.Library } upstreamNode.PreviousNode = currentNode; await upstreamNode.StartFlowAsync(context); // 执行流程节点的上游分支 - if (upstreamNode.NextOrientation == ConnectionType.IsError) + if (context.NextOrientation == ConnectionInvokeType.IsError) { // 如果上游分支执行失败,不再继续执行 // 使上游节点(仅上游节点本身,不包含上游节点的后继节点) @@ -197,7 +193,7 @@ namespace Serein.Library #region 执行完成 // 选择后继分支 - var nextNodes = currentNode.SuccessorNodes[currentNode.NextOrientation]; + var nextNodes = currentNode.SuccessorNodes[context.NextOrientation]; // 将下一个节点集合中的所有节点逆序推入栈中 for (int i = nextNodes.Count - 1; i >= 0; i--) @@ -215,7 +211,6 @@ namespace Serein.Library } } - /// /// 执行节点对应的方法 /// @@ -234,7 +229,6 @@ namespace Serein.Library #endregion MethodDetails md = MethodDetails; - //var del = md.MethodDelegate.Clone(); if (md is null) { throw new Exception($"节点{this.Guid}不存在方法信息,请检查是否需要重写节点的ExecutingAsync"); @@ -247,35 +241,70 @@ namespace Serein.Library { md.ActingInstance = context.Env.IOC.Get(md.ActingInstanceType); } - // md.ActingInstance ??= context.Env.IOC.Get(md.ActingInstanceType); - object instance = md.ActingInstance; - - - object result = null; - try { - object[] args = GetParameters(context, this, md); - result = await dd.InvokeAsync(md.ActingInstance, args); - NextOrientation = ConnectionType.IsSucceed; + object[] args = await GetParametersAsync(context, this, md); + var result = await dd.InvokeAsync(md.ActingInstance, args); + context.NextOrientation = ConnectionInvokeType.IsSucceed; return result; } catch (Exception ex) { await Console.Out.WriteLineAsync($"节点[{this.MethodDetails?.MethodName}]异常:" + ex); - NextOrientation = ConnectionType.IsError; + context.NextOrientation = ConnectionInvokeType.IsError; RuningException = ex; return null; } } + /// + /// 执行单个节点对应的方法,并不做状态检查 + /// + /// + /// + public virtual async Task InvokeAsync(IFlowEnvironment env) + { + try + { + MethodDetails md = MethodDetails; + if (md is null) + { + throw new Exception($"不存在方法信息{md.MethodName}"); + } + if (!env.TryGetDelegateDetails(md.MethodName, out var dd)) + { + throw new Exception($"不存在对应委托{md.MethodName}"); + } + if (md.ActingInstance is null) + { + md.ActingInstance = env.IOC.Get(md.ActingInstanceType); + if (md.ActingInstance is null) + { + md.ActingInstance = env.IOC.Instantiate(md.ActingInstanceType); + if (md.ActingInstance is null) + { + throw new Exception($"无法创建相应的实例{md.ActingInstanceType.FullName}"); + } + } + } + object[] args = await GetParametersAsync(null, this, md); + var result = await dd.InvokeAsync(md.ActingInstance, args); + return result; + } + catch (Exception ex) + { + await Console.Out.WriteLineAsync($"节点[{this.MethodDetails?.MethodName}]异常:" + ex); + return null; + } + } /// /// 获取对应的参数数组 /// - public static object[] GetParameters(IDynamicContext context, NodeModelBase nodeModel, MethodDetails md) + public static async Task GetParametersAsync(IDynamicContext context, NodeModelBase nodeModel, MethodDetails md) { + await Task.Delay(0); // 用正确的大小初始化参数数组 if (md.ParameterDetailss.Length == 0) { @@ -283,22 +312,34 @@ namespace Serein.Library } object[] parameters = new object[md.ParameterDetailss.Length]; - var flowData = nodeModel.PreviousNode?.FlowData; // 当前传递的数据 - var previousDataType = flowData?.GetType(); + var previousFlowData = nodeModel.PreviousNode?.FlowData; // 当前传递的数据 + var previousDataType = previousFlowData?.GetType(); // 当前传递数据的类型 for (int i = 0; i < parameters.Length; i++) { - - object inputParameter; // 存放解析的临时参数 var ed = md.ParameterDetailss[i]; // 方法入参描述 + #region 获取基础的上下文数据 + if (ed.DataType == typeof(IFlowEnvironment)) // 获取流程上下文 + { + parameters[i] = nodeModel.Env; + continue; + } + if (ed.DataType == typeof(IDynamicContext)) // 获取流程上下文 + { + parameters[i] = context; + continue; + } + #endregion + #region 确定[预入参]数据 + object inputParameter; // 存放解析的临时参数 if (ed.IsExplicitData) // 判断是否使用显示的输入参数 { - if (ed.DataValue.StartsWith("@get", StringComparison.OrdinalIgnoreCase) && !(flowData is null)) + if (ed.DataValue.StartsWith("@get", StringComparison.OrdinalIgnoreCase) && !(previousFlowData is null)) { // 执行表达式从上一节点获取对象 - inputParameter = SerinExpressionEvaluator.Evaluate(ed.DataValue, flowData, out _); + inputParameter = SerinExpressionEvaluator.Evaluate(ed.DataValue, previousFlowData, out _); } else { @@ -308,9 +349,31 @@ namespace Serein.Library } else { - inputParameter = flowData; // 使用上一节点的对象 - } + if (ed.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData) + { + inputParameter = previousFlowData; // 使用运行时上一节点的返回值 + } + else if (ed.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData) + { + // 获取指定节点的数据 + // 如果指定节点没有被执行,会返回null + // 如果执行过,会获取上一次执行结果作为预入参数据 + inputParameter = ed.ArgDataSourceNodeMoels[i].FlowData; + } + else if (ed.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) + { + // 立刻调用对应节点获取数据。 + var result = await ed.ArgDataSourceNodeMoels[i].InvokeAsync(nodeModel.Env); + inputParameter = result; + } + else + { + throw new Exception("节点执行方法获取入参参数时,ConnectionArgSourceType枚举是意外的枚举值"); + } + } + #endregion + #region 入参存在取值转换器,调用对应的转换器获取入参数据 // 入参存在取值转换器 if (ed.ExplicitType.IsEnum && !(ed.Convertor is null)) { @@ -327,13 +390,11 @@ namespace Serein.Library parameters[i] = value; continue; } - //if (Enum.TryParse(ed.ExplicitType, ed.DataValue, out var resultEnum)) - //{ - - //} } + #endregion - // 入参存在类型转换器,获取枚举转换器中记录的枚举 + #region 入参存在基于BinValue的类型转换器,获取枚举转换器中记录的类型 + // 入参存在基于BinValue的类型转换器,获取枚举转换器中记录的类型 if (ed.ExplicitType.IsEnum && ed.DataType != ed.ExplicitType) { var resultEnum = Enum.Parse(ed.ExplicitType, ed.DataValue); @@ -341,7 +402,7 @@ namespace Serein.Library var type = EnumHelper.GetBoundValue(ed.ExplicitType, resultEnum, attr => attr.Value); if (type is Type enumBindType && !(enumBindType is null)) { - var value = context.Env.IOC.Instantiate(enumBindType); + var value = nodeModel.Env.IOC.Instantiate(enumBindType); if (value is null) { @@ -351,62 +412,83 @@ namespace Serein.Library parameters[i] = value; continue; } - } } + #endregion + #region 对入参数据尝试进行转换 - if (ed.DataType.IsValueType) + if (inputParameter.GetType() == ed.DataType) { - var valueStr = inputParameter?.ToString(); - parameters[i] = valueStr.ToValueData(ed.DataType); + parameters[i] = inputParameter; // 类型一致无需转换,直接装入入参数组 } - else + else if (ed.DataType.IsValueType) { + // 值类型 var valueStr = inputParameter?.ToString(); - if (ed.DataType == typeof(string)) + parameters[i] = valueStr.ToValueData(ed.DataType); // 类型不一致,尝试进行转换,如果转换失败返回类型对应的默认值 + } + else + { + // 引用类型 + if (ed.DataType == typeof(string)) // 转为字符串 { + var valueStr = inputParameter?.ToString(); parameters[i] = valueStr; } - else if (ed.DataType == typeof(IDynamicContext)) + else if(ed.DataType.IsSubclassOf(inputParameter.GetType())) // 入参类型 是 预入参数据类型 的 子类/实现类 { - parameters[i] = context; + // 方法入参中,父类不能隐式转为子类,这里需要进行强制转换 + parameters[i] = ObjectConvertHelper.ConvertParentToChild(inputParameter, ed.DataType); } - else if (ed.DataType == typeof(MethodDetails)) - { - parameters[i] = md; - } - else if (ed.DataType == typeof(NodeModelBase)) - { - parameters[i] = nodeModel; - } - else + else if(ed.DataType.IsAssignableFrom(inputParameter.GetType())) // 入参类型 是 预入参数据类型 的 父类/接口 { parameters[i] = inputParameter; } + // 集合类型 + else if(inputParameter is IEnumerable collection) + { + var enumerableMethods = typeof(Enumerable).GetMethods(); // 获取所有的 Enumerable 扩展方法 + MethodInfo conversionMethod; + if (ed.DataType.IsArray) // 转为数组 + { + parameters[i] = inputParameter; + conversionMethod = enumerableMethods.FirstOrDefault(m => m.Name == "ToArray" && m.IsGenericMethodDefinition); + } + else if (ed.DataType.GetGenericTypeDefinition() == typeof(List<>)) // 转为集合 + { + conversionMethod = enumerableMethods.FirstOrDefault(m => m.Name == "ToList" && m.IsGenericMethodDefinition); + } + else + { + throw new InvalidOperationException("输入对象不是集合或目标类型不支持(目前仅支持Array、List的自动转换)"); + } + var genericMethod = conversionMethod.MakeGenericMethod(ed.DataType); + var result = genericMethod.Invoke(null, new object[] { collection }); + parameters[i] = result; + } + - //parameters[i] = ed.DataType switch + + //else if (ed.DataType == typeof(MethodDetails)) // 希望获取节点对应的方法描述,好像没啥用 //{ - // Type t when t == typeof(string) => valueStr, - // Type t when t == typeof(IDynamicContext) => context, // 上下文 - // Type t when t == typeof(DateTime) => string.IsNullOrEmpty(valueStr) ? null : DateTime.Parse(valueStr), - - // Type t when t == typeof(MethodDetails) => md, // 节点方法描述 - // Type t when t == typeof(NodeModelBase) => nodeModel, // 节点实体类 - - // Type t when t.IsArray => (inputParameter as Array)?.Cast().ToList(), - // Type t when t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>) => inputParameter, - // _ => inputParameter, - //}; - } + // parameters[i] = md; + //} + //else if (ed.DataType == typeof(NodeModelBase)) // 希望获取方法生成的节点,好像没啥用 + //{ + // parameters[i] = nodeModel; + //} + } + #endregion } return parameters; } + /// /// 更新节点数据,并检查监视表达式是否生效 /// @@ -479,7 +561,6 @@ namespace Serein.Library } } - /// /// 释放对象 /// diff --git a/Library/FlowNode/ParameterDetails.cs b/Library/FlowNode/ParameterDetails.cs index d9c9491..b5dbce2 100644 --- a/Library/FlowNode/ParameterDetails.cs +++ b/Library/FlowNode/ParameterDetails.cs @@ -1,9 +1,6 @@ using Serein.Library.Api; using System; -using System.Collections.Generic; -using System.Diagnostics.Contracts; using System.Linq; -using System.Text; namespace Serein.Library { @@ -17,7 +14,7 @@ namespace Serein.Library private readonly IFlowEnvironment env; /// - /// 对应的节点 + /// 所在的节点 /// [PropertyInfo(IsProtection = true)] private NodeModelBase _nodeModel; @@ -29,7 +26,9 @@ namespace Serein.Library private int _index; /// - /// 是否为显式参数(固定值/表达式) + /// 是否为显式参数(固定值/表达式) + /// 如果为 true ,则使用UI输入的文本值作为入参数据(过程中会尽可能转为类型需要的数据)。 + /// 如果为 false ,则根据 ArgDataSourceType 调用相应节点的GetFlowData()方法,获取返回的数据作为入参数据。 /// [PropertyInfo(IsNotification = true)] private bool _isExplicitData ; @@ -41,7 +40,7 @@ namespace Serein.Library private Func _convertor ; /// - /// 显式类型 + /// 方法入参若无相关转换器特性标注,则无需关注该变量。该变量用于需要用到枚举BinValue转换器时,指示相应的入参变量需要转为的类型。 /// [PropertyInfo] private Type _explicitType ; @@ -56,7 +55,22 @@ namespace Serein.Library private string _explicitTypeName ; /// - /// 方法需要的类型 + /// 入参数据来源。默认使用上一节点作为入参数据。 + /// + [PropertyInfo(IsNotification = true)] + private ConnectionArgSourceType _argDataSourceType = ConnectionArgSourceType.GetPreviousNodeData; + + /// + /// 当 ArgDataSourceType 不为 GetPreviousNodeData 时(从运行时上一节点获取数据)。 + /// 则通过该集合对应的节点,获取其 FlowData 作为预处理的入参参数。 + /// + [PropertyInfo(IsProtection = true)] + public NodeModelBase[] _argDataSourceNodeMoels; + + + + /// + /// 方法入参需要的类型。 /// [PropertyInfo] private Type _dataType ; @@ -74,7 +88,7 @@ namespace Serein.Library private string _dataValue; /// - /// 如果是引用类型,拷贝时不会发生改变。 + /// 只有当ExplicitTypeName 为 Select 时,才会需要该成员。 /// [PropertyInfo(IsNotification = true)] private string[] _items ; @@ -91,6 +105,7 @@ namespace Serein.Library this.env = env; this.NodeModel = nodeModel; } + /// /// 通过参数信息加载实体,用于加载项目文件、远程连接的场景 /// @@ -109,12 +124,15 @@ namespace Serein.Library /// /// 用于创建元数据 /// - /// 方法参数信息 public ParameterDetails() { } + + + + /// /// 转为描述 /// @@ -151,6 +169,7 @@ namespace Serein.Library Name = this.Name, DataValue = string.IsNullOrEmpty(DataValue) ? string.Empty : DataValue, Items = this.Items?.Select(it => it).ToArray(), + }; return pd; } diff --git a/Library/NodeStaticConfig.cs b/Library/NodeStaticConfig.cs index 562df2d..c08c257 100644 --- a/Library/NodeStaticConfig.cs +++ b/Library/NodeStaticConfig.cs @@ -29,12 +29,12 @@ namespace Serein.Library /// /// 节点连接关系种类 /// - public static readonly ConnectionType[] ConnectionTypes = new ConnectionType[] + public static readonly ConnectionInvokeType[] ConnectionTypes = new ConnectionInvokeType[] { - ConnectionType.Upstream, - ConnectionType.IsSucceed, - ConnectionType.IsFail, - ConnectionType.IsError, + ConnectionInvokeType.Upstream, + ConnectionInvokeType.IsSucceed, + ConnectionInvokeType.IsFail, + ConnectionInvokeType.IsError, }; } } diff --git a/Library/Utils/ObjectConvertHelper.cs b/Library/Utils/ObjectConvertHelper.cs new file mode 100644 index 0000000..2aa566d --- /dev/null +++ b/Library/Utils/ObjectConvertHelper.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Utils +{ + public static class ObjectConvertHelper + { + /// + /// 父类转为子类 + /// + /// 父类对象 + /// 子类类型 + /// + public static object ConvertParentToChild(object parent,Type childType) + { + var child = Activator.CreateInstance(childType); + var parentType = parent.GetType(); + + // 复制父类属性 + foreach (var prop in parentType.GetProperties()) + { + if (prop.CanWrite) + { + var value = prop.GetValue(parent); + childType.GetProperty(prop.Name)?.SetValue(child, value); + } + } + return child; + } + + + /// + /// 集合类型转换为Array/List + /// + /// + /// + /// + /// + public static object ConvertToEnumerableType(object obj, Type targetType) + { + // 获取目标类型的元素类型 + Type targetElementType = targetType.IsArray + ? targetType.GetElementType() + : targetType.GetGenericArguments().FirstOrDefault(); + + if (targetElementType == null) + throw new InvalidOperationException("无法获取目标类型的元素类型"); + + // 检查输入对象是否为集合类型 + if (obj is IEnumerable collection) + { + // 判断目标类型是否是数组 + if (targetType.IsArray) + { + var toArrayMethod = typeof(Enumerable).GetMethod("ToArray").MakeGenericMethod(targetElementType); + return toArrayMethod.Invoke(null, new object[] { collection }); + } + // 判断目标类型是否是 List + else if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(List<>)) + { + var toListMethod = typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(targetElementType); + return toListMethod.Invoke(null, new object[] { collection }); + } + // 判断目标类型是否是 HashSet + else if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(HashSet<>)) + { + var toHashSetMethod = typeof(Enumerable).GetMethod("ToHashSet").MakeGenericMethod(targetElementType); + return toHashSetMethod.Invoke(null, new object[] { collection }); + } + // 其他类型可以扩展类似的处理 + } + + throw new InvalidOperationException("输入对象不是集合或目标类型不支持"); + } + + + } +} diff --git a/Library/Utils/SereinIoc.cs b/Library/Utils/SereinIoc.cs index 1be8414..6650d89 100644 --- a/Library/Utils/SereinIoc.cs +++ b/Library/Utils/SereinIoc.cs @@ -88,10 +88,8 @@ namespace Serein.Library.Utils { var constructor = type.GetConstructors().First(); // 获取第一个构造函数 var parameters = constructor.GetParameters(); // 获取参数列表 - var parameterValues = parameters.Select(param => ResolveDependency(param.ParameterType)).ToArray(); - var instance = Activator.CreateInstance(type, parameterValues); - - //var instance =CreateInstance(controllerType, parameters); // CreateInstance(controllerType, parameters); // 创建目标类型的实例 + var parameterValues = parameters.Select(param => ResolveDependency(param.ParameterType)).ToArray(); // 生成创建类型的入参参数 + var instance = Activator.CreateInstance(type, parameterValues); // 创建实例 if (instance != null) { InjectDependencies(instance, false); // 完成创建后注入实例需要的特性依赖项 diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index 9dea87b..9309e25 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -7,6 +7,7 @@ using Serein.Library.Utils.SereinExpression; using Serein.NodeFlow.Model; using Serein.NodeFlow.Tool; using System.Collections.Concurrent; +using System.Numerics; using System.Reflection; using System.Xml.Linq; using static Serein.Library.Utils.ChannelFlowInterrupt; @@ -409,6 +410,24 @@ namespace Serein.NodeFlow.Env } } + /// + /// 单独运行一个节点 + /// + /// + /// + /// + public async Task InvokeNodeAsync(string nodeGuid) + { + if(this.NodeModels.TryGetValue(nodeGuid, out var model)) + { + return await model.ExecutingAsync(null); + } + else + { + return null; + } + } + /// /// 退出 /// @@ -633,23 +652,23 @@ namespace Serein.NodeFlow.Env } - List<(ConnectionType connectionType, string[] guids)> allToNodes = [(ConnectionType.IsSucceed,nodeInfo.TrueNodes), - (ConnectionType.IsFail, nodeInfo.FalseNodes), - (ConnectionType.IsError, nodeInfo.ErrorNodes), - (ConnectionType.Upstream, nodeInfo.UpstreamNodes)]; + List<(ConnectionInvokeType connectionType, string[] guids)> allToNodes = [(ConnectionInvokeType.IsSucceed,nodeInfo.TrueNodes), + (ConnectionInvokeType.IsFail, nodeInfo.FalseNodes), + (ConnectionInvokeType.IsError, nodeInfo.ErrorNodes), + (ConnectionInvokeType.Upstream, nodeInfo.UpstreamNodes)]; - List<(ConnectionType, NodeModelBase[])> fromNodes = allToNodes.Where(info => info.guids.Length > 0) + 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 ((ConnectionType connectionType, NodeModelBase[] toNodes) item in fromNodes) + foreach ((ConnectionInvokeType connectionType, NodeModelBase[] toNodes) item in fromNodes) { // 遍历当前类型分支的节点(确认连接关系) foreach (var toNode in item.toNodes) { - ConnectNodeAsync(fromNode, toNode, item.connectionType); // 加载时确定节点间的连接关系 + _ = ConnectInvokeOfNode(fromNode, toNode, item.connectionType); // 加载时确定节点间的连接关系 } } } @@ -859,9 +878,11 @@ namespace Serein.NodeFlow.Env NodeModelBase? pNode = pnc.Value[i]; pNode.SuccessorNodes[pCType].Remove(remoteNode); - UIContextOperation?.Invoke(() => OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(pNode.Guid, + UIContextOperation?.Invoke(() => OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs( + pNode.Guid, remoteNode.Guid, - pCType, + JunctionOfConnectionType.Invoke, + pCType, // 对应的连接关系 NodeConnectChangeEventArgs.ConnectChangeType.Remote))); // 通知UI } @@ -894,15 +915,63 @@ namespace Serein.NodeFlow.Env /// 起始节点控制点 /// 目标节点控制点 /// 连接关系 - public async Task ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, JunctionType toNodeJunctionType, ConnectionType connectionType) + public async Task ConnectNodeAsync(string fromNodeGuid, + string toNodeGuid, + JunctionType fromNodeJunctionType, + JunctionType toNodeJunctionType, + ConnectionInvokeType connectionType, + int argIndex) { + // 获取起始节点与目标节点 var fromNode = GuidToModel(fromNodeGuid); var toNode = GuidToModel(toNodeGuid); if (fromNode is null || toNode is null) return false; + (var type,var state) = CheckConnect(fromNode, toNode, fromNodeJunctionType, toNodeJunctionType); + if (!state) + { + Console.WriteLine("出现非预期的连接行为"); + return false; // 出现不符预期的连接行为,忽略此次连接行为 + } - // 开始连接 - return await ConnectNodeAsync(fromNode, toNode, connectionType); // 外部调用连接方法 + + if(type == JunctionOfConnectionType.Invoke) + { + if (fromNodeJunctionType == JunctionType.Execute) + { + // 如果 起始控制点 是“方法调用”,需要反转 from to 节点 + (fromNode, toNode) = (toNode, fromNode); + } + // 从起始节点“下一个方法”控制点,连接到目标节点“方法调用”控制点 + state = ConnectInvokeOfNode(fromNode, toNode, connectionType); // 本地环境进行连接 + } + else if (type == JunctionOfConnectionType.Arg) + { + ConnectionArgSourceType connectionArgSourceType; + + if (fromNode.Guid.Equals(toNode.Guid)) + { + connectionArgSourceType = ConnectionArgSourceType.GetPreviousNodeData; + } + else + { + connectionArgSourceType = ConnectionArgSourceType.GetOtherNodeData; + } + + // (连接自身的情况下)从上一个节点“返回值”控制点,连接到目标节点“方法入参”控制点 + // 从起始节点“返回值”控制点,连接到目标节点“方法入参”控制点 + if (fromNodeJunctionType == JunctionType.ArgData) + { + // 如果 起始控制点 是“方法入参”,需要反转 from to 节点 + (fromNode, toNode) = (toNode, fromNode); + } + + + // 确定方法入参关系 + state = ConnectGerResultOfNode(fromNode, toNode, connectionArgSourceType, argIndex); // 本地环境进行连接 + } + + return state; } @@ -913,7 +982,7 @@ namespace Serein.NodeFlow.Env /// 目标节点Guid /// 连接关系 /// - public async Task RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) + public async Task RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) { // 获取起始节点与目标节点 var fromNode = GuidToModel(fromNodeGuid); @@ -923,8 +992,6 @@ namespace Serein.NodeFlow.Env return result; } - - /// /// 获取方法描述 /// @@ -1245,6 +1312,9 @@ namespace Serein.NodeFlow.Env #region 私有方法 + + + /// /// 加载指定路径的DLL文件 /// @@ -1290,7 +1360,7 @@ namespace Serein.NodeFlow.Env /// 目标节点Model /// 连接关系 /// - private async Task RemoteConnectAsync(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType) + private async Task RemoteConnectAsync(NodeModelBase fromNode, NodeModelBase toNode, ConnectionInvokeType connectionType) { fromNode.SuccessorNodes[connectionType].Remove(toNode); toNode.PreviousNodes[connectionType].Remove(fromNode); @@ -1298,8 +1368,10 @@ namespace Serein.NodeFlow.Env if (OperatingSystem.IsWindows()) { - await UIContextOperation.InvokeAsync(() => OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, + await UIContextOperation.InvokeAsync(() => OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs( + fromNode.Guid, toNode.Guid, + JunctionOfConnectionType.Invoke, connectionType, NodeConnectChangeEventArgs.ConnectChangeType.Remote))); } @@ -1431,13 +1503,85 @@ namespace Serein.NodeFlow.Env return true; } + + + + + /// + /// 检查连接 + /// + /// 发起连接的起始节点 + /// 要连接的目标节点 + /// 发起连接节点的控制点类型 + /// 被连接节点的控制点类型 + /// + public static (JunctionOfConnectionType,bool) CheckConnect(NodeModelBase fromNode, + NodeModelBase toNode, + JunctionType fromNodeJunctionType, + JunctionType toNodeJunctionType) + { + var type = JunctionOfConnectionType.None; + var state = false; + if (fromNodeJunctionType == JunctionType.Execute) + { + if (toNodeJunctionType == JunctionType.NextStep && !fromNode.Guid.Equals(toNode.Guid)) + { + // “方法执行”控制点拖拽到“下一节点”控制点,且不是同一个节点, 添加方法执行关系 + type = JunctionOfConnectionType.Invoke; + state = true; + } + else if (toNodeJunctionType == JunctionType.ArgData && fromNode.Guid.Equals(toNode.Guid)) + { + // “方法执行”控制点拖拽到“方法入参”控制点,且是同一个节点,则添加获取参数关系,表示生成入参参数时自动从该节点的上一节点获取flowdata + type = JunctionOfConnectionType.Arg; + state = true; + } + } + else if (fromNodeJunctionType == JunctionType.NextStep && !fromNode.Guid.Equals(toNode.Guid)) + { + // “下一节点”控制点只能拖拽到“方法执行”控制点,且不能是同一个节点 + if (toNodeJunctionType == JunctionType.Execute && !fromNode.Guid.Equals(toNode.Guid)) + { + type = JunctionOfConnectionType.Invoke; + state = true; + } + } + else if (fromNodeJunctionType == JunctionType.ArgData) + { + if (toNodeJunctionType == JunctionType.Execute && fromNode.Guid.Equals(toNode.Guid)) // 添加获取参数关系 + { + // “方法入参”控制点拖拽到“方法执行”控制点,且是同一个节点,则添加获取参数关系,生成入参参数时自动从该节点的上一节点获取flowdata + type = JunctionOfConnectionType.Arg; + state = true; + } + if(toNodeJunctionType == JunctionType.ReturnData && !fromNode.Guid.Equals(toNode.Guid)) + { + // “”控制点拖拽到“方法返回值”控制点,且不是同一个节点,添加获取参数关系,生成参数时从目标节点获取flowdata + type = JunctionOfConnectionType.Arg; + state = true; + } + } + else if (fromNodeJunctionType == JunctionType.ReturnData) + { + if (toNodeJunctionType == JunctionType.ArgData && !fromNode.Guid.Equals(toNode.Guid)) + { + // “方法返回值”控制点拖拽到“方法入参”控制点,且不是同一个节点,添加获取参数关系,生成参数时从目标节点获取flowdata + type = JunctionOfConnectionType.Arg; + state = true; + } + } + // 剩下的情况都是不符预期的连接行为,忽略。 + return (type,state); + } + + /// /// 连接节点 /// /// 起始节点 /// 目标节点 /// 连接关系 - private async Task ConnectNodeAsync(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType) + private bool ConnectInvokeOfNode(NodeModelBase fromNode, NodeModelBase toNode, ConnectionInvokeType connectionType) { if (fromNode is null || toNode is null || fromNode == toNode) { @@ -1446,10 +1590,10 @@ namespace Serein.NodeFlow.Env var ToExistOnFrom = true; var FromExistInTo = true; - ConnectionType[] ct = [ConnectionType.IsSucceed, - ConnectionType.IsFail, - ConnectionType.IsError, - ConnectionType.Upstream]; + ConnectionInvokeType[] ct = [ConnectionInvokeType.IsSucceed, + ConnectionInvokeType.IsFail, + ConnectionInvokeType.IsError, + ConnectionInvokeType.Upstream]; if (toNode is SingleFlipflopNode flipflopNode) { @@ -1457,7 +1601,7 @@ namespace Serein.NodeFlow.Env } var isPass = false; - foreach (ConnectionType ctType in ct) + 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(); @@ -1494,12 +1638,18 @@ namespace Serein.NodeFlow.Env toNode.PreviousNodes[connectionType].Add(fromNode); // 添加到目标节点的父分支 if (OperatingSystem.IsWindows()) { - UIContextOperation?.Invoke(() => OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, - toNode.Guid, - connectionType, - NodeConnectChangeEventArgs.ConnectChangeType.Create))); // 通知UI + UIContextOperation?.Invoke(() => + OnNodeConnectChange?.Invoke( + new NodeConnectChangeEventArgs( + fromNode.Guid, // 从哪个节点开始 + toNode.Guid, // 连接到那个节点 + JunctionOfConnectionType.Invoke, + connectionType, // 连接线的样式类型 + NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 + ))); // 通知UI } - + // Invoke + // GetResult return true; } else @@ -1510,6 +1660,30 @@ namespace Serein.NodeFlow.Env } + /// + /// 连接节点参数 + /// + /// + /// + /// + /// + /// + private bool ConnectGerResultOfNode(NodeModelBase fromNode, NodeModelBase toNode, ConnectionArgSourceType connectionArgSourceType,int argIndex) + { + UIContextOperation?.Invoke(() => + OnNodeConnectChange?.Invoke( + new NodeConnectChangeEventArgs( + fromNode.Guid, // 从哪个节点开始 + toNode.Guid, // 连接到那个节点 + JunctionOfConnectionType.Arg, + (int)argIndex, // 连接线的样式类型 + connectionArgSourceType, + NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 + ))); // 通知UI + return false; + } + + /// /// 更改起点节点 /// diff --git a/NodeFlow/Env/FlowEnvironmentDecorator.cs b/NodeFlow/Env/FlowEnvironmentDecorator.cs index bf18881..b8577d9 100644 --- a/NodeFlow/Env/FlowEnvironmentDecorator.cs +++ b/NodeFlow/Env/FlowEnvironmentDecorator.cs @@ -180,9 +180,14 @@ namespace Serein.NodeFlow.Env currentFlowEnvironment.ClearAll(); } - public async Task ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, JunctionType toNodeJunctionType, ConnectionType connectionType) + public async Task ConnectNodeAsync(string fromNodeGuid, + string toNodeGuid, + JunctionType fromNodeJunctionType, + JunctionType toNodeJunctionType, + ConnectionInvokeType connectionType, + int argIndex) { - return await currentFlowEnvironment.ConnectNodeAsync(fromNodeGuid, toNodeGuid, fromNodeJunctionType, toNodeJunctionType, connectionType); + return await currentFlowEnvironment.ConnectNodeAsync(fromNodeGuid, toNodeGuid, fromNodeJunctionType, toNodeJunctionType, connectionType, argIndex); } public async Task<(bool, RemoteEnvControl)> ConnectRemoteEnv(string addres, int port, string token) @@ -265,13 +270,12 @@ namespace Serein.NodeFlow.Env } - public bool RemoteDll(string assemblyFullName) { return currentFlowEnvironment.RemoteDll(assemblyFullName); } - public async Task RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) + public async Task RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) { return await currentFlowEnvironment.RemoveConnectAsync(fromNodeGuid, toNodeGuid, connectionType); } @@ -282,7 +286,6 @@ namespace Serein.NodeFlow.Env } - public void SetConsoleOut() { currentFlowEnvironment.SetConsoleOut(); @@ -313,6 +316,11 @@ namespace Serein.NodeFlow.Env await currentFlowEnvironment.StartAsyncInSelectNode(startNodeGuid); } + public async Task InvokeNodeAsync(string nodeGuid) + { + return await currentFlowEnvironment.InvokeNodeAsync(nodeGuid); + } + public async Task StartRemoteServerAsync(int port = 7525) { await currentFlowEnvironment.StartRemoteServerAsync(port); diff --git a/NodeFlow/Env/FlowFunc.cs b/NodeFlow/Env/FlowFunc.cs index 6173f04..b8423d2 100644 --- a/NodeFlow/Env/FlowFunc.cs +++ b/NodeFlow/Env/FlowFunc.cs @@ -107,14 +107,14 @@ namespace Serein.NodeFlow.Env /// /// /// - public static ConnectionType ToContentType(this FlipflopStateType flowStateType) + public static ConnectionInvokeType ToContentType(this FlipflopStateType flowStateType) { return flowStateType switch { - FlipflopStateType.Succeed => ConnectionType.IsSucceed, - FlipflopStateType.Fail => ConnectionType.IsFail, - FlipflopStateType.Error => ConnectionType.IsError, - FlipflopStateType.Cancel => ConnectionType.None, + FlipflopStateType.Succeed => ConnectionInvokeType.IsSucceed, + FlipflopStateType.Fail => ConnectionInvokeType.IsFail, + FlipflopStateType.Error => ConnectionInvokeType.IsError, + FlipflopStateType.Cancel => ConnectionInvokeType.None, _ => throw new NotImplementedException("未定义的流程状态") }; } @@ -126,11 +126,11 @@ namespace Serein.NodeFlow.Env /// public static bool NotExitPreviousNode(this SingleFlipflopNode node) { - ConnectionType[] ct = [ConnectionType.IsSucceed, - ConnectionType.IsFail, - ConnectionType.IsError, - ConnectionType.Upstream]; - foreach (ConnectionType ctType in ct) + ConnectionInvokeType[] ct = [ConnectionInvokeType.IsSucceed, + ConnectionInvokeType.IsFail, + ConnectionInvokeType.IsError, + ConnectionInvokeType.Upstream]; + foreach (ConnectionInvokeType ctType in ct) { if (node.PreviousNodes[ctType].Count > 0) { diff --git a/NodeFlow/Env/MsgControllerOfServer.cs b/NodeFlow/Env/MsgControllerOfServer.cs index 5597b8b..a5ee4fc 100644 --- a/NodeFlow/Env/MsgControllerOfServer.cs +++ b/NodeFlow/Env/MsgControllerOfServer.cs @@ -340,7 +340,7 @@ namespace Serein.NodeFlow.Env [AutoSocketHandle(ThemeValue = EnvMsgTheme.ConnectNode)] public async Task ConnectNode(string fromNodeGuid, string toNodeGuid, string connectionType) { - if (!EnumHelper.TryConvertEnum(connectionType, out var tmpConnectionType)) + if (!EnumHelper.TryConvertEnum(connectionType, out var tmpConnectionType)) { return new { @@ -348,7 +348,7 @@ namespace Serein.NodeFlow.Env }; } //environment.ConnectNodeAsync(fromNodeGuid, toNodeGuid, tmpConnectionType); - var result = await environment.ConnectNodeAsync(fromNodeGuid, toNodeGuid, tmpConnectionType); + var result = await environment.ConnectNodeAsync(fromNodeGuid, toNodeGuid,0,0, tmpConnectionType,0); return new { state = result @@ -365,7 +365,7 @@ namespace Serein.NodeFlow.Env [AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveConnect)] public async Task RemoveConnect(string fromNodeGuid, string toNodeGuid, string connectionType) { - if (!EnumHelper.TryConvertEnum(connectionType, out var tmpConnectionType)) + if (!EnumHelper.TryConvertEnum(connectionType, out var tmpConnectionType)) { return new { diff --git a/NodeFlow/Env/RemoteFlowEnvironment.cs b/NodeFlow/Env/RemoteFlowEnvironment.cs index bdfb37e..a7e075d 100644 --- a/NodeFlow/Env/RemoteFlowEnvironment.cs +++ b/NodeFlow/Env/RemoteFlowEnvironment.cs @@ -218,18 +218,18 @@ namespace Serein.NodeFlow.Env } - List<(ConnectionType connectionType, string[] guids)> allToNodes = [(ConnectionType.IsSucceed,nodeInfo.TrueNodes), - (ConnectionType.IsFail, nodeInfo.FalseNodes), - (ConnectionType.IsError, nodeInfo.ErrorNodes), - (ConnectionType.Upstream, nodeInfo.UpstreamNodes)]; + List<(ConnectionInvokeType connectionType, string[] guids)> allToNodes = [(ConnectionInvokeType.IsSucceed,nodeInfo.TrueNodes), + (ConnectionInvokeType.IsFail, nodeInfo.FalseNodes), + (ConnectionInvokeType.IsError, nodeInfo.ErrorNodes), + (ConnectionInvokeType.Upstream, nodeInfo.UpstreamNodes)]; - List<(ConnectionType, NodeModelBase[])> fromNodes = allToNodes.Where(info => info.guids.Length > 0) + 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 ((ConnectionType connectionType, NodeModelBase[] toNodes) item in fromNodes) + foreach ((ConnectionInvokeType connectionType, NodeModelBase[] toNodes) item in fromNodes) { // 遍历当前类型分支的节点(确认连接关系) foreach (var toNode in item.toNodes) @@ -237,6 +237,7 @@ namespace Serein.NodeFlow.Env UIContextOperation?.Invoke(() => OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, toNode.Guid, + JunctionOfConnectionType.Invoke, item.connectionType, NodeConnectChangeEventArgs.ConnectChangeType.Create))); // 通知UI连接节点 //OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, @@ -273,7 +274,7 @@ namespace Serein.NodeFlow.Env return true; } - private void ConnectNode(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType) + private void ConnectNode(NodeModelBase fromNode, NodeModelBase toNode, ConnectionInvokeType connectionType) { if (fromNode is null || toNode is null || fromNode == toNode) { @@ -282,13 +283,13 @@ namespace Serein.NodeFlow.Env var ToExistOnFrom = true; var FromExistInTo = true; - ConnectionType[] ct = [ConnectionType.IsSucceed, - ConnectionType.IsFail, - ConnectionType.IsError, - ConnectionType.Upstream]; + ConnectionInvokeType[] ct = [ConnectionInvokeType.IsSucceed, + ConnectionInvokeType.IsFail, + ConnectionInvokeType.IsError, + ConnectionInvokeType.Upstream]; - foreach (ConnectionType ctType in ct) + 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(); @@ -325,6 +326,7 @@ namespace Serein.NodeFlow.Env toNode.PreviousNodes[connectionType].Add(fromNode); // 添加到目标节点的父分支 OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, toNode.Guid, + JunctionOfConnectionType.Invoke, connectionType, NodeConnectChangeEventArgs.ConnectChangeType.Create)); // 通知UI } @@ -424,7 +426,22 @@ namespace Serein.NodeFlow.Env //UIContextOperation?.Invoke(() => OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(nodeGuid,nodeGuid))); } - public async Task ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, JunctionType toNodeJunctionType, ConnectionType connectionType) + public async Task InvokeNodeAsync(string nodeGuid) + { + Console.WriteLine("远程环境尚未实现接口 InvokeNodeAsync"); + _ = msgClient.SendAsync(EnvMsgTheme.SetStartNode, new + { + nodeGuid + }); + return null; + } + + public async Task ConnectNodeAsync(string fromNodeGuid, + string toNodeGuid, + JunctionType fromNodeJunctionType, + JunctionType toNodeJunctionType, + ConnectionInvokeType connectionType, + int argIndex = 0) { var result = await msgClient.SendAndWaitDataAsync(EnvMsgTheme.ConnectNode, new { @@ -438,6 +455,7 @@ namespace Serein.NodeFlow.Env { OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNodeGuid, toNodeGuid, + JunctionOfConnectionType.Invoke, connectionType, NodeConnectChangeEventArgs.ConnectChangeType.Create)); // 通知UI } @@ -472,7 +490,7 @@ namespace Serein.NodeFlow.Env return nodeInfo; } - public async Task RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) + public async Task RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) { var result = await msgClient.SendAndWaitDataAsync(EnvMsgTheme.RemoveConnect, new { @@ -486,6 +504,7 @@ namespace Serein.NodeFlow.Env { OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNodeGuid, toNodeGuid, + JunctionOfConnectionType.Invoke, connectionType, NodeConnectChangeEventArgs.ConnectChangeType.Remote)); }); diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index c784236..81418a7 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -351,9 +351,9 @@ namespace Serein.NodeFlow { var newFlowData = await singleFlipFlopNode.ExecutingAsync(context); // 获取触发器等待Task await NodeModelBase.RefreshFlowDataAndExpInterrupt(context, singleFlipFlopNode, newFlowData); // 全局触发器触发后刷新该触发器的节点数据 - if (singleFlipFlopNode.NextOrientation != ConnectionType.None) + if (context.NextOrientation != ConnectionInvokeType.None) { - var nextNodes = singleFlipFlopNode.SuccessorNodes[singleFlipFlopNode.NextOrientation]; + var nextNodes = singleFlipFlopNode.SuccessorNodes[context.NextOrientation]; for (int i = nextNodes.Count - 1; i >= 0 && !_flipFlopCts.IsCancellationRequested; i--) { // 筛选出启用的节点 diff --git a/NodeFlow/Model/CompositeActionNode.cs b/NodeFlow/Model/CompositeActionNode.cs index 369e911..1801537 100644 --- a/NodeFlow/Model/CompositeActionNode.cs +++ b/NodeFlow/Model/CompositeActionNode.cs @@ -35,10 +35,10 @@ namespace Serein.NodeFlow.Model { if (MethodDetails is null) return null; - var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支 - var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支 - var errorNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 异常分支 - var upstreamNodes = SuccessorNodes[ConnectionType.Upstream].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(); diff --git a/NodeFlow/Model/CompositeConditionNode.cs b/NodeFlow/Model/CompositeConditionNode.cs index 38cdb85..8d56f52 100644 --- a/NodeFlow/Model/CompositeConditionNode.cs +++ b/NodeFlow/Model/CompositeConditionNode.cs @@ -51,8 +51,8 @@ namespace Serein.NodeFlow.Model foreach (SingleConditionNode? node in ConditionNodes) { var state = await JudgeAsync(context, node); - NextOrientation = state; // 每次判读完成后,设置区域后继方向为判断结果 - if (state != ConnectionType.IsSucceed) + context.NextOrientation = state; // 每次判读完成后,设置区域后继方向为判断结果 + if (state != ConnectionInvokeType.IsSucceed) { // 如果条件不通过,立刻推出循环 break; @@ -62,19 +62,19 @@ namespace Serein.NodeFlow.Model } - private async Task JudgeAsync(IDynamicContext context, SingleConditionNode node) + private async Task JudgeAsync(IDynamicContext context, SingleConditionNode node) { try { await node.ExecutingAsync(context); - return node.NextOrientation; + return context.NextOrientation; } catch (Exception ex) { Console.WriteLine(ex.Message); - NextOrientation = ConnectionType.IsError; + context.NextOrientation = ConnectionInvokeType.IsError; RuningException = ex; - return ConnectionType.IsError; + return ConnectionInvokeType.IsError; } } @@ -91,10 +91,10 @@ namespace Serein.NodeFlow.Model //var falseNodes = FailBranch.Select(item => item.Guid);// 假分支 //var upstreamNodes = UpstreamBranch.Select(item => item.Guid);// 上游分支 //var errorNodes = ErrorBranch.Select(item => item.Guid);// 异常分支 - var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支 - var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支 - var errorNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 异常分支 - var upstreamNodes = SuccessorNodes[ConnectionType.Upstream].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(); diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs index c9b9738..bc53ae5 100644 --- a/NodeFlow/Model/SingleConditionNode.cs +++ b/NodeFlow/Model/SingleConditionNode.cs @@ -76,15 +76,15 @@ namespace Serein.NodeFlow.Model { var isPass = SereinConditionParser.To(parameter, Expression); - NextOrientation = isPass ? ConnectionType.IsSucceed : ConnectionType.IsFail; + context.NextOrientation = isPass ? ConnectionInvokeType.IsSucceed : ConnectionInvokeType.IsFail; } catch (Exception ex) { - NextOrientation = ConnectionType.IsError; + context.NextOrientation = ConnectionInvokeType.IsError; RuningException = ex; } - Console.WriteLine($"{result} {Expression} -> " + NextOrientation); + Console.WriteLine($"{result} {Expression} -> " + context.NextOrientation); return Task.FromResult(result); } diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs index 0af3db8..5eaa994 100644 --- a/NodeFlow/Model/SingleExpOpNode.cs +++ b/NodeFlow/Model/SingleExpOpNode.cs @@ -47,12 +47,12 @@ namespace Serein.NodeFlow.Model result = data; } - NextOrientation = ConnectionType.IsSucceed; + context.NextOrientation = ConnectionInvokeType.IsSucceed; return Task.FromResult(result); } catch (Exception ex) { - NextOrientation = ConnectionType.IsError; + context.NextOrientation = ConnectionInvokeType.IsError; RuningException = ex; return Task.FromResult(data); } diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs index 74465ec..c32858b 100644 --- a/NodeFlow/Model/SingleFlipflopNode.cs +++ b/NodeFlow/Model/SingleFlipflopNode.cs @@ -42,11 +42,11 @@ namespace Serein.NodeFlow.Model object instance = md.ActingInstance; try { - var args = GetParameters(context, this, md); + var args = await GetParametersAsync(context, this, md); var result = await dd.InvokeAsync(md.ActingInstance, args); dynamic flipflopContext = result; FlipflopStateType flipflopStateType = flipflopContext.State; - NextOrientation = flipflopStateType.ToContentType(); + context.NextOrientation = flipflopStateType.ToContentType(); if (flipflopContext.Type == TriggerType.Overtime) { throw new FlipflopException(base.MethodDetails.MethodName + "触发器超时触发。Guid" + base.Guid); @@ -61,14 +61,14 @@ namespace Serein.NodeFlow.Model throw; } await Console.Out.WriteLineAsync($"触发器[{this.MethodDetails.MethodName}]异常:" + ex); - NextOrientation = ConnectionType.None; + context.NextOrientation = ConnectionInvokeType.None; RuningException = ex; return null; } catch (Exception ex) { await Console.Out.WriteLineAsync($"触发器[{this.MethodDetails.MethodName}]异常:" + ex); - NextOrientation = ConnectionType.IsError; + context.NextOrientation = ConnectionInvokeType.IsError; RuningException = ex; return null; } diff --git a/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs b/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs index 677e111..7f06ab8 100644 --- a/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs +++ b/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs @@ -118,6 +118,7 @@ namespace Serein.Library.NodeGenerator // 生成命名空间和类的开始部分 sb.AppendLine($"using System;"); + sb.AppendLine($"using System.Linq;"); sb.AppendLine($"using System.Threading;"); sb.AppendLine($"using System.Threading.Tasks;"); sb.AppendLine($"using System.Collections.Concurrent;"); diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index 2b3a9d2..2ee9010 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -5,9 +5,11 @@ using Serein.Library.Api; using Serein.Library.Utils; using Serein.Library.Utils.SereinExpression; using Serein.NodeFlow.Tool; +using Serein.Workbench.Node; using Serein.Workbench.Node.View; using Serein.Workbench.Node.ViewModel; using Serein.Workbench.Themes; +using System; using System.IO; using System.Windows; using System.Windows.Controls; @@ -118,11 +120,11 @@ namespace Serein.Workbench /// /// 当前正在绘制的连接线 /// - private Line? currentLine; + //private Line? currentLine; /// /// 当前正在绘制的真假分支属性 /// - private ConnectionType currentConnectionType; + private ConnectionInvokeType currentConnectionType; /// @@ -229,7 +231,6 @@ namespace Serein.Workbench - #region 窗体加载方法 private void Window_Loaded(object sender, RoutedEventArgs e) { @@ -250,7 +251,7 @@ namespace Serein.Workbench InitializeCanvas(project.Basic.Canvas.Width, project.Basic.Canvas.Height);// 设置画布大小 foreach (var connection in Connections) { - connection.AddOrRefreshLine(); // 窗体完成加载后试图刷新所有连接线 + connection.RefreshLine(); // 窗体完成加载后试图刷新所有连接线 } var canvasData = project.Basic.Canvas; @@ -358,44 +359,145 @@ namespace Serein.Workbench { string fromNodeGuid = eventArgs.FromNodeGuid; string toNodeGuid = eventArgs.ToNodeGuid; - if (!TryGetControl(fromNodeGuid, out var fromNode) - || !TryGetControl(toNodeGuid, out var toNode)) + if (!TryGetControl(fromNodeGuid, out var fromNodeControl) + || !TryGetControl(toNodeGuid, out var toNodeControl)) { return; } + + - ConnectionType connectionType = eventArgs.ConnectionType; - if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create) // 添加连接 + + if (eventArgs.JunctionOfConnectionType == JunctionOfConnectionType.Invoke) { - // 添加连接 - var connection = new ConnectionControl(EnvDecorator, FlowChartCanvas, connectionType, fromNode, toNode); - - if (toNode is FlipflopNodeControl flipflopControl - && flipflopControl?.ViewModel?.NodeModel is NodeModelBase nodeModel) // 某个节点连接到了触发器,尝试从全局触发器视图中移除该触发器 + #region 创建/删除节点之间的调用关系 + ConnectionInvokeType connectionType = eventArgs.ConnectionInvokeType; + if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create) // 添加连接 { - NodeTreeViewer.RemoteGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除 + if (fromNodeControl is not INodeJunction IFormJunction || toNodeControl is not INodeJunction IToJunction) + { + Console.WriteLine("非预期的情况"); + return; + } + JunctionControlBase startJunction = IFormJunction.NextStepJunction; + JunctionControlBase endJunction = IToJunction.ExecuteJunction; + + // 添加连接 + var connection = new ConnectionControl( + FlowChartCanvas, + connectionType, + startJunction, + endJunction, + () => EnvDecorator.RemoveConnectAsync(fromNodeGuid, toNodeGuid, connectionType) + ); + + if (toNodeControl is FlipflopNodeControl flipflopControl + && flipflopControl?.ViewModel?.NodeModel is NodeModelBase nodeModel) // 某个节点连接到了触发器,尝试从全局触发器视图中移除该触发器 + { + NodeTreeViewer.RemoteGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除 + } + connection.RefreshLine(); // 添加贝塞尔曲线显示 + Connections.Add(connection); + fromNodeControl.AddCnnection(connection); + toNodeControl.AddCnnection(connection); + EndConnection(); + + } - connection.InvalidateVisual(); // 添加贝塞尔曲线显示 - - Connections.Add(connection); - EndConnection(); + else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remote) // 移除连接 + { + // 需要移除连接 + var removeConnections = Connections.Where(c => c.Start.MyNode.Guid.Equals(fromNodeGuid) + && c.End.MyNode.Guid.Equals(toNodeGuid)) + .ToList(); + foreach (var connection in removeConnections) + { + connection.DeleteConnection(); + Connections.Remove(connection); + fromNodeControl.RemoveCnnection(connection); + toNodeControl.RemoveCnnection(connection); + if(NodeControls.TryGetValue(connection.End.MyNode.Guid, out var control)) + { + JudgmentFlipFlopNode(control); // 连接关系变更时判断 + } + } + } + #endregion } - else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remote) // 移除连接 + else { - // 需要移除连接 - var removeConnections = Connections.Where(c => c.Start.ViewModel.NodeModel.Guid.Equals(fromNodeGuid) - && c.End.ViewModel.NodeModel.Guid.Equals(toNodeGuid)) - .ToList(); - - - foreach (var connection in removeConnections) + #region 创建/删除节点之间的参数传递关系 + ConnectionArgSourceType connectionArgSourceType = eventArgs.ConnectionArgSourceType; + if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create) // 添加连接 { - connection.RemoveFromCanvas(); - Connections.Remove(connection); - JudgmentFlipFlopNode(connection.End); // 连接关系变更时判断 + if (fromNodeControl is not INodeJunction IFormJunction || toNodeControl is not INodeJunction IToJunction) + { + Console.WriteLine("非预期的情况"); + return; + } + + JunctionControlBase startJunction = eventArgs.ConnectionArgSourceType switch + { + ConnectionArgSourceType.GetPreviousNodeData => IFormJunction.ExecuteJunction, // 自身节点 + ConnectionArgSourceType.GetOtherNodeData => IFormJunction.ReturnDataJunction, // 其它节点的返回值控制点 + ConnectionArgSourceType.GetOtherNodeDataOfInvoke => IFormJunction.ReturnDataJunction, // 其它节点的返回值控制点 + _ => throw new Exception("窗体事件 FlowEnvironment_NodeConnectChangeEvemt 创建/删除节点之间的参数传递关系 JunctionControlBase 枚举值错误 。非预期的枚举值。") // 应该不会触发 + }; + + JunctionControlBase endJunction = IToJunction.ArgDataJunction[eventArgs.ArgIndex]; + LineType lineType = LineType.Bezier; + if(eventArgs.ConnectionArgSourceType == ConnectionArgSourceType.GetPreviousNodeData) + { + lineType = LineType.Semicircle; + } + + // 添加连接 + var connection = new ConnectionControl( + lineType, + FlowChartCanvas, + eventArgs.ArgIndex, + eventArgs.ConnectionArgSourceType, + startJunction, + endJunction, + () => EnvDecorator.RemoveConnectAsync(fromNodeGuid, toNodeGuid, 0) + ); + + if (toNodeControl is FlipflopNodeControl flipflopControl + && flipflopControl?.ViewModel?.NodeModel is NodeModelBase nodeModel) // 某个节点连接到了触发器,尝试从全局触发器视图中移除该触发器 + { + NodeTreeViewer.RemoteGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除 + } + connection.RefreshLine(); // 添加贝塞尔曲线显示 + Connections.Add(connection); + fromNodeControl.AddCnnection(connection); + toNodeControl.AddCnnection(connection); + EndConnection(); + + } + else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remote) // 移除连接 + { + // 需要移除连接 + var removeConnections = Connections.Where(c => c.Start.MyNode.Guid.Equals(fromNodeGuid) + && c.End.MyNode.Guid.Equals(toNodeGuid)) + .ToList(); + + + foreach (var connection in removeConnections) + { + connection.DeleteConnection(); + Connections.Remove(connection); + fromNodeControl.RemoveCnnection(connection); + toNodeControl.RemoveCnnection(connection); + if (NodeControls.TryGetValue(connection.End.MyNode.Guid, out var control)) + { + JudgmentFlipFlopNode(control); // 连接关系变更时判断 + } + } + } + #endregion } @@ -434,6 +536,7 @@ namespace Serein.Workbench } FlowChartCanvas.Children.Remove(nodeControl); + nodeControl.RemoveAllConection(); NodeControls.Remove(nodeControl.ViewModel.NodeModel.Guid); } @@ -724,7 +827,7 @@ namespace Serein.Workbench private void FlowEnvironment_OnNodeMoved(NodeMovedEventArgs eventArgs) { if (!TryGetControl(eventArgs.NodeGuid, out var nodeControl)) return; - UpdateConnections(nodeControl); + nodeControl.UpdateLocationConnections(); //var newLeft = eventArgs.X; //var newTop = eventArgs.Y; @@ -859,35 +962,34 @@ namespace Serein.Workbench } - /// /// 开始创建连接 True线 操作,设置起始块和绘制连接线。 /// - private void StartConnection(NodeControlBase startNodeControl, ConnectionType connectionType) - { - var tf = Connections.FirstOrDefault(it => it.Start == startNodeControl)?.Type; - IsConnecting = true; - currentConnectionType = connectionType; - startConnectNodeControl = startNodeControl; + //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 == ConnectionType.IsSucceed ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")) - : connectionType == ConnectionType.IsFail ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")) - : connectionType == ConnectionType.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, - }; + // // 确保起点和终点位置的正确顺序 + // 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; - } + // FlowChartCanvas.Children.Add(currentLine); + // this.KeyDown += MainWindow_KeyDown; + //} #endregion @@ -963,10 +1065,10 @@ namespace Serein.Workbench contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => EnvDecorator.SetStartNode(nodeGuid))); contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => EnvDecorator.RemoveNodeAsync(nodeGuid))); - contextMenu.Items.Add(CreateMenuItem("添加 真分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsSucceed))); - contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsFail))); - contextMenu.Items.Add(CreateMenuItem("添加 异常分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsError))); - contextMenu.Items.Add(CreateMenuItem("添加 上游分支", (s, e) => StartConnection(nodeControl, ConnectionType.Upstream))); + //contextMenu.Items.Add(CreateMenuItem("添加 真分支", (s, e) => StartConnection(nodeControl, ConnectionInvokeType.IsSucceed))); + //contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionInvokeType.IsFail))); + //contextMenu.Items.Add(CreateMenuItem("添加 异常分支", (s, e) => StartConnection(nodeControl, ConnectionInvokeType.IsError))); + //contextMenu.Items.Add(CreateMenuItem("添加 上游分支", (s, e) => StartConnection(nodeControl, ConnectionInvokeType.Upstream))); @@ -1028,21 +1130,6 @@ namespace Serein.Workbench Console.WriteLine(ex); } } - - //private void DisplayFlowDataTreeViewer(object @object) - //{ - // try - // { - // var typeViewerWindow = new ViewObjectViewerWindow(); - // typeViewerWindow.LoadObjectInformation(@object); - // typeViewerWindow.Show(); - // } - // catch (Exception ex) - // { - // Console.WriteLine(ex); - // } - //} - #endregion #region 拖拽DLL文件到左侧功能区,加载相关节点清单 @@ -1090,30 +1177,34 @@ namespace Serein.Workbench private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e) { - if (e.LeftButton == MouseButtonState.Pressed && GlobalJunctionData.MyGlobalData is not null) + if (e.LeftButton == MouseButtonState.Pressed && GlobalJunctionData.MyGlobalConnectingData is not null) { // 正在连接节点 - var virtualLine = GlobalJunctionData.MyGlobalData.VirtualLine; - var controlPointPosition = GlobalJunctionData.MyGlobalData.StartPoint; + //var controlPointPosition = GlobalJunctionData.MyGlobalConnectingData.StartPoint; var currentPoint = e.GetPosition(FlowChartCanvas); - virtualLine.VirtualLine.X1 = controlPointPosition.X; - virtualLine.VirtualLine.Y1 = controlPointPosition.Y; - virtualLine.VirtualLine.X2 = currentPoint.X; - virtualLine.VirtualLine.Y2 = currentPoint.Y; + GlobalJunctionData.MyGlobalConnectingData.UpdatePoint(currentPoint); + + + //virtualLine.VirtualLine.UpdatePoints(currentPoint); + + //virtualLine.VirtualLine.X1 = controlPointPosition.X; + //virtualLine.VirtualLine.Y1 = controlPointPosition.Y; + //virtualLine.VirtualLine.X2 = currentPoint.X; + //virtualLine.VirtualLine.Y2 = currentPoint.Y; return; } - if (IsConnecting) // 正在连接节点 - { - Point position = e.GetPosition(FlowChartCanvas); - if (currentLine is null || startConnectNodeControl is null) - { - return; - } - currentLine.X1 = Canvas.GetLeft(startConnectNodeControl) + startConnectNodeControl.ActualWidth / 2; - currentLine.Y1 = Canvas.GetTop(startConnectNodeControl) + startConnectNodeControl.ActualHeight / 2; - currentLine.X2 = position.X; - currentLine.Y2 = position.Y; - } + //if (IsConnecting) // 正在连接节点 + //{ + // Point position = e.GetPosition(FlowChartCanvas); + // if (currentLine is null || startConnectNodeControl is null) + // { + // return; + // } + // currentLine.X1 = Canvas.GetLeft(startConnectNodeControl) + startConnectNodeControl.ActualWidth / 2; + // currentLine.Y1 = Canvas.GetTop(startConnectNodeControl) + startConnectNodeControl.ActualHeight / 2; + // currentLine.X2 = position.X; + // currentLine.Y2 = position.Y; + //} if (IsCanvasDragging && e.MiddleButton == MouseButtonState.Pressed) // 正在移动画布(按住中键) { @@ -1128,7 +1219,7 @@ namespace Serein.Workbench foreach (var line in Connections) { - line.AddOrRefreshLine(); // 画布移动时刷新所有连接线 + line.RefreshLine(); // 画布移动时刷新所有连接线 } } @@ -1288,10 +1379,10 @@ namespace Serein.Workbench /// private void Block_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { - if (GlobalJunctionData.IsCreatingConnection) - { - return; - } + //if (GlobalJunctionData.IsCreatingConnection) + //{ + // return; + //} if(sender is NodeControlBase nodeControl) { ChangeViewerObjOfNode(nodeControl); @@ -1302,6 +1393,7 @@ namespace Serein.Workbench e.Handled = true; // 防止事件传播影响其他控件 } } + /// /// 控件的鼠标移动事件,根据鼠标拖动更新控件的位置。批量移动计算移动逻辑。 /// @@ -1353,7 +1445,7 @@ namespace Serein.Workbench // 更新节点之间线的连接位置 foreach (var nodeControl in selectNodeControls) { - UpdateConnections(nodeControl); + nodeControl.UpdateLocationConnections(); } } else @@ -1367,7 +1459,7 @@ namespace Serein.Workbench double newLeft = Canvas.GetLeft(nodeControl) + deltaX; // 新的左边距 double newTop = Canvas.GetTop(nodeControl) + deltaY; // 新的上边距 this.EnvDecorator.MoveNode(nodeControl.ViewModel.NodeModel.Guid, newLeft, newTop); // 移动节点 - UpdateConnections(nodeControl); + nodeControl.UpdateLocationConnections(); } startControlDragPoint = currentPosition; // 更新起始点位置 } @@ -1429,18 +1521,17 @@ namespace Serein.Workbench } - if (IsConnecting) - { - var formNodeGuid = startConnectNodeControl?.ViewModel.NodeModel.Guid; - var toNodeGuid = (sender as NodeControlBase)?.ViewModel.NodeModel.Guid; - if (string.IsNullOrEmpty(formNodeGuid) || string.IsNullOrEmpty(toNodeGuid)) - { - return; - } - EnvDecorator.ConnectNodeAsync(formNodeGuid, toNodeGuid, currentConnectionType); - } - - GlobalJunctionData.OK(); + //if (IsConnecting) + //{ + // var formNodeGuid = startConnectNodeControl?.ViewModel.NodeModel.Guid; + // var toNodeGuid = (sender as NodeControlBase)?.ViewModel.NodeModel.Guid; + // if (string.IsNullOrEmpty(formNodeGuid) || string.IsNullOrEmpty(toNodeGuid)) + // { + // return; + // } + // EnvDecorator.ConnectNodeAsync(formNodeGuid, toNodeGuid,0,0, currentConnectionType); + //} + //GlobalJunctionData.OK(); } /// @@ -1463,28 +1554,30 @@ namespace Serein.Workbench IsConnecting = false; startConnectNodeControl = null; // 移除虚线 - if (currentLine != null) - { - FlowChartCanvas.Children.Remove(currentLine); - currentLine = null; - } + //if (currentLine != null) + //{ + // FlowChartCanvas.Children.Remove(currentLine); + // currentLine = null; + //} } /// /// 更新与指定控件相关的所有连接的位置。 /// - private void UpdateConnections(NodeControlBase nodeControl) - { - foreach (var connection in Connections) - { - if (connection.Start == nodeControl || connection.End == nodeControl) - { - connection.AddOrRefreshLine(); // 主动更新某个控件相关的所有连接线 - //connection.RemoveFromCanvas(); - //BezierLineDrawer.UpdateBezierLine(FlowChartCanvas, connection.Start, connection.End, connection.BezierPath, connection.ArrowPath); - } - } - } + //private void UpdateConnections(NodeControlBase nodeControl) + //{ + // nodeControl.UpdateLocationConnections(); + // //foreach (var connection in Connections) + // //{ + // // if (connection.Start.MyNode.Guid == nodeControl.ViewModel.NodeModel.Guid + // // || connection.End.MyNode.Guid == nodeControl.ViewModel.NodeModel.Guid) + // // { + // // connection.RefreshLine(); // 主动更新某个控件相关的所有连接线 + // // //connection.RemoveFromCanvas(); + // // //BezierLineDrawer.UpdateBezierLine(FlowChartCanvas, connection.Start, connection.End, connection.BezierPath, connection.ArrowPath); + // // } + // //} + //} #endregion #region 拖动画布实现缩放平移效果 @@ -1688,7 +1781,7 @@ namespace Serein.Workbench /// private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { - if (GlobalJunctionData.IsCreatingConnection) + if (GlobalJunctionData.MyGlobalConnectingData is not null) { return; } @@ -1724,7 +1817,7 @@ namespace Serein.Workbench } /// - /// 在画布中释放鼠标按下,结束选取状态 + /// 在画布中释放鼠标按下,结束选取状态 / 停止创建连线,尝试连接节点 /// /// /// @@ -1744,26 +1837,36 @@ namespace Serein.Workbench } // 创建连线 - if (GlobalJunctionData.MyGlobalData is not null) + if (GlobalJunctionData.MyGlobalConnectingData is not null) { - var myData = GlobalJunctionData.MyGlobalData; + var myData = GlobalJunctionData.MyGlobalConnectingData; + GlobalJunctionData.OK(); var canvas = this.FlowChartCanvas; - if (GlobalJunctionData.CanCreate) + if (myData.IsCanConnected) { - //var startPoint = myDataType.StartPoint; var currentendPoint = e.GetPosition(canvas); // 当前鼠标落点 - var changingJunctionPosition = myData.ChangingJunction.TranslatePoint(new Point(0, 0), canvas); - var changingJunctionRect = new Rect(changingJunctionPosition, new Size(myData.ChangingJunction.Width, myData.ChangingJunction.Height)); + var changingJunctionPosition = myData.CurrentJunction.TranslatePoint(new Point(0, 0), canvas); + var changingJunctionRect = new Rect(changingJunctionPosition, new Size(myData.CurrentJunction.Width, myData.CurrentJunction.Height)); - if (changingJunctionRect.Contains(currentendPoint)) + if (changingJunctionRect.Contains(currentendPoint)) // 可以创建连接 { - this.EnvDecorator.ConnectNodeAsync(myData.StartJunction.NodeGuid, myData.ChangingJunction.NodeGuid, ConnectionType.IsSucceed); - } + var argIndex = 0; + if(myData.StartJunction is ArgJunctionControl argJunction1) + { + argIndex = argJunction1.ArgIndex; - + } + else if (myData.CurrentJunction is ArgJunctionControl argJunction2) + { + argIndex = argJunction2.ArgIndex; + } + this.EnvDecorator.ConnectNodeAsync(myData.StartJunction.MyNode.Guid, myData.CurrentJunction.MyNode.Guid, + myData.StartJunction.JunctionType, + myData.CurrentJunction.JunctionType, + ConnectionInvokeType.IsSucceed,argIndex); + } } - GlobalJunctionData.OK(); } e.Handled = true; @@ -2628,7 +2731,7 @@ namespace Serein.Workbench FlowChartCanvas.Children.Clear(); Connections.Clear(); NodeControls.Clear(); - currentLine = null; + //currentLine = null; startConnectNodeControl = null; MessageBox.Show("所有DLL已卸载。", "信息", MessageBoxButton.OK, MessageBoxImage.Information); } diff --git a/WorkBench/Node/View/ActionNodeControl.xaml b/WorkBench/Node/View/ActionNodeControl.xaml index 3afec98..43f9657 100644 --- a/WorkBench/Node/View/ActionNodeControl.xaml +++ b/WorkBench/Node/View/ActionNodeControl.xaml @@ -10,14 +10,14 @@ d:DataContext="{d:DesignInstance vm:ActionNodeControlViewModel}" mc:Ignorable="d" MaxWidth="300"> - + - + @@ -28,10 +28,10 @@ - +