修改了自述文件

This commit is contained in:
fengjiayi
2024-12-24 11:51:12 +08:00
parent 5941f75313
commit 949ac973bc
8 changed files with 243 additions and 87 deletions

View File

@@ -818,8 +818,14 @@ namespace Serein.Library.Api
/// <param name="methodDetailsInfo">节点绑定的方法说明</param>
Task<NodeInfo> CreateNodeAsync(NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null);
/// <summary>
/// 设置两个节点某个类型的方法调用关系为优先调用
/// </summary>
/// <param name="fromNodeGuid">起始节点</param>
/// <param name="toNodeGuid">目标节点</param>
/// <param name="connectionType">连接关系</param>
/// <returns></returns>
Task<bool> SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType);
/// <summary>
/// 移除两个节点之间的方法调用关系

View File

@@ -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
}
/// <summary>
/// 设置两个节点某个类型的方法调用关系为优先调用
/// </summary>
/// <param name="fromNodeGuid">起始节点</param>
/// <param name="toNodeGuid">目标节点</param>
/// <param name="connectionType">连接关系</param>
/// <returns>是否成功调用</returns>
public Task<bool> 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);
}
/// <summary>
/// 移除连接节点之间方法调用的关系
/// </summary>

View File

@@ -350,6 +350,24 @@ namespace Serein.NodeFlow.Env
return currentFlowEnvironment.UnloadLibrary(assemblyName);
}
/// <summary>
/// 设置两个节点某个类型的方法调用关系为优先调用
/// </summary>
/// <param name="fromNodeGuid">起始节点</param>
/// <param name="toNodeGuid">目标节点</param>
/// <param name="connectionType">连接关系</param>
/// <returns>是否成功调用</returns>
public async Task<bool> SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType)
{
return await currentFlowEnvironment.SetConnectPriorityInvoke(fromNodeGuid, toNodeGuid, connectionType);
}
/// <summary>
/// 移除方法调用关系
/// </summary>
/// <param name="fromNodeGuid"></param>
/// <param name="toNodeGuid"></param>
/// <param name="connectionType"></param>
/// <returns></returns>
public async Task<bool> RemoveConnectInvokeAsync(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType)
{
return await currentFlowEnvironment.RemoveConnectInvokeAsync(fromNodeGuid, toNodeGuid, connectionType);

View File

@@ -639,7 +639,18 @@ namespace Serein.NodeFlow.Env
return result;
}
/// <summary>
/// 设置两个节点某个类型的方法调用关系为优先调用
/// </summary>
/// <param name="fromNodeGuid">起始节点</param>
/// <param name="toNodeGuid">目标节点</param>
/// <param name="connectionType">连接关系</param>
/// <returns>是否成功调用</returns>
public async Task<bool> SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType)
{
this.WriteLine(InfoType.WARN, "远程环境尚未实现的接口(重要,会尽快实现)SetConnectPriorityInvoke");
return false;
}
/// <summary>
/// 移除两个节点之间的方法调用关系
/// </summary>
@@ -705,7 +716,7 @@ namespace Serein.NodeFlow.Env
/// <returns></returns>
public async Task LoadNodeInfosAsync(List<NodeInfo> nodeInfos)
{
this.WriteLine(InfoType.INFO, "远程环境尚未实现的接口(重要,会尽快实现)LoadNodeInfoAsync");
this.WriteLine(InfoType.WARN, "远程环境尚未实现的接口(重要,会尽快实现)LoadNodeInfoAsync");
}

View File

@@ -38,7 +38,7 @@ namespace Serein.NodeFlow.Model
}
/// <summary>
/// 数据节点
/// 数据来源的节点
/// </summary>
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;
}

190
README.md
View File

