diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 1f4989d..0ba3861 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -818,8 +818,14 @@ namespace Serein.Library.Api /// 节点绑定的方法说明 Task CreateNodeAsync(NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null); - - + /// + /// 设置两个节点某个类型的方法调用关系为优先调用 + /// + /// 起始节点 + /// 目标节点 + /// 连接关系 + /// + Task SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType); /// /// 移除两个节点之间的方法调用关系 diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index 6196196..bcf8b38 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -10,6 +10,7 @@ using Serein.Library.Utils.SereinExpression; using Serein.NodeFlow.Model; using Serein.NodeFlow.Tool; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; using System.Numerics; using System.Reflection; @@ -1194,6 +1195,33 @@ namespace Serein.NodeFlow.Env } + + /// + /// 设置两个节点某个类型的方法调用关系为优先调用 + /// + /// 起始节点 + /// 目标节点 + /// 连接关系 + /// 是否成功调用 + public Task SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) + { + // 获取起始节点与目标节点 + var fromNode = GuidToModel(fromNodeGuid); + var toNode = GuidToModel(toNodeGuid); + if (fromNode is null || toNode is null) return Task.FromResult(false); + if ( fromNode.SuccessorNodes.TryGetValue(connectionType, out var nodes)) + { + var idx = nodes.IndexOf(toNode); + if (idx > -1) + { + nodes.RemoveAt(idx); + nodes.Insert(0, toNode); + return Task.FromResult(true); + } + } + return Task.FromResult(false); + } + /// /// 移除连接节点之间方法调用的关系 /// diff --git a/NodeFlow/Env/FlowEnvironmentDecorator.cs b/NodeFlow/Env/FlowEnvironmentDecorator.cs index da4ad6c..47f63b4 100644 --- a/NodeFlow/Env/FlowEnvironmentDecorator.cs +++ b/NodeFlow/Env/FlowEnvironmentDecorator.cs @@ -350,6 +350,24 @@ namespace Serein.NodeFlow.Env return currentFlowEnvironment.UnloadLibrary(assemblyName); } + /// + /// 设置两个节点某个类型的方法调用关系为优先调用 + /// + /// 起始节点 + /// 目标节点 + /// 连接关系 + /// 是否成功调用 + public async Task SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) + { + return await currentFlowEnvironment.SetConnectPriorityInvoke(fromNodeGuid, toNodeGuid, connectionType); + } + /// + /// 移除方法调用关系 + /// + /// + /// + /// + /// public async Task RemoveConnectInvokeAsync(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) { return await currentFlowEnvironment.RemoveConnectInvokeAsync(fromNodeGuid, toNodeGuid, connectionType); diff --git a/NodeFlow/Env/RemoteFlowEnvironment.cs b/NodeFlow/Env/RemoteFlowEnvironment.cs index e30c7e3..1db75cd 100644 --- a/NodeFlow/Env/RemoteFlowEnvironment.cs +++ b/NodeFlow/Env/RemoteFlowEnvironment.cs @@ -639,7 +639,18 @@ namespace Serein.NodeFlow.Env return result; } - + /// + /// 设置两个节点某个类型的方法调用关系为优先调用 + /// + /// 起始节点 + /// 目标节点 + /// 连接关系 + /// 是否成功调用 + public async Task SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) + { + this.WriteLine(InfoType.WARN, "远程环境尚未实现的接口(重要,会尽快实现):SetConnectPriorityInvoke"); + return false; + } /// /// 移除两个节点之间的方法调用关系 /// @@ -705,7 +716,7 @@ namespace Serein.NodeFlow.Env /// public async Task LoadNodeInfosAsync(List nodeInfos) { - this.WriteLine(InfoType.INFO, "远程环境尚未实现的接口(重要,会尽快实现):LoadNodeInfoAsync"); + this.WriteLine(InfoType.WARN, "远程环境尚未实现的接口(重要,会尽快实现):LoadNodeInfoAsync"); } diff --git a/NodeFlow/Model/SingleGlobalDataNode.cs b/NodeFlow/Model/SingleGlobalDataNode.cs index d07bcd4..8641214 100644 --- a/NodeFlow/Model/SingleGlobalDataNode.cs +++ b/NodeFlow/Model/SingleGlobalDataNode.cs @@ -38,7 +38,7 @@ namespace Serein.NodeFlow.Model } /// - /// 数据节点 + /// 数据来源的节点 /// private string? DataNodeGuid; @@ -76,7 +76,7 @@ namespace Serein.NodeFlow.Model if (DataNodeGuid == null) { context.NextOrientation = ConnectionInvokeType.IsError; - SereinEnv.WriteLine(InfoType.ERROR, $"全局数据节点没有数据[{this.Guid}]"); + SereinEnv.WriteLine(InfoType.ERROR, $"全局数据节点没有设置数据来源[{this.Guid}]"); return null; } diff --git a/README.md b/README.md index ffea762..f4c1f17 100644 --- a/README.md +++ b/README.md @@ -2,71 +2,151 @@ 基于WPF(Dotnet 8)的流程可视化编辑器,需二次开发。 不定期在Bilibili个人空间上更新相关的视频。 https://space.bilibili.com/33526379 - # 计划任务 2024年10月28日更新 -* 正在重写节点的实现方式 -* 准备新增基础节点“属性包装器”,用来收集各个节点的数据,包装成匿名对象/Json类型(暂时未想到如何设计) -* 后续拓展远程管理与远程客户端的功能(目前仅支持远程修改节点属性、添加/移除节点、启动流程、停止流程) +* 重新完善远程管理与远程客户端的功能(目前仅支持远程修改节点属性、添加/移除节点、启动流程、停止流程) +* 重新完善节点树视图、IOC容器对象视图(目前残废版) * 计划实现单步执行(暂未想到如何在不影响异步流程的前提下停止流程) -* 计划实现节点树视图、IOC容器对象视图(目前残废版) -* 计划实现不停机更新类库(更新整个DLL/某个节点),但似乎难度过大 - - +* 考虑模仿AST的方式,将流程图以“Node token → C# Code”的方式进行原生代码支持 # 如何加载我的DLL? -使用 **DynamicFlow** 特性标记你的类,可以参照 **Net461DllTest** 的实现。编译为 Dll文件 后,拖入到软件中即可。 +为你的工程添加**Serein.Library**项目引用(也可在Negut上下载),使用 **DynamicFlow** 特性标记你的类,可以参照 **Net461DllTest** 的实现(该示例工程的设计并不完善,并未做依赖分离,仅做参考)。编译为 Dll文件 后,拖入到软件中即可。 如果你不想下载整个工程文件,“FLowEdit”目录下放有“FlowEdit可视化流程编辑器.zip”压缩包,可以直接解压使用(但可能需要你安装 .Net8 运行环境)。 - # 如何让我的方法成为节点? 使用 **NodeAction** 特性标记你的方法。 * 动作节点 - Action * 触发器节点 - Flipflop - # 关于 DynamicNodeType 枚举的补充说明。 - ## 1. 不生成节点控件的枚举值: - * **Init - 初始化方法** - * 入参:**IDynamicContext**(有且只有一个参数)。 - * 返回值:自定义,但不会处理返回值,支持异步等待。 - * 描述:在运行时首先被调用。语义类似于构造方法。建议在Init方法内初始化类、注册类等一切需要在构造函数中执行的方法。 - - * **Loading - 加载方法** - * 入参:**IDynamicContext**(有且只有一个参数)。 - * 返回值:自定义,但不会处理返回值,支持异步等待。 - * 描述:当所有Dll的Init方法调用完成后,首先调用、也才会调用DLL的Loading方法。建议在Loading方法内进行业务上的初始化(例如启动Web,启动第三方服务)。 - - * **Exit - 结束方法** - * 入参:**IDynamicContext**(有且只有一个参数)。 - * 返回值:自定义,但不会处理返回值,支持异步等待。 - * 描述:当结束/手动结束运行时,会调用所有Dll的Exit方法。使用场景类似于:终止内部的其它线程,通知其它进程关闭,例如停止第三方服务。 - * **关于IDynamicContext说明** - * 基本说明:IDynamicContext是动态上下文接口,内部提供全局单例的IFlowEnvironment环境接口,用以注册、获取实例(单例模式),一般情况下,你无须关注IFlowEnvironment对外暴露的属性方法。 - +* **Init - 初始化方法** + * 入参:**IDynamicContext**(有且只有一个参数)。 + * 返回值:自定义,但不会处理返回值,支持异步等待。 + * 描述:在运行时首先被调用。语义类似于构造方法。建议在Init方法内初始化类、注册类等一切需要在构造函数中执行的方法。 +* **Loading - 加载方法** + * 入参:**IDynamicContext**(有且只有一个参数)。 + * 返回值:自定义,但不会处理返回值,支持异步等待。 + * 描述:当所有Dll的Init方法调用完成后,首先调用、也才会调用DLL的Loading方法。建议在Loading方法内进行业务上的初始化(例如启动Web,启动第三方服务)。 +* **Exit - 结束方法** + * 入参:**IDynamicContext**(有且只有一个参数)。 + * 返回值:自定义,但不会处理返回值,支持异步等待。 + * 描述:当结束/手动结束运行时,会调用所有Dll的Exit方法。使用场景类似于:终止内部的其它线程,通知其它进程关闭,例如停止第三方服务。 +* **关于IDynamicContext说明** + * 基本说明:IDynamicContext是动态上下文接口,内部提供全局单例的IFlowEnvironment环境接口,用以注册、获取实例(单例模式),一般情况下,你无须关注IFlowEnvironment对外暴露的属性方法。 ## 2. 基础节点 - * 待更新 +* **Script - 脚本节点** + * 入参:可选可变 + * 描述:有时我们需要定义一个临时的类对象,但又不想在代码中写死属性,又或者某些流程操作中,因为业务场景需要布置大量的逻辑判断,导致流程图变得极为臃肿不堪入目,于是引入了脚本节点。脚本节点动态能力强,不同于表达式使用递归下降,而是基于AST抽象语法树调用相应的C#代码,性能至少差强人意。 + * 使用方式: + ``` + // 定义一个类 + class Info{ + string PlcName; + string Content; + string LogType; + DateTime LogTime; + } + // 获取必要的参数 + let flow = GetFlowApi(); // 脚本默认挂载的方法,获取当前流程的API + let plc = flow.GetGlobalData("JC-PLC"); // 流程API对应的方法,获取全局数据 + let arg = flow.GetArgData(0); // 获取第一个入参 + let varInfo = arg.Var; // 获取入参对象的Var属性 + let data = arg.Data; // 获取入参对象的Data属性 + + let log = new Info(); // 创建一个类 + log.Content = plc + " " + varInfo + " - 状态 Value : " + data; + log.PlcName = plc.Name; + log.LogType = "info"; + log.LogTime = GetNow(); // 脚本默认挂载的方法,获取当前时间 + return log; // 返回对象 + ``` +* **GlobalData - 全局数据节点** + * 入参:KeyName ,在整个流程环境中标识某个数据的key值。 + * 描述:有时需要获取其它节点的数据,但如果强行在两个节点之间进行连线,会让项目流程图变得无比丑陋,如果在类库代码中自己对全局数据进行维护,可能也不太优雅,所以引入了全局数据节点(全局变量) + * 使用方式:全局数据节点实质上只是一个节点容器,这意味着你能将任意节点拖拽到该容器节点上,当流程执行到这个容器节点(全局数据节点)时,会自动调用容器内部的节点对应的方法,并将返回的数据保存在运行环境维护的Map中。 + * 其它获取到全局数据的方式: + 1. 表达式 : + ~~~ + @Get #KeyName# // 使用##符号表达全局数据KeyName的标识符 + ~~~ + 2. Script代码: + ~~~~ + let flow = GetFlowApi(); // 获取流程API + let data = flow.GetGlobalData("KeyName"); // 获取全局数据 + ~~~~ + 3. C# 代码中(不建议): + ~~~ + SereinEnv.GetFlowGlobalData("KeyName"); // 获取全局数据 + SereinEnv.AddOrUpdateFlowGlobalData("KeyName", obj); // 设置/更新全局数据,不建议 + ~~~ +* **ExpOp- 表达式节点** + * 入参: 自定义的表达式。 + * 取值表达式:@Get + * 描述:有时节点返回了object,但下一个节点只需要对象中某个属性,而非整个对象。如果修改节点的定义,有可能破坏了代码的封装,为了解决这个痛点,于是增加了表达式功能。 + * 使用方法: + 1. 获取对象的属性成员: + ~~~~ + @Get .[property]/[field] + ~~~~ + 2. 获取对象的数组成员中下标为22的项: + ~~~~ + @Get .array[22] + ~~~~ + 3. 获取对象的字典成员中键为“33”的值: + ~~~ + @Get .dict[33] + ~~~ + 4. 获取对象“ID”属性并转为int: + ~~~ + @Get .ID + ~~~ + 5. 获取KeyName为【 MyDevice 】全局数据: + ~~~ + @Get #MyDevice# + ~~~ + 6. 从全局数据【 MyDevice 】中获取“IP”属性: + ~~~ + @Get #MyDevice#.IP + ~~~ + * 数据类型转换表达式:@Dtc + * 描述:有时需要显式的设置节点参数值,但参数接收了其它的类型,需要经过一次转换,将显式的文本值转为入参数据类型。 + * 使用方法: + ~~~ + @Dtc 1233 + @Dtc True + @Dtc 2024-12-24 11:13:42 (注:如果右值为“now”,则自动获取当前时间) + ~~~ +* **ExpCondition - 条件表达式节点** + * 入参: 自定义。 + * 描述:与表达式节点不同,条件表达式节点是判断条件是否成立,如果成立,返回true,否则返回false,如果表达式执行失败,而进入 error 分支。 + * 增加描述:如果入参数据为某个对象,需要得到其属性/字段,可以在表达式输入框使用“ .[property]/[field]< type> [op] value ”的方式判断条件,注意,必须使用“.”符号,这有助于显然的表达需要从入参对象中取内部某个值,另外,也可以在入参数据编辑框,使用“@Get .[property]/[field]”的方式重新定义入参数据。 + * 表达式符号说明: + * [property] /[field] : 属性/字段 + * [op] : 操作符 + 1. bool表达式:== + 2. 数值表达式 :==、>=、 <=、in a-b (表示判断是否在a至b的数值范围内), !in a-b(取反); + 3. 文本表达式:==/equals(等于)、!=/notequals(不等于)、c/contains(出现过)、nc/doesnotcontain(没有出现过)、sw/startswith(开头等于)、ew/endswith(结尾等于) + * [value] : 条件值 ## 3. 从DLL生成控件的枚举值: - * **Action - 动作** - * 入参:自定义。如果传入IDynamicContext,会传入当前的上下文;如果传入NodeBase,会传入节点对应的Model。如果不显式指定参数来源,参数会尝试获取运行时上一节点返回值,并根据当前入参类型尝试进行类型转换。 - * 返回值:自定义,返回值由对应的Model类的object? FlowData变量接收。支持异步等待。 - * 描述:同步执行对应的方法。 - - * **Flipflop - 触发器** - * 全局触发器 - * 入参:依照Action节点。 - * 返回值:Task`>` - * 描述:运行开始时,所有无上级节点的触发器节点(在当前分支中作为起始节点),分别建立新的线程运行,然后异步等待触发(如果有)。这种触发器拥有独自的DynamicContext上下文(共用同一个Ioc),执行完成之后,会重新从分支起点的触发器开始等待。 - * 分支中的触发器 - * 入参:依照Action节点。 - * 返回值:Task`>` - * 描述:接收上一节点传递的上下文,同样进入异步等待,但执行完成后不会再次等待自身(只会触发一次)。 - * IFlipflopContext`` - * 基本说明:IFlipflopContext是一个接口,你无须关心内部实现。 - * 参数描述:State,状态枚举描述(Succeed、Cancel、Error、Cancel),如果返回Cancel,则不会执行后继分支,如果返回其它状态,则会获取对应的后继分支,开始执行。 - * 参数描述:Type,触发状态描述(External外部触发,Overtime超时触发),当你在代码中的其他地方主动触发了触发器,则该次触发类型为External,当你在创建触发器后超过了指定时间(创建触发器时会要求声明超时时间),则会自动触发,但触发类型为Overtime,触发参数未你在创建触发器时指定的值) - * 参数描述:Value,触发时传递的参数。 - * 使用场景:配合 FlowTrigger`` 使用,例如定时从PLC中获取状态,当某个变量发生改变时,会通知相应的触发器,如果需要,可以传递对应的数据。 -演示: -![image](https://github.com/fhhyyp/serein-flow/blob/cc5f8255135b96c6bb3669bc4aa8d8167a71c262/Image/%E6%BC%94%E7%A4%BA%20-%201.png) -![image](https://github.com/fhhyyp/serein-flow/blob/cc5f8255135b96c6bb3669bc4aa8d8167a71c262/Image/%E6%BC%94%E7%A4%BA%20-%202.png) -![image](https://github.com/fhhyyp/serein-flow/blob/8f17b786f3585cabfeef60d9ab871d43b69e5461/Image/%E6%BC%94%E7%A4%BA%20-%203.png) -![image](https://github.com/fhhyyp/serein-flow/blob/8f17b786f3585cabfeef60d9ab871d43b69e5461/Image/%E6%BC%94%E7%A4%BA%20-%204.png) \ No newline at end of file +* **Action - 动作** + * 入参:自定义。如果入参类型为IDynamicContext,会传入当前的上下文;如果入参类型为NodeBase,会传入节点对应的Model。如果不显式指定参数来源,参数会尝试获取运行时上一节点返回值,并根据当前入参类型尝试进行类型转换。 + * 返回值:自定义,支持异步等待。 + * 描述:同步执行对应的方法。 +* **Flipflop - 触发器** + * 全局触发器 + * 入参:依照Action节点。 + * 返回值:Task`>` + * 描述:运行开始时,所有无上级节点的触发器节点(在当前分支中作为起始节点),分别建立新的线程运行,然后异步等待触发(如果有)。这种触发器拥有独自的DynamicContext上下文(共用同一个Ioc),执行完成之后,会重新从分支起点的触发器开始等待。 + * 分支中的触发器 + * 入参:依照Action节点。 + * 返回值:Task`>` + * 描述:接收上一节点传递的上下文,同样进入异步等待,但执行完成后不会再次等待自身(只会触发一次)。 + * IFlipflopContext`` + * 基本说明:IFlipflopContext是一个接口,你无须关心内部实现。 + * 参数描述:State,状态枚举描述(Succeed、Cancel、Error、Cancel),如果返回Cancel,则不会执行后继分支,如果返回其它状态,则会获取对应的后继分支,开始执行。 + * 参数描述:Type,触发状态描述(External外部触发,Overtime超时触发),当你在代码中的其他地方主动触发了触发器,则该次触发类型为External,当你在创建触发器后超过了指定时间(创建触发器时会要求声明超时时间),则会自动触发,但触发类型为Overtime,触发参数未你在创建触发器时指定的值) + * 参数描述:Value,触发时传递的参数。 + * 使用场景:配合 FlowTrigger`` 使用,例如定时从PLC中获取状态,当某个变量发生改变时,会通知相应的触发器,如果需要,可以传递对应的数据。 + 演示: + ![image](https://github.com/fhhyyp/serein-flow/blob/cc5f8255135b96c6bb3669bc4aa8d8167a71c262/Image/%E6%BC%94%E7%A4%BA%20-%201.png) + ![image](https://github.com/fhhyyp/serein-flow/blob/cc5f8255135b96c6bb3669bc4aa8d8167a71c262/Image/%E6%BC%94%E7%A4%BA%20-%202.png) + ![image](https://github.com/fhhyyp/serein-flow/blob/8f17b786f3585cabfeef60d9ab871d43b69e5461/Image/%E6%BC%94%E7%A4%BA%20-%203.png) + ![image](https://github.com/fhhyyp/serein-flow/blob/8f17b786f3585cabfeef60d9ab871d43b69e5461/Image/%E6%BC%94%E7%A4%BA%20-%204.png) diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index 85e0433..e7424ab 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -373,24 +373,39 @@ namespace Serein.Workbench for (int index = 0; index < projectData.Librarys.Length; index++) { NodeLibraryInfo? library = projectData.Librarys[index]; - string sourceFile = new Uri(library.FilePath).LocalPath; // 源文件夹 - string targetPath = System.IO.Path.Combine(librarySavePath, library.FileName); // 目标文件夹 - SereinEnv.WriteLine(InfoType.INFO, $"源路径 : {sourceFile}"); - SereinEnv.WriteLine(InfoType.INFO, $"目标路径 : {targetPath}"); - + string sourceFilePath = new Uri(library.FilePath).LocalPath; // 源文件夹 + string targetFilePath = System.IO.Path.Combine(librarySavePath, library.FileName); // 目标文件夹 + try { - File.Copy(sourceFile, targetPath, true); + if (File.Exists(sourceFilePath)) + { + if (!File.Exists(targetFilePath)) + { + SereinEnv.WriteLine(InfoType.INFO, $"源文件路径 : {sourceFilePath}"); + SereinEnv.WriteLine(InfoType.INFO, $"目标路径 : {targetFilePath}"); + File.Copy(sourceFilePath, targetFilePath, true); + + } + else + { + SereinEnv.WriteLine(InfoType.WARN, $"目标路径已有类库文件: {targetFilePath}"); + } + } + else + { + SereinEnv.WriteLine(InfoType.WARN, $"源文件不存在 : {targetFilePath}"); + } } catch (IOException ex) { SereinEnv.WriteLine(InfoType.ERROR, ex.Message); } - var dirName = System.IO.Path.GetDirectoryName(targetPath); + var dirName = System.IO.Path.GetDirectoryName(targetFilePath); if (!string.IsNullOrEmpty(dirName)) { - var tmpUri2 = new Uri(targetPath); + var tmpUri2 = new Uri(targetFilePath); var relativePath = saveProjectFileUri.MakeRelativeUri(tmpUri2).ToString(); // 转为类库的相对文件路径 @@ -491,9 +506,6 @@ namespace Serein.Workbench return; } - - - if (eventArgs.JunctionOfConnectionType == JunctionOfConnectionType.Invoke) { ConnectionInvokeType connectionType = eventArgs.ConnectionInvokeType; @@ -509,7 +521,6 @@ namespace Serein.Workbench JunctionControlBase startJunction = IFormJunction.NextStepJunction; JunctionControlBase endJunction = IToJunction.ExecuteJunction; - // 添加连接 var connection = new ConnectionControl( FlowChartCanvas, @@ -518,20 +529,16 @@ namespace Serein.Workbench endJunction ); - //() => EnvDecorator.RemoveConnectInvokeAsync(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(); // 环境触发了创建节点连接事件 - - + } #endregion #region 移除连接 @@ -1191,13 +1198,6 @@ 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, 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))); - - - #region 右键菜单功能 - 控件对齐 var AvoidMenu = new MenuItem(); @@ -1962,6 +1962,7 @@ namespace Serein.Workbench myData.ConnectionInvokeType); } #endregion + #region 参数来源关系创建 else if (myData.Type == JunctionOfConnectionType.Arg) { @@ -1982,7 +1983,6 @@ namespace Serein.Workbench argIndex); } #endregion - } EndConnection(); } diff --git a/Workbench/Node/View/ConnectionControl.cs b/Workbench/Node/View/ConnectionControl.cs index 8b292aa..e41637d 100644 --- a/Workbench/Node/View/ConnectionControl.cs +++ b/Workbench/Node/View/ConnectionControl.cs @@ -224,7 +224,8 @@ namespace Serein.Workbench.Node.View private void ConfigureLineContextMenu() { var contextMenu = new ContextMenu(); - contextMenu.Items.Add(MainWindow.CreateMenuItem("删除连线", (s, e) => this.Remote())); + contextMenu.Items.Add(MainWindow.CreateMenuItem("删除连线", (s, e) => Remote())); + contextMenu.Items.Add(MainWindow.CreateMenuItem("于父节点调用顺序中置顶", (s, e) => Topping())); BezierLine.ContextMenu = contextMenu; } @@ -246,6 +247,18 @@ namespace Serein.Workbench.Node.View } } + /// + /// 置顶调用关系 + /// + public void Topping() + { + var env = Start.MyNode.Env; + if (Start.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke) + { + env.SetConnectPriorityInvoke(Start.MyNode.Guid, End.MyNode.Guid, InvokeType); + } + } + /// /// 重新绘制 ///