@@ -2,71 +2,151 @@
基于WPFDotnet 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<int>
~~~
5. 获取KeyName为【 MyDevice 】全局数据:
~~~
@Get #MyDevice#
~~~
6. 从全局数据【 MyDevice 】中获取“IP”属性
~~~
@Get #MyDevice#.IP
~~~
* 数据类型转换表达式:@Dtc
* 描述:有时需要显式的设置节点参数值,但参数接收了其它的类型,需要经过一次转换,将显式的文本值转为入参数据类型。
* 使用方法:
~~~
@Dtc <long>1233
@Dtc <bool>True
@Dtc <DateTime>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`<IFlipflopContext<TResult>>`
* 描述运行开始时所有无上级节点的触发器节点在当前分支中作为起始节点分别建立新的线程运行然后异步等待触发如果有。这种触发器拥有独自的DynamicContext上下文共用同一个Ioc执行完成之后会重新从分支起点的触发器开始等待。
* 分支中的触发器
* 入参依照Action节点。
* 返回值Task`<IFlipflopContext<TResult>>`
* 描述:接收上一节点传递的上下文,同样进入异步等待,但执行完成后不会再次等待自身(只会触发一次)。
* IFlipflopContext`<TResult>`
* 基本说明IFlipflopContext是一个接口你无须关心内部实现
* 参数描述:State状态枚举描述Succeed、Cancel、Error、Cancel如果返回Cancel则不会执行后继分支如果返回其它状态则会获取对应的后继分支开始执行。
* 参数描述:Type触发状态描述External外部触发Overtime超时触发当你在代码中的其他地方主动触发了触发器则该次触发类型为External当你在创建触发器后超过了指定时间创建触发器时会要求声明超时时间则会自动触发但触发类型为Overtime触发参数未你在创建触发器时指定的值
* 参数描述Value触发时传递的参数
* 使用场景:配合 FlowTrigger`<TEnum>` 使用例如定时从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)
* **Action - 动作**
* 入参:自定义。如果入参类型为IDynamicContext会传入当前的上下文如果入参类型为NodeBase会传入节点对应的Model。如果不显式指定参数来源参数会尝试获取运行时上一节点返回值并根据当前入参类型尝试进行类型转换。
* 返回值:自定义,支持异步等待。
* 描述:同步执行对应的方法。
* **Flipflop - 触发器**
* 全局触发器
* 入参依照Action节点。
* 返回值Task`<IFlipflopContext<TResult>>`
* 描述运行开始时所有无上级节点的触发器节点在当前分支中作为起始节点分别建立新的线程运行然后异步等待触发如果有。这种触发器拥有独自的DynamicContext上下文共用同一个Ioc执行完成之后会重新从分支起点的触发器开始等待。
* 分支中的触发器
* 入参依照Action节点。
* 返回值Task`<IFlipflopContext<TResult>>`
* 描述:接收上一节点传递的上下文,同样进入异步等待,但执行完成后不会再次等待自身(只会触发一次)。
* IFlipflopContext`<TResult>`
* 基本说明:IFlipflopContext是一个接口,你无须关心内部实现。
* 参数描述State状态枚举描述Succeed、Cancel、Error、Cancel如果返回Cancel则不会执行后继分支如果返回其它状态则会获取对应的后继分支开始执行
* 参数描述:Type触发状态描述External外部触发Overtime超时触发当你在代码中的其他地方主动触发了触发器则该次触发类型为External当你在创建触发器后超过了指定时间创建触发器时会要求声明超时时间则会自动触发但触发类型为Overtime触发参数未你在创建触发器时指定的值
* 参数描述:Value触发时传递的参数。
* 使用场景:配合 FlowTrigger`<TEnum>` 使用例如定时从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)

View File

@@ -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();
}

View File

@@ -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
}
}
/// <summary>
/// 置顶调用关系
/// </summary>
public void Topping()
{
var env = Start.MyNode.Env;
if (Start.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke)
{
env.SetConnectPriorityInvoke(Start.MyNode.Guid, End.MyNode.Guid, InvokeType);
}
}
/// <summary>
/// 重新绘制
/// </summary>