实现了拖拽式设置方法调用顺序、方法入参参数来源

This commit is contained in:
fengjiayi
2024-10-24 23:32:43 +08:00
parent 0666f0b2c1
commit 6f26d303e4
43 changed files with 2282 additions and 763 deletions

View File

@@ -30,6 +30,11 @@ namespace Serein.Library.Core.NodeFlow
/// </summary>
public RunState RunState { get; set; } = RunState.NoStart;
/// <summary>
/// 当前节点执行完成后,设置该属性,让运行环境判断接下来要执行哪个分支的节点。
/// </summary>
public ConnectionInvokeType NextOrientation { get; set; }
/// <summary>
/// 每个上下文分别存放节点的当前数据
/// </summary>

View File

@@ -28,6 +28,12 @@ namespace Serein.Library.Framework.NodeFlow
/// 运行状态
/// </summary>
public RunState RunState { get; set; } = RunState.NoStart;
/// <summary>
/// 当前节点执行完成后,设置该属性,让运行环境判断接下来要执行哪个分支的节点。
/// </summary>
public ConnectionInvokeType NextOrientation { get; set; }
/// <summary>
/// 每个上下文分别存放节点的当前数据
/// </summary>

View File

@@ -15,8 +15,16 @@ namespace Serein.Library.Api
/// </summary>
IFlowEnvironment Env { get; }
/// <summary>
/// 是否正在运行
/// </summary>
RunState RunState { get; }
/// <summary>
/// 下一个要执行的节点
/// </summary>
ConnectionInvokeType NextOrientation { get; set; }
/// <summary>
/// 获取节点的数据(当前节点需要获取上一节点数据时,需要从 运行时上一节点 的Guid 通过这个方法进行获取
/// </summary>

View File

@@ -1,4 +1,5 @@
using Serein.Library.Utils;
using System;
using System.Collections.Generic;
@@ -175,12 +176,33 @@ namespace Serein.Library.Api
/// </summary>
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;
}
/// <summary>
/// 连接关系中始节点的Guid
@@ -193,11 +215,22 @@ namespace Serein.Library.Api
/// <summary>
/// 连接类型
/// </summary>
public ConnectionType ConnectionType { get; protected set; }
public ConnectionInvokeType ConnectionInvokeType { get; protected set; }
/// <summary>
/// 表示此次需要在两个节点之间创建连接关系,或是移除连接关系
/// </summary>
public ConnectChangeType ChangeType { get; protected set; }
/// <summary>
/// 指示需要创建什么类型的连接线
/// </summary>
public JunctionOfConnectionType JunctionOfConnectionType { get; protected set; }
/// <summary>
/// 节点对应的方法入参所需参数来源
/// </summary>
public ConnectionArgSourceType ConnectionArgSourceType { get; protected set; }
public int ArgIndex { get; protected set; }
}
@@ -639,6 +672,13 @@ namespace Serein.Library.Api
/// <returns></returns>
Task StartAsyncInSelectNode(string startNodeGuid);
/// <summary>
/// 立刻调用某个节点,并获取其返回值
/// </summary>
/// <param name="nodeGuid">节点Guid</param>
/// <returns></returns>
Task<object> InvokeNodeAsync(string nodeGuid);
/// <summary>
/// 结束运行
/// </summary>
@@ -663,8 +703,16 @@ namespace Serein.Library.Api
/// </summary>
/// <param name="fromNodeGuid">起始节点Guid</param>
/// <param name="toNodeGuid">目标节点Guid</param>
/// <param name="connectionType">连接类型</param>
Task<bool> ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, JunctionType toNodeJunctionType, ConnectionType connectionType);
/// <param name="fromNodeJunctionType">起始节点控制点</param>
/// <param name="toNodeJunctionType">目标节点控制点</param>
/// <param name="connectionType">决定了方法执行后的后继行为</param>
/// <param name="argIndex">决定了方法入参来源</param>
Task<bool> ConnectNodeAsync(string fromNodeGuid,
string toNodeGuid,
JunctionType fromNodeJunctionType,
JunctionType toNodeJunctionType,
ConnectionInvokeType connectionType,
int argIndex);
/// <summary>
/// 创建节点/区域/基础控件
@@ -680,7 +728,7 @@ namespace Serein.Library.Api
/// <param name="fromNodeGuid">起始节点</param>
/// <param name="toNodeGuid">目标节点</param>
/// <param name="connectionType">连接类型</param>
Task<bool> RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType);
Task<bool> RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType);
/// <summary>
/// 移除节点/区域/基础控件

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library
{
/// <summary>
/// 节点对应方法的入参来源
/// </summary>
public enum ConnectionArgSourceType
{
/// <summary>
/// (连接自身)从上一节点获取数据
/// </summary>
GetPreviousNodeData,
/// <summary>
/// 从指定节点获取数据
/// </summary>
GetOtherNodeData,
/// <summary>
/// 立刻执行某个节点获取其数据
/// </summary>
GetOtherNodeDataOfInvoke,
}
}

View File

@@ -8,7 +8,7 @@ namespace Serein.Library
/// <summary>
/// 表示了两个节点之间的连接关系,同时表示节点运行完成后,所会执行的下一个节点类型。
/// </summary>
public enum ConnectionType
public enum ConnectionInvokeType
{
/// <summary>
/// 将不会继续执行
@@ -30,11 +30,8 @@ namespace Serein.Library
/// 异常发生分支(当前节点对应的方法执行时出现非预期的异常)
/// </summary>
IsError,
/// <summary>
/// 无视
/// </summary>
// IsIgnore,
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library
{
/// <summary>
/// 连接的控制点类型枚举
/// </summary>
public enum JunctionOfConnectionType
{
/// <summary>
/// 没有关系,用于处理非预期连接的情况需要的返回值
/// </summary>
None,
/// <summary>
/// 表示方法执行顺序关系
/// </summary>
Invoke,
/// <summary>
/// 表示参数获取来源关系
/// </summary>
Arg
}
}

View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace Serein.Library
{
/// <summary>
/// 连接点类型
/// 控制点类型
/// </summary>
public enum JunctionType
{
@@ -28,4 +28,8 @@ namespace Serein.Library
/// </summary>
NextStep,
}
}

View File

@@ -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
*
*/
/// <summary>
/// 依附于节点的连接点
/// </summary>
public class JunctionModel
{
public JunctionModel(NodeModelBase NodeModel, JunctionType JunctionType)
{
Guid = System.Guid.NewGuid().ToString();
this.NodeModel = NodeModel;
this.JunctionType = JunctionType;
}
/// <summary>
/// 用于标识连接点
/// </summary>
public string Guid { get; }
/// <summary>
/// 标识连接点的类型
/// </summary>
public JunctionType JunctionType { get; }
/// <summary>
/// 连接点依附的节点
/// </summary>
public NodeModelBase NodeModel { get; }
}
}

View File

@@ -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
/// <summary>
/// 当前节点执行完毕后需要执行的下一个分支的类别
/// </summary>
[PropertyInfo]
private ConnectionType _nextOrientation = ConnectionType.None;
//[PropertyInfo]
//private ConnectionInvokeType _nextOrientation = ConnectionInvokeType.None;
/// <summary>
/// 运行时的异常信息(仅在 FlowState 为 Error 时存在对应值)
@@ -91,9 +92,9 @@ namespace Serein.Library
{
public NodeModelBase(IFlowEnvironment environment)
{
PreviousNodes = new Dictionary<ConnectionType, List<NodeModelBase>>();
SuccessorNodes = new Dictionary<ConnectionType, List<NodeModelBase>>();
foreach (ConnectionType ctType in NodeStaticConfig.ConnectionTypes)
PreviousNodes = new Dictionary<ConnectionInvokeType, List<NodeModelBase>>();
SuccessorNodes = new Dictionary<ConnectionInvokeType, List<NodeModelBase>>();
foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
{
PreviousNodes[ctType] = new List<NodeModelBase>();
SuccessorNodes[ctType] = new List<NodeModelBase>();
@@ -102,15 +103,17 @@ namespace Serein.Library
this.Env = environment;
}
/// <summary>
/// 不同分支的父节点
/// </summary>
public Dictionary<ConnectionType, List<NodeModelBase>> PreviousNodes { get; }
public Dictionary<ConnectionInvokeType, List<NodeModelBase>> PreviousNodes { get; }
/// <summary>
/// 不同分支的子节点
/// </summary>
public Dictionary<ConnectionType, List<NodeModelBase>> SuccessorNodes { get; }
public Dictionary<ConnectionInvokeType, List<NodeModelBase>> SuccessorNodes { get; }
/// <summary>

View File

@@ -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
/// </summary>
public abstract partial class NodeModelBase : IDynamicFlowNode
{
#region
/// <summary>
/// 不再中断
/// </summary>
public void CancelInterrupt()
{
this.DebugSetting.InterruptClass = InterruptClass.None;
DebugSetting.CancelInterruptCallback?.Invoke();
}
#endregion
#region /
/// <summary>
@@ -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
/// <returns></returns>
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
/// <summary>
/// 不再中断
/// </summary>
public void CancelInterrupt()
{
this.DebugSetting.InterruptClass = InterruptClass.None;
DebugSetting.CancelInterruptCallback?.Invoke();
}
#endregion
@@ -137,8 +135,6 @@ namespace Serein.Library
return false;
}
/// <summary>
/// 开始执行
/// </summary>
@@ -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
}
}
/// <summary>
/// 执行节点对应的方法
/// </summary>
@@ -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;
}
}
/// <summary>
/// 执行单个节点对应的方法,并不做状态检查
/// </summary>
/// <param name="env"></param>
/// <returns></returns>
public virtual async Task<object> 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;
}
}
/// <summary>
/// 获取对应的参数数组
/// </summary>
public static object[] GetParameters(IDynamicContext context, NodeModelBase nodeModel, MethodDetails md)
public static async Task<object[]> 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<object>().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;
}
/// <summary>
/// 更新节点数据,并检查监视表达式是否生效
/// </summary>
@@ -479,7 +561,6 @@ namespace Serein.Library
}
}
/// <summary>
/// 释放对象
/// </summary>

View File

@@ -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;
/// <summary>
/// 对应的节点
/// 所在的节点
/// </summary>
[PropertyInfo(IsProtection = true)]
private NodeModelBase _nodeModel;
@@ -29,7 +26,9 @@ namespace Serein.Library
private int _index;
/// <summary>
/// 是否为显式参数(固定值/表达式)
/// <para>是否为显式参数(固定值/表达式)</para>
/// <para>如果为 true 则使用UI输入的文本值作为入参数据过程中会尽可能转为类型需要的数据。</para>
/// <para>如果为 false ,则根据 ArgDataSourceType 调用相应节点的GetFlowData()方法,获取返回的数据作为入参数据。</para>
/// </summary>
[PropertyInfo(IsNotification = true)]
private bool _isExplicitData ;
@@ -41,7 +40,7 @@ namespace Serein.Library
private Func<object, object> _convertor ;
/// <summary>
/// 显式类型
/// 方法入参若无相关转换器特性标注则无需关注该变量。该变量用于需要用到枚举BinValue转换器时指示相应的入参变量需要转为的类型
/// </summary>
[PropertyInfo]
private Type _explicitType ;
@@ -56,7 +55,22 @@ namespace Serein.Library
private string _explicitTypeName ;
/// <summary>
/// 方法需要的类型
/// 入参数据来源。默认使用上一节点作为入参数据。
/// </summary>
[PropertyInfo(IsNotification = true)]
private ConnectionArgSourceType _argDataSourceType = ConnectionArgSourceType.GetPreviousNodeData;
/// <summary>
/// 当 ArgDataSourceType 不为 GetPreviousNodeData 时(从运行时上一节点获取数据)。
/// 则通过该集合对应的节点,获取其 FlowData 作为预处理的入参参数。
/// </summary>
[PropertyInfo(IsProtection = true)]
public NodeModelBase[] _argDataSourceNodeMoels;
/// <summary>
/// 方法入参需要的类型。
/// </summary>
[PropertyInfo]
private Type _dataType ;
@@ -74,7 +88,7 @@ namespace Serein.Library
private string _dataValue;
/// <summary>
/// 如果是引用类型,拷贝时不会发生改变
/// 只有当ExplicitTypeName 为 Select 时,才会需要该成员
/// </summary>
[PropertyInfo(IsNotification = true)]
private string[] _items ;
@@ -91,6 +105,7 @@ namespace Serein.Library
this.env = env;
this.NodeModel = nodeModel;
}
/// <summary>
/// 通过参数信息加载实体,用于加载项目文件、远程连接的场景
/// </summary>
@@ -109,12 +124,15 @@ namespace Serein.Library
/// <summary>
/// 用于创建元数据
/// </summary>
/// <param name="info">方法参数信息</param>
public ParameterDetails()
{
}
/// <summary>
/// 转为描述
/// </summary>
@@ -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;
}

View File

@@ -29,12 +29,12 @@ namespace Serein.Library
/// <summary>
/// 节点连接关系种类
/// </summary>
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,
};
}
}

View File

@@ -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
{
/// <summary>
/// 父类转为子类
/// </summary>
/// <param name="parent">父类对象</param>
/// <param name="childType">子类类型</param>
/// <returns></returns>
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;
}
/// <summary>
/// 集合类型转换为Array/List
/// </summary>
/// <param name="obj"></param>
/// <param name="targetType"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
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<T>
else if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(List<>))
{
var toListMethod = typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(targetElementType);
return toListMethod.Invoke(null, new object[] { collection });
}
// 判断目标类型是否是 HashSet<T>
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("输入对象不是集合或目标类型不支持");
}
}
}

View File

@@ -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); // 完成创建后注入实例需要的特性依赖项

View File

@@ -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
}
}
/// <summary>
/// 单独运行一个节点
/// </summary>
/// <param name="context"></param>
/// <param name="nodeGuid"></param>
/// <returns></returns>
public async Task<object> InvokeNodeAsync(string nodeGuid)
{
if(this.NodeModels.TryGetValue(nodeGuid, out var model))
{
return await model.ExecutingAsync(null);
}
else
{
return null;
}
}
/// <summary>
/// 退出
/// </summary>
@@ -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
/// <param name="fromNodeJunctionType">起始节点控制点</param>
/// <param name="toNodeJunctionType">目标节点控制点</param>
/// <param name="connectionType">连接关系</param>
public async Task<bool> ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, JunctionType toNodeJunctionType, ConnectionType connectionType)
public async Task<bool> 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
/// <param name="toNodeGuid">目标节点Guid</param>
/// <param name="connectionType">连接关系</param>
/// <exception cref="NotImplementedException"></exception>
public async Task<bool> RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
public async Task<bool> RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType)
{
// 获取起始节点与目标节点
var fromNode = GuidToModel(fromNodeGuid);
@@ -923,8 +992,6 @@ namespace Serein.NodeFlow.Env
return result;
}
/// <summary>
/// 获取方法描述
/// </summary>
@@ -1245,6 +1312,9 @@ namespace Serein.NodeFlow.Env
#region
/// <summary>
/// 加载指定路径的DLL文件
/// </summary>
@@ -1290,7 +1360,7 @@ namespace Serein.NodeFlow.Env
/// <param name="toNodeGuid">目标节点Model</param>
/// <param name="connectionType">连接关系</param>
/// <exception cref="NotImplementedException"></exception>
private async Task<bool> RemoteConnectAsync(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType)
private async Task<bool> 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;
}
/// <summary>
/// 检查连接
/// </summary>
/// <param name="fromNode">发起连接的起始节点</param>
/// <param name="toNode">要连接的目标节点</param>
/// <param name="fromNodeJunctionType">发起连接节点的控制点类型</param>
/// <param name="toNodeJunctionType">被连接节点的控制点类型</param>
/// <returns></returns>
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);
}
/// <summary>
/// 连接节点
/// </summary>
/// <param name="fromNode">起始节点</param>
/// <param name="toNode">目标节点</param>
/// <param name="connectionType">连接关系</param>
private async Task<bool> 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
}
/// <summary>
/// 连接节点参数
/// </summary>
/// <param name="fromNode"></param>
/// <param name="toNode"></param>
/// <param name="connectionArgSourceType"></param>
/// <param name="argIndex"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 更改起点节点
/// </summary>

View File

@@ -180,9 +180,14 @@ namespace Serein.NodeFlow.Env
currentFlowEnvironment.ClearAll();
}
public async Task<bool> ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, JunctionType toNodeJunctionType, ConnectionType connectionType)
public async Task<bool> 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<bool> RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
public async Task<bool> 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<object> InvokeNodeAsync(string nodeGuid)
{
return await currentFlowEnvironment.InvokeNodeAsync(nodeGuid);
}
public async Task StartRemoteServerAsync(int port = 7525)
{
await currentFlowEnvironment.StartRemoteServerAsync(port);

View File

@@ -107,14 +107,14 @@ namespace Serein.NodeFlow.Env
/// <param name="flowStateType"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
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
/// <returns></returns>
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)
{

View File

@@ -340,7 +340,7 @@ namespace Serein.NodeFlow.Env
[AutoSocketHandle(ThemeValue = EnvMsgTheme.ConnectNode)]
public async Task<object> ConnectNode(string fromNodeGuid, string toNodeGuid, string connectionType)
{
if (!EnumHelper.TryConvertEnum<ConnectionType>(connectionType, out var tmpConnectionType))
if (!EnumHelper.TryConvertEnum<ConnectionInvokeType>(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<object> RemoveConnect(string fromNodeGuid, string toNodeGuid, string connectionType)
{
if (!EnumHelper.TryConvertEnum<ConnectionType>(connectionType, out var tmpConnectionType))
if (!EnumHelper.TryConvertEnum<ConnectionInvokeType>(connectionType, out var tmpConnectionType))
{
return new
{

View File

@@ -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<bool> ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, JunctionType toNodeJunctionType, ConnectionType connectionType)
public async Task<object> InvokeNodeAsync(string nodeGuid)
{
Console.WriteLine("远程环境尚未实现接口 InvokeNodeAsync");
_ = msgClient.SendAsync(EnvMsgTheme.SetStartNode, new
{
nodeGuid
});
return null;
}
public async Task<bool> ConnectNodeAsync(string fromNodeGuid,
string toNodeGuid,
JunctionType fromNodeJunctionType,
JunctionType toNodeJunctionType,
ConnectionInvokeType connectionType,
int argIndex = 0)
{
var result = await msgClient.SendAndWaitDataAsync<bool>(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<bool> RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
public async Task<bool> RemoveConnectAsync(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType)
{
var result = await msgClient.SendAndWaitDataAsync<bool>(EnvMsgTheme.RemoveConnect, new
{
@@ -486,6 +504,7 @@ namespace Serein.NodeFlow.Env
{
OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNodeGuid,
toNodeGuid,
JunctionOfConnectionType.Invoke,
connectionType,
NodeConnectChangeEventArgs.ConnectChangeType.Remote));
});

View File

@@ -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--)
{
// 筛选出启用的节点

View File

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

View File

@@ -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<ConnectionType> JudgeAsync(IDynamicContext context, SingleConditionNode node)
private async Task<ConnectionInvokeType> 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();

View File

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

View File

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

View File

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

View File

@@ -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;");

View File

@@ -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
/// <summary>
/// 当前正在绘制的连接线
/// </summary>
private Line? currentLine;
//private Line? currentLine;
/// <summary>
/// 当前正在绘制的真假分支属性
/// </summary>
private ConnectionType currentConnectionType;
private ConnectionInvokeType currentConnectionType;
/// <summary>
@@ -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
}
/// <summary>
/// 开始创建连接 True线 操作,设置起始块和绘制连接线。
/// </summary>
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
/// </summary>
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; // 防止事件传播影响其他控件
}
}
/// <summary>
/// 控件的鼠标移动事件,根据鼠标拖动更新控件的位置。批量移动计算移动逻辑。
/// </summary>
@@ -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();
}
/// <summary>
@@ -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;
//}
}
/// <summary>
/// 更新与指定控件相关的所有连接的位置。
/// </summary>
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
/// <param name="e"></param>
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
}
/// <summary>
/// 在画布中释放鼠标按下,结束选取状态
/// 在画布中释放鼠标按下,结束选取状态 / 停止创建连线,尝试连接节点
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
@@ -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);
}

View File

@@ -10,14 +10,14 @@
d:DataContext="{d:DesignInstance vm:ActionNodeControlViewModel}"
mc:Ignorable="d"
MaxWidth="300">
<UserControl.Resources>
<!--<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />-->
<Converters:InvertableBooleanToVisibilityConverter x:Key="InvertedBoolConverter"/>
<!--<ResourceDictionary Source="/Serein.Workbench;Node/View/NodeExecuteJunctionControl.xaml" x:Key="NodeExecuteJunctionControl"/>-->
</UserControl.Resources>
<Border BorderBrush="#8DE9FD" BorderThickness="1">
<Border BorderBrush="#8DE9FD" BorderThickness="4">
<Grid>
@@ -28,10 +28,10 @@
<!--<TextBlock Text="{Binding NodelModel.DebugSetting.IsInterrupt}}"></TextBlock>-->
<Border x:Name="InterruptBorder">
<Border.Style>
<Style TargetType="Border">
<!--默认无边框-->
<!--默认无边框-->
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Style.Triggers>
@@ -52,24 +52,32 @@
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Background="#8DE9FD" >
<!--<Grid Grid.Row="0" Background="#8DE9FD" >-->
<Grid Grid.Row="0" >
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<local:ExecuteJunctionControl Grid.Column="0" NodeGuid="{Binding NodeModel.Guid}" x:Name="ExecuteJunctionControl"/>
<TextBlock Grid.Column="1" Text="{Binding NodeModel.MethodDetails.MethodTips, Mode=TwoWay}" HorizontalAlignment="Center"/>
<local:ResultJunctionControl Grid.Column="2" NodeGuid="{Binding NodeModel.Guid}" x:Name="ResultJunctionControl"/>
<local:ExecuteJunctionControl Grid.Column="0" MyNode="{Binding NodeModel}" x:Name="ExecuteJunctionControl" HorizontalAlignment="Left" Grid.RowSpan="2"/>
<StackPanel Grid.Column="1" Grid.RowSpan="2" >
<TextBlock Text="{Binding NodeModel.MethodDetails.MethodTips, Mode=TwoWay}" HorizontalAlignment="Center"/>
</StackPanel>
<local:NextStepJunctionControl Grid.Column="2" MyNode="{Binding NodeModel}" x:Name="NextStepJunctionControl" HorizontalAlignment="Right" Grid.RowSpan="2"/>
</Grid>
<!--<StackPanel Background="#8DE9FD" >
</StackPanel>-->
<themes:MethodDetailsControl Grid.Row="2" MethodDetails="{Binding NodeModel.MethodDetails}"/>
<themes:MethodDetailsControl x:Name="MethodDetailsControl" Grid.Row="2" MethodDetails="{Binding NodeModel.MethodDetails}"/>
<!-- ParameterProtectionMask 参数保护 -->
<!--取反 Visibility="{Binding DebugSetting.IsEnable, Converter={StaticResource InvertedBoolConverter}, ConverterParameter=Inverted}"-->
<Border Grid.Row="2" x:Name="ParameterProtectionMask" Background="LightBlue" Opacity="0.5" BorderBrush="#0A4651" BorderThickness="0"
@@ -79,16 +87,21 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderThickness="1">
<TextBlock Text="result" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock Text="result ->" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<Border Grid.Column="1" BorderThickness="1">
<TextBlock Text="{Binding NodeModel.MethodDetails.ReturnType.FullName, Mode=OneTime}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<TextBlock Text="{Binding NodeModel.MethodDetails.ReturnType.FullName, Mode=OneTime}" TextTrimming="CharacterEllipsis" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Border>
<Border Grid.Column="2" BorderThickness="1">
<local:ResultJunctionControl Grid.Column="2" MyNode="{Binding NodeModel}" x:Name="ResultJunctionControl" HorizontalAlignment="Right"/>
</Border>
</Grid>
<Grid Grid.Row="4" Background="#8DE9FD" >
<Grid Grid.Row="4" Background="Azure" >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>

View File

@@ -1,21 +1,94 @@
using Serein.NodeFlow.Model;
using Serein.Workbench.Node.ViewModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Serein.Workbench.Node.View
{
/// <summary>
/// ActionNode.xaml 的交互逻辑
/// </summary>
public partial class ActionNodeControl : NodeControlBase
public partial class ActionNodeControl : NodeControlBase, INodeJunction
{
public ActionNodeControl(ActionNodeControlViewModel viewModel) : base(viewModel)
{
DataContext = viewModel;
InitializeComponent();
ExecuteJunctionControl.NodeGuid = viewModel.NodeModel.Guid;
ExecuteJunctionControl.MyNode.Guid = viewModel.NodeModel.Guid;
}
/// <summary>
/// 入参控制点(可能有,可能没)
/// </summary>
JunctionControlBase INodeJunction.ExecuteJunction => this.ExecuteJunctionControl;
/// <summary>
/// 下一个调用方法控制点(可能有,可能没)
/// </summary>
JunctionControlBase INodeJunction.NextStepJunction => this.NextStepJunctionControl;
/// <summary>
/// 返回值控制点(可能有,可能没)
/// </summary>
JunctionControlBase INodeJunction.ReturnDataJunction => this.ResultJunctionControl;
/// <summary>
/// 方法入参控制点(可能有,可能没)
/// </summary>
JunctionControlBase[] INodeJunction.ArgDataJunction { get {
if(argDataJunction == null)
{
// 获取 MethodDetailsControl 实例
var methodDetailsControl = this.MethodDetailsControl;
argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length];
var itemsControl = FindVisualChild<ItemsControl>(methodDetailsControl); // 查找 ItemsControl
if (itemsControl != null)
{
var controls = new List<JunctionControlBase>();
for (int i = 0; i < itemsControl.Items.Count; i++)
{
var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
if (container != null)
{
var argControl = FindVisualChild<ArgJunctionControl>(container);
if (argControl != null)
{
controls.Add(argControl); // 收集 ArgJunctionControl 实例
}
}
}
argDataJunction = controls.ToArray();
}
}
return argDataJunction;
} }
/// <summary>
/// 方法入参控制点(可能有,可能没)
/// </summary>
private JunctionControlBase[] argDataJunction;
private T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is T typedChild)
{
return typedChild;
}
var childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
return null;
}
}
}

View File

@@ -28,25 +28,27 @@
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid Background="#E3FDFD">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<view:ArgJunctionControl Grid.Column="0" ArgIndex="{Binding Index}" NodeGuid="{Binding NodeModel.Guid}" />
<TextBlock Grid.Column="1" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox Grid.Column="2" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
<TextBlock Grid.Column="3" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<TextBlock Grid.Column="4" MinWidth="50" Text="无须指定参数"/>
</Grid>
<StackPanel Orientation="Horizontal">
<view:ArgJunctionControl x:Name="ArgJunctionControl" Grid.Column="0" ArgIndex="{Binding Index}" MyNode="{Binding NodeModel}" />
<Grid Background="#E3FDFD">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<TextBlock Grid.Column="3" MinWidth="50" Text="无须指定参数"/>
</Grid>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</MultiDataTrigger>
<!--指定参数:选项类型-->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
@@ -56,23 +58,24 @@
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid Background="#E3FDFD">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<view:ArgJunctionControl Grid.Column="0" ArgIndex="{Binding Index}" NodeGuid="{Binding NodeModel.Guid}" />
<TextBlock Grid.Column="1" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox Grid.Column="2" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
<TextBlock Grid.Column="3" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<ComboBox Grid.Column="4"
<StackPanel Orientation="Horizontal">
<view:ArgJunctionControl x:Name="ArgJunctionControl" Grid.Column="0" ArgIndex="{Binding Index}" MyNode="{Binding NodeModel}" />
<Grid Background="#E3FDFD">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<ComboBox Grid.Column="3"
MinWidth="50"
ItemsSource="{Binding Items}"
SelectedItem="{Binding DataValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Grid>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
@@ -86,20 +89,22 @@
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid Background="#E3FDFD">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<view:ArgJunctionControl Grid.Column="0" ArgIndex="{Binding Index}" NodeGuid="{Binding NodeModel.Guid}" />
<TextBlock Grid.Column="1" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox Grid.Column="2" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
<TextBlock Grid.Column="3" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<TextBox Grid.Column="4" MinWidth="50" Text="{Binding DataValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
<StackPanel Orientation="Horizontal">
<view:ArgJunctionControl x:Name="ArgJunctionControl" Grid.Column="0" ArgIndex="{Binding Index}" MyNode="{Binding NodeModel}" />
<Grid Background="#E3FDFD">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<TextBox Grid.Column="3" MinWidth="50" Text="{Binding DataValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>

View File

@@ -31,7 +31,7 @@ namespace Serein.Workbench.Themes
private class NodeTreeModel
{
public NodeModelBase RootNode { get; set; }
public Dictionary<ConnectionType, List<NodeModelBase>> ChildNodes { get; set; }
public Dictionary<ConnectionInvokeType, List<NodeModelBase>> ChildNodes { get; set; }
}
@@ -48,12 +48,12 @@ namespace Serein.Workbench.Themes
NodeTreeModel nodeTreeModel = new NodeTreeModel
{
RootNode = rootNodeModel,
ChildNodes = new Dictionary<ConnectionType, List<NodeModelBase>>()
ChildNodes = new Dictionary<ConnectionInvokeType, List<NodeModelBase>>()
{
{ConnectionType.Upstream, []},
{ConnectionType.IsSucceed, [rootNodeModel]},
{ConnectionType.IsFail, []},
{ConnectionType.IsError, []},
{ConnectionInvokeType.Upstream, []},
{ConnectionInvokeType.IsSucceed, [rootNodeModel]},
{ConnectionInvokeType.IsFail, []},
{ConnectionInvokeType.IsError, []},
}
};
string? itemName = rootNodeModel.MethodDetails?.MethodTips;
@@ -231,25 +231,25 @@ namespace Serein.Workbench.Themes
item.Items.Clear();
}
}
public static TreeView ToTreeView(NodeTreeItemViewControl item, ConnectionType connectionType)
public static TreeView ToTreeView(NodeTreeItemViewControl item, ConnectionInvokeType connectionType)
{
return connectionType switch
{
ConnectionType.Upstream => item.UpstreamTreeNodes,
ConnectionType.IsError => item.IsErrorTreeNodes,
ConnectionType.IsFail => item.IsFailTreeNodes,
ConnectionType.IsSucceed => item.IsSucceedTreeNodes,
ConnectionInvokeType.Upstream => item.UpstreamTreeNodes,
ConnectionInvokeType.IsError => item.IsErrorTreeNodes,
ConnectionInvokeType.IsFail => item.IsFailTreeNodes,
ConnectionInvokeType.IsSucceed => item.IsSucceedTreeNodes,
_ => throw new Exception("LoadNodeItem Error ConnectionType is " + connectionType)
};
}
public static Grid ToGridView(NodeTreeItemViewControl item, ConnectionType connectionType)
public static Grid ToGridView(NodeTreeItemViewControl item, ConnectionInvokeType connectionType)
{
return connectionType switch
{
ConnectionType.Upstream => item.UpstreamTreeGuid,
ConnectionType.IsError => item.IsErrorTreeGuid,
ConnectionType.IsFail => item.IsFailTreeGuid,
ConnectionType.IsSucceed => item.IsSucceedTreeGuid,
ConnectionInvokeType.Upstream => item.UpstreamTreeGuid,
ConnectionInvokeType.IsError => item.IsErrorTreeGuid,
ConnectionInvokeType.IsFail => item.IsFailTreeGuid,
ConnectionInvokeType.IsSucceed => item.IsSucceedTreeGuid,
_ => throw new Exception("LoadNodeItem Error ConnectionType is " + connectionType)
};
}

View File

@@ -0,0 +1,34 @@
using Serein.Workbench.Node.View;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Serein.Workbench.Node
{
/// <summary>
/// 约束一个节点应该有哪些控制点
/// </summary>
public interface INodeJunction
{
/// <summary>
/// 方法执行入口控制点
/// </summary>
JunctionControlBase ExecuteJunction { get; }
/// <summary>
/// 执行完成后下一个要执行的方法控制点
/// </summary>
JunctionControlBase NextStepJunction { get; }
/// <summary>
/// 参数节点控制点
/// </summary>
JunctionControlBase[] ArgDataJunction { get; }
/// <summary>
/// 返回值控制点
/// </summary>
JunctionControlBase ReturnDataJunction { get; }
}
}

View File

@@ -0,0 +1,222 @@
using Serein.Library;
using Serein.Workbench.Extension;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Serein.Workbench.Node.View
{
/// <summary>
/// 连接线的类型
/// </summary>
public enum LineType
{
/// <summary>
/// 贝塞尔曲线
/// </summary>
Bezier,
/// <summary>
/// 半圆线
/// </summary>
Semicircle,
}
/// <summary>
/// 贝塞尔曲线
/// </summary>
public class BezierLine : Shape
{
private readonly double strokeThickness;
private readonly LineType lineType;
/// <summary>
/// 确定起始坐标和目标坐标、外光样式的曲线
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
/// <param name="brush"></param>
/// <param name="strokeThickness"></param>
public BezierLine(LineType lineType, Point start, Point end, Brush brush, double strokeThickness = 4)
{
this.lineType = lineType;
this.brush = brush;
startPoint = start;
endPoint = end;
this.strokeThickness = strokeThickness;
InitElementPoint();
InvalidateVisual(); // 触发重绘
}
public void InitElementPoint()
{
hitVisiblePen = new Pen(Brushes.Transparent, 1.0); // 初始化碰撞检测线
hitVisiblePen.Freeze(); // Freeze以提高性能
visualPen = new Pen(brush, 3.0); // 默认可视化Pen
visualPen.Freeze(); // Freeze以提高性能
linkSize = 4; // 整线条粗细
Panel.SetZIndex(this, -9999999); // 置底
}
/// <summary>
/// 更新线条落点位置
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
public void UpdatePoints(Point start, Point end)
{
startPoint = start;
endPoint = end;
InvalidateVisual(); // 触发重绘
}
/// <summary>
/// 更新线条落点位置
/// </summary>
/// <param name="point"></param>
public void UpdateEndPoints(Point point)
{
endPoint = point;
InvalidateVisual(); // 触发重绘
}
/// <summary>
/// 更新线条落点位置
/// </summary>
/// <param name="point"></param>
public void UpdateStartPoints(Point point)
{
startPoint = point;
InvalidateVisual(); // 触发重绘
}
/// <summary>
/// 控件重绘事件
/// </summary>
/// <param name="drawingContext"></param>
protected override void OnRender(DrawingContext drawingContext)
{
// 刷新线条显示位置
switch (this.lineType)
{
case LineType.Bezier:
DrawBezierCurve(drawingContext, startPoint, endPoint, linkSize);
break;
case LineType.Semicircle:
DrawBezierCurve(drawingContext, startPoint, endPoint);
break;
default:
break;
}
}
private readonly StreamGeometry streamGeometry = new StreamGeometry();
private Point rightCenterOfStartLocation; // 目标节点选择左侧边缘中心
private Point leftCenterOfEndLocation; // 起始节点选择右侧边缘中心
private Pen hitVisiblePen; // 初始化碰撞检测线
private Pen visualPen; // 默认可视化Pen
private Point startPoint; // 连接线的起始节点
private Point endPoint; // 连接线的终点
private Brush brush; // 线条颜色
double linkSize; // 根据缩放比例调整线条粗细
protected override Geometry DefiningGeometry => streamGeometry;
#region
private Point c0, c1; // 用于计算贝塞尔曲线控制点逻辑
private Vector axis = new Vector(1, 0);
private Vector startToEnd;
private void DrawBezierCurve(DrawingContext drawingContext,
Point start,
Point end,
double linkSize,
bool isHitTestVisible = false,
double strokeThickness = 1.0,
bool isMouseOver = false,
double dashOffset = 0.0)
{
// 控制点的计算逻辑
double power = 100; // 控制贝塞尔曲线的“拉伸”强度
// 计算轴向向量与起点到终点的向量
//var axis = new Vector(1, 0);
startToEnd = (end.ToVector() - start.ToVector()).NormalizeTo();
// 计算拉伸程度k拉伸与水平夹角正相关
var k = 1 - Math.Pow(Math.Max(0, axis.DotProduct(startToEnd)), 10.0);
// 如果起点x大于终点x增加额外的偏移量避免重叠
var bias = start.X > end.X ? Math.Abs(start.X - end.X) * 0.25 : 0;
// 控制点的实际计算
c0 = new Point(+(power + bias) * k + start.X, start.Y);
c1 = new Point(-(power + bias) * k + end.X, end.Y);
// 准备StreamGeometry以用于绘制曲线
streamGeometry.Clear();
using (var context = streamGeometry.Open())
{
context.BeginFigure(start, true, false); // 曲线起点
context.BezierTo(c0, c1, end, true, false); // 画贝塞尔曲线
}
drawingContext.DrawGeometry(null, visualPen, streamGeometry);
// 绘制碰撞检测线
//if (true)
//{
// //hitVisiblePen = new Pen(Brushes.Transparent, linkSize + strokeThickness);
// //hitVisiblePen.Freeze();
// drawingContext.DrawGeometry(null, hitVisiblePen, streamGeometry);
//}
//else
//{
//}
}
#endregion
private void DrawBezierCurve(DrawingContext drawingContext, Point start, Point end)
{
// 计算中心点和半径
// 计算圆心和半径
double x = 35 ;
// 创建一个弧线路径
streamGeometry.Clear();
using (var context = streamGeometry.Open())
{
// 开始绘制
context.BeginFigure(start, false, false);
// 生成弧线
context.ArcTo(
end, // 结束点
new Size(x, x), // 椭圆的半径
0, // 椭圆的旋转角度
false, // 是否大弧
SweepDirection.Counterclockwise, // 方向
true, // 是否连接到起始点
true // 是否使用高质量渲染
);
// 结束绘制
context.LineTo(start, false, false); // 连接到起始点(可选)
}
// 绘制弧线
drawingContext.DrawGeometry(null, visualPen, streamGeometry);
}
}
}

View File

@@ -1,93 +0,0 @@
using Serein.Library;
using Serein.Library.Utils;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Serein.Workbench.Node.View
{
public abstract class JunctionControlBase : UserControl
{
public double _MyWidth = 20;
public double _MyHeight = 20;
protected JunctionControlBase()
{
//this.Width = 20;
//this.Height = 20;
this.MouseDown += ControlPointBase_MouseDown;
this.MouseMove += ControlPointBase_MouseMove; ;
}
#region
public static readonly DependencyProperty NodeGuidProperty =
DependencyProperty.Register("NodeGuid", typeof(string), typeof(JunctionControlBase), new PropertyMetadata(default(string)));
/// <summary>
/// 所在的节点
/// </summary>
public string NodeGuid
{
get { return (string)GetValue(NodeGuidProperty); }
set { SetValue(NodeGuidProperty, value.ToString()); }
}
#endregion
#region
public static readonly DependencyProperty JunctionTypeProperty =
DependencyProperty.Register("JunctionType", typeof(string), typeof(JunctionControlBase), new PropertyMetadata(default(string)));
public JunctionType JunctionType
{
get { return EnumHelper.ConvertEnum<JunctionType>(GetValue(JunctionTypeProperty).ToString()); }
set { SetValue(JunctionTypeProperty, value.ToString()); }
}
#endregion
public abstract void Render();
private void ControlPointBase_MouseMove(object sender, MouseEventArgs e)
{
if (GlobalJunctionData.MyGlobalData is null) return;
GlobalJunctionData.MyGlobalData.ChangingJunction = this;
}
/// <summary>
/// 在碰撞点上按下鼠标控件开始进行移动
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ControlPointBase_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
GlobalJunctionData.MyGlobalData = new ConnectingData();
var myDataType = GlobalJunctionData.MyGlobalData;
myDataType.StartJunction = this;
var canvas = MainWindow.GetParentOfType<Canvas>(this);
myDataType.StartPoint = this.TranslatePoint(new Point(this.Width /2 , this.Height /2 ), canvas);
myDataType.VirtualLine = new MyLine(canvas, new Line // 虚拟线
{
Stroke = Brushes.OldLace,
StrokeThickness = 2
});
}
e.Handled = true;
}
}
}

View File

@@ -0,0 +1,134 @@
using Serein.Library;
using Serein.Library.Utils;
using System;
using System.Net;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Serein.Workbench.Node.View
{
public abstract class JunctionControlBase : Shape
{
protected JunctionControlBase()
{
this.Width = 25;
this.Height = 20;
this.MouseDown += ControlPointBase_MouseDown;
this.MouseMove += ControlPointBase_MouseMove;
}
#region
public static readonly DependencyProperty NodeProperty =
DependencyProperty.Register(nameof(MyNode), typeof(NodeModelBase), typeof(JunctionControlBase), new PropertyMetadata(default(NodeModelBase)));
/// <summary>
/// 所在的节点
/// </summary>
public NodeModelBase MyNode
{
get { return (NodeModelBase)GetValue(NodeProperty); }
set { SetValue(NodeProperty, value); }
}
#endregion
#region
public static readonly DependencyProperty JunctionTypeProperty =
DependencyProperty.Register(nameof(JunctionType), typeof(string), typeof(JunctionControlBase), new PropertyMetadata(default(string)));
/// <summary>
/// 控制点类型
/// </summary>
public JunctionType JunctionType
{
get { return EnumHelper.ConvertEnum<JunctionType>(GetValue(JunctionTypeProperty).ToString()); }
set { SetValue(JunctionTypeProperty, value.ToString()); }
}
#endregion
protected readonly StreamGeometry StreamGeometry = new StreamGeometry();
protected override Geometry DefiningGeometry => StreamGeometry;
/// <summary>
/// 重绘方法
/// </summary>
/// <param name="drawingContext"></param>
public abstract void Render(DrawingContext drawingContext);
/// <summary>
/// 中心点
/// </summary>
public abstract Point MyCenterPoint { get; }
// 处理鼠标悬停状态
private bool _isMouseOver;
public bool IsMouseOver
{
get => _isMouseOver;
set
{
_isMouseOver = value;
InvalidateVisual();
}
}
/// <summary>
/// 控件重绘事件
/// </summary>
/// <param name="drawingContext"></param>
protected override void OnRender(DrawingContext drawingContext)
{
Render(drawingContext);
}
protected void ControlPointBase_MouseMove(object sender, MouseEventArgs e)
{
if (GlobalJunctionData.MyGlobalConnectingData is null) return;
GlobalJunctionData.MyGlobalConnectingData.CurrentJunction = this;
}
/// <summary>
/// 在碰撞点上按下鼠标控件开始进行移动
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void ControlPointBase_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
GlobalJunctionData.MyGlobalConnectingData = new ConnectingData();
var myDataType = GlobalJunctionData.MyGlobalConnectingData;
myDataType.StartJunction = this;
var canvas = MainWindow.GetParentOfType<Canvas>(this);
if (canvas != null)
{
//myDataType.StartPoint = this.MyCenterPoint;
myDataType.StartPoint = this.TranslatePoint(new Point(this.Width / 2, this.Height / 2), canvas);
var bezierLine = new BezierLine(LineType.Bezier, myDataType.StartPoint, myDataType.StartPoint, Brushes.Green);
myDataType.VirtualLine = new MyLine(canvas, bezierLine);
}
}
e.Handled = true;
}
private Point GetStartPoint()
{
return new Point(this.ActualWidth / 2, this.ActualHeight / 2); // 起始节点选择右侧边缘中心
}
}
}

View File

@@ -13,7 +13,7 @@ namespace Serein.Workbench.Node.View
#region Model
public class MyLine
{
public MyLine(Canvas canvas, Line line)
public MyLine(Canvas canvas, BezierLine line)
{
Canvas = canvas;
VirtualLine = line;
@@ -21,7 +21,7 @@ namespace Serein.Workbench.Node.View
}
public Canvas Canvas { get; set; }
public Line VirtualLine { get; set; }
public BezierLine VirtualLine { get; set; }
public void Remove()
{
@@ -32,33 +32,98 @@ namespace Serein.Workbench.Node.View
public class ConnectingData
{
public JunctionControlBase StartJunction { get; set; }
public JunctionControlBase ChangingJunction { get; set; }
public JunctionControlBase CurrentJunction { get; set; }
public Point StartPoint { get; set; }
public MyLine VirtualLine { get; set; }
}
public static class GlobalJunctionData
{
private static ConnectingData? myGlobalData;
/// <summary>
/// 是否允许连接
/// </summary>
public static ConnectingData? MyGlobalData
{
get => myGlobalData;
set
public bool IsCanConnected { get
{
if (myGlobalData == null)
if(StartJunction is null
|| CurrentJunction is null
)
{
myGlobalData = value;
return false;
}
if (!StartPoint.Equals(CurrentJunction))
{
return true;
}
else
{
// 自己连接自己的情况下只能是从arg控制点连接到execute控制点。
if (CurrentJunction.JunctionType == Library.JunctionType.Execute
&& StartJunction.JunctionType == Library.JunctionType.ArgData)
{
return true;
}
if (CurrentJunction.JunctionType == Library.JunctionType.ArgData
&& StartJunction.JunctionType == Library.JunctionType.Execute)
{
// 需要是自己连接自己且只能是从arg控制点连接到execute控制点。
return true;
}
return false;
}
}
}
/// <summary>
/// 更新临时的连接线
/// </summary>
/// <param name="point"></param>
public void UpdatePoint(Point point)
{
if (StartJunction is null
|| CurrentJunction is null
)
{
return;
}
if (StartJunction.JunctionType == Library.JunctionType.Execute
|| StartJunction.JunctionType == Library.JunctionType.ArgData)
{
VirtualLine.VirtualLine.UpdateStartPoints(point);
}
else
{
VirtualLine.VirtualLine.UpdateEndPoints(point);
}
}
public static bool IsCreatingConnection => myGlobalData is not null;
}
public static bool CanCreate => myGlobalData?.ChangingJunction.Equals(myGlobalData?.StartJunction) == false;
public static class GlobalJunctionData
{
private static ConnectingData? myGlobalData;
private static object _lockObj = new object();
/// <summary>
/// 创建节点之间控制点的连接行为
/// </summary>
public static ConnectingData? MyGlobalConnectingData
{
get => myGlobalData;
set
{
lock (_lockObj)
{
myGlobalData ??= value;
}
}
}
/// <summary>
/// 删除连接视觉效果
/// </summary>
public static void OK()
{
myGlobalData?.VirtualLine.Remove();

View File

@@ -10,7 +10,7 @@ namespace Serein.Workbench.Node.View
public ArgJunctionControl()
{
base.JunctionType = JunctionType.ArgData;
Render();
this.InvalidateVisual();
}
#region
@@ -25,28 +25,50 @@ namespace Serein.Workbench.Node.View
get { return (int)GetValue(ArgIndexProperty); }
set { SetValue(ArgIndexProperty, value); }
}
#endregion
public override void Render()
private Point _myCenterPoint;
public override Point MyCenterPoint { get => _myCenterPoint; }
public override void Render(DrawingContext drawingContext)
{
if(double.IsNaN(base.Width))
{
base.Width = base._MyWidth;
}
if (double.IsNaN(base.Height))
{
base.Height = base._MyHeight;
}
double width = ActualWidth;
double height = ActualHeight;
// 输入连接器的背景
var connectorBackground = IsMouseOver ? Brushes.DarkCyan : Brushes.Transparent;
var connectorRect = new Rect(4, 4, width - 8, height - 8);
drawingContext.DrawRectangle(connectorBackground, null, connectorRect);
// 定义圆形的大小和位置
double connectorSize = 10; // 连接器的大小
double circleCenterX = 8; // 圆心 X 坐标
double circleCenterY = height / 2; // 圆心 Y 坐标
var circlePoint = new Point(circleCenterX, circleCenterY);
// 绘制连接器的圆形部分
var ellipse = new EllipseGeometry(circlePoint, connectorSize / 2, connectorSize / 2);
_myCenterPoint = new Point(circleCenterX - connectorSize / 2, circleCenterY);
drawingContext.DrawGeometry(IsMouseOver ? Brushes.DarkCyan : Brushes.Transparent, new Pen(Brushes.Black, 1), ellipse);
var ellipse = new Ellipse
// 定义三角形的间距
double triangleOffsetX = 4; // 三角形与圆形的间距
double triangleCenterX = circleCenterX + connectorSize / 2 + triangleOffsetX; // 三角形中心 X 坐标
double triangleCenterY = circleCenterY; // 三角形中心 Y 坐标
// 绘制三角形
var pathGeometry = new StreamGeometry();
using (var context = pathGeometry.Open())
{
Width = base.Width,
Height = base.Height,
Fill = Brushes.Orange,
ToolTip = "入参"
};
Content = ellipse;
context.BeginFigure(new Point(triangleCenterX, triangleCenterY - 4.5), true, true);
context.LineTo(new Point(triangleCenterX + 5, triangleCenterY), true, false);
context.LineTo(new Point(triangleCenterX, triangleCenterY + 4.5), true, false);
context.LineTo(new Point(triangleCenterX, triangleCenterY - 4.5), true, false);
}
drawingContext.DrawGeometry(IsMouseOver ? Brushes.DarkCyan : Brushes.Transparent, new Pen(Brushes.Black, 1), pathGeometry);
}
}

View File

@@ -1,4 +1,6 @@
using System.Windows.Media;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using Serein.Library;
@@ -6,32 +8,75 @@ namespace Serein.Workbench.Node.View
{
public class ExecuteJunctionControl : JunctionControlBase
{
//public override JunctionType JunctionType { get; } = JunctionType.Execute;
public ExecuteJunctionControl()
{
base.JunctionType = JunctionType.Execute;
Render();
this.InvalidateVisual();
}
public override void Render()
private Point _myCenterPoint;
public override Point MyCenterPoint { get => _myCenterPoint; }
public override void Render(DrawingContext drawingContext)
{
if (double.IsNaN(base.Width))
{
base.Width = base._MyWidth;
}
if (double.IsNaN(base.Height))
{
base.Height = base._MyHeight;
}
double width = ActualWidth;
double height = ActualHeight;
var rect = new Rectangle
// 绘制边框
//var borderBrush = new SolidColorBrush(Colors.Black);
//var borderThickness = 1.0;
//var borderRect = new Rect(0, 0, width, height);
//drawingContext.DrawRectangle(null, new Pen(borderBrush, borderThickness), borderRect);
// 输入连接器的背景
var connectorBackground = IsMouseOver ? Brushes.DarkCyan : Brushes.Transparent;
var connectorRect = new Rect(4, 4, width - 8, height - 8);
drawingContext.DrawRectangle(connectorBackground, null, connectorRect);
// 定义圆形的大小和位置
double connectorSize = 10; // 连接器的大小
double circleCenterX = 8; // 圆心 X 坐标
double circleCenterY = height / 2; // 圆心 Y 坐标
var circlePoint = new Point(circleCenterX, circleCenterY);
// 绘制连接器的圆形部分
var ellipse = new EllipseGeometry(circlePoint, connectorSize / 2, connectorSize / 2);
_myCenterPoint = new Point(circleCenterX - connectorSize / 2, circleCenterY);
drawingContext.DrawGeometry(IsMouseOver ? Brushes.DarkCyan : Brushes.Transparent, new Pen(Brushes.Black, 1), ellipse);
// 定义三角形的间距
double triangleOffsetX = 4; // 三角形与圆形的间距
double triangleCenterX = circleCenterX + connectorSize / 2 + triangleOffsetX; // 三角形中心 X 坐标
double triangleCenterY = circleCenterY; // 三角形中心 Y 坐标
// 绘制三角形
var pathGeometry = new StreamGeometry();
using (var context = pathGeometry.Open())
{
Width = base.Width,
Height = base.Height,
Fill = Brushes.Green,
ToolTip = "方法执行"
};
Content = rect;
context.BeginFigure(new Point(triangleCenterX, triangleCenterY - 4.5), true, true);
context.LineTo(new Point(triangleCenterX + 5, triangleCenterY), true, false);
context.LineTo(new Point(triangleCenterX, triangleCenterY + 4.5), true, false);
context.LineTo(new Point(triangleCenterX, triangleCenterY - 4.5), true, false);
}
drawingContext.DrawGeometry(IsMouseOver ? Brushes.DarkCyan : Brushes.Transparent, new Pen(Brushes.Black, 1), pathGeometry);
// 绘制标签
//var formattedText = new FormattedText(
// "执行",
// System.Globalization.CultureInfo.CurrentCulture,
// FlowDirection.LeftToRight,
// new Typeface("Segoe UI"),
// 12,
// Brushes.Black,
// VisualTreeHelper.GetDpi(this).PixelsPerDip);
//drawingContext.DrawText(formattedText, new Point(18,1));
}
}

View File

@@ -1,4 +1,5 @@
using System.Windows.Media;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using Serein.Library;
@@ -11,28 +12,50 @@ namespace Serein.Workbench.Node.View
public NextStepJunctionControl()
{
base.JunctionType = JunctionType.NextStep;
Render();
this.InvalidateVisual();
}
public override void Render()
private Point _myCenterPoint;
public override Point MyCenterPoint { get => _myCenterPoint; }
public override void Render(DrawingContext drawingContext)
{
if (double.IsNaN(base.Width))
{
base.Width = base._MyWidth;
}
if (double.IsNaN(base.Height))
{
base.Height = base._MyHeight;
}
double width = ActualWidth;
double height = ActualHeight;
var rect = new Rectangle
// 输入连接器的背景
var connectorBackground = IsMouseOver ? Brushes.DarkCyan : Brushes.Transparent;
var connectorRect = new Rect(4, 4, width - 8, height - 8);
drawingContext.DrawRectangle(connectorBackground, null, connectorRect);
// 定义圆形的大小和位置
double connectorSize = 10; // 连接器的大小
double circleCenterX = 8; // 圆心 X 坐标
double circleCenterY = height / 2; // 圆心 Y 坐标
var circlePoint = new Point(circleCenterX, circleCenterY);
// 绘制连接器的圆形部分
var ellipse = new EllipseGeometry(circlePoint, connectorSize / 2, connectorSize / 2);
drawingContext.DrawGeometry(IsMouseOver ? Brushes.DarkCyan : Brushes.Transparent, new Pen(Brushes.Black, 1), ellipse);
_myCenterPoint = new Point(circleCenterX + connectorSize / 2, circleCenterY);
// 绘制连接器的圆形部分
//var ellipse = new EllipseGeometry(circlePoint, connectorSize / 2, connectorSize / 2);
// 定义三角形的间距
double triangleOffsetX = 4; // 三角形与圆形的间距
double triangleCenterX = circleCenterX + connectorSize / 2 + triangleOffsetX; // 三角形中心 X 坐标
double triangleCenterY = circleCenterY; // 三角形中心 Y 坐标
// 绘制三角形
var pathGeometry = new StreamGeometry();
using (var context = pathGeometry.Open())
{
Width = base.Width,
Height = base.Height,
Fill = Brushes.Blue,
ToolTip = "下一个方法值"
};
Content = rect;
context.BeginFigure(new Point(triangleCenterX, triangleCenterY - 4.5), true, true);
context.LineTo(new Point(triangleCenterX + 5, triangleCenterY), true, false);
context.LineTo(new Point(triangleCenterX, triangleCenterY + 4.5), true, false);
context.LineTo(new Point(triangleCenterX, triangleCenterY - 4.5), true, false);
}
drawingContext.DrawGeometry(IsMouseOver ? Brushes.DarkCyan : Brushes.Transparent, new Pen(Brushes.Black, 1), pathGeometry);
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Windows.Media;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using Serein.Library;
@@ -12,28 +13,47 @@ namespace Serein.Workbench.Node.View
public ResultJunctionControl()
{
base.JunctionType = JunctionType.ReturnData;
Render();
this.InvalidateVisual();
}
private Point _myCenterPoint;
public override Point MyCenterPoint { get => _myCenterPoint; }
public override void Render()
public override void Render(DrawingContext drawingContext)
{
if (double.IsNaN(base.Width))
{
base.Width = base._MyWidth;
}
if (double.IsNaN(base.Height))
{
base.Height = base._MyHeight;
}
double width = ActualWidth;
double height = ActualHeight;
var rect = new Rectangle
// 输入连接器的背景
var connectorBackground = IsMouseOver ? Brushes.DarkCyan : Brushes.Transparent;
var connectorRect = new Rect(4, 4, width - 8, height - 8);
drawingContext.DrawRectangle(connectorBackground, null, connectorRect);
// 定义圆形的大小和位置
double connectorSize = 10; // 连接器的大小
double circleCenterX = 8; // 圆心 X 坐标
double circleCenterY = height / 2; // 圆心 Y 坐标
var circlePoint = new Point(circleCenterX, circleCenterY);
// 绘制连接器的圆形部分
var ellipse = new EllipseGeometry(circlePoint, connectorSize / 2, connectorSize / 2);
_myCenterPoint = new Point(circleCenterX - connectorSize / 2, circleCenterY);
drawingContext.DrawGeometry(IsMouseOver ? Brushes.DarkCyan : Brushes.Transparent, new Pen(Brushes.Black, 1), ellipse);
// 定义三角形的间距
double triangleOffsetX = 4; // 三角形与圆形的间距
double triangleCenterX = circleCenterX + connectorSize / 2 + triangleOffsetX; // 三角形中心 X 坐标
double triangleCenterY = circleCenterY; // 三角形中心 Y 坐标
// 绘制三角形
var pathGeometry = new StreamGeometry();
using (var context = pathGeometry.Open())
{
Width = base.Width,
Height = base.Height,
Fill = Brushes.Red,
ToolTip = "返回值"
};
Content = rect;
context.BeginFigure(new Point(triangleCenterX, triangleCenterY - 4.5), true, true);
context.LineTo(new Point(triangleCenterX + 5, triangleCenterY), true, false);
context.LineTo(new Point(triangleCenterX, triangleCenterY + 4.5), true, false);
context.LineTo(new Point(triangleCenterX, triangleCenterY - 4.5), true, false);
}
drawingContext.DrawGeometry(IsMouseOver ? Brushes.DarkCyan : Brushes.Transparent, new Pen(Brushes.Black, 1), pathGeometry);
}
}
}

View File

@@ -15,6 +15,11 @@ namespace Serein.Workbench.Node.View
/// </summary>
public abstract class NodeControlBase : UserControl, IDynamicFlowNode
{
/// <summary>
/// 记录与该节点控件有关的所有连接
/// </summary>
private readonly List<ConnectionControl> connectionControls = new List<ConnectionControl>();
public NodeControlViewModelBase ViewModel { get; set; }
@@ -30,7 +35,51 @@ namespace Serein.Workbench.Node.View
SetBinding();
}
/// <summary>
/// 添加与该节点有关的连接后,记录下来
/// </summary>
/// <param name="connection"></param>
public void AddCnnection(ConnectionControl connection)
{
connectionControls.Add(connection);
}
/// <summary>
/// 删除了连接之后,还需要从节点中的记录移除
/// </summary>
/// <param name="connection"></param>
public void RemoveCnnection(ConnectionControl connection)
{
connectionControls.Remove(connection);
connection.Remote();
}
/// <summary>
/// 删除了连接之后,还需要从节点中的记录移除
/// </summary>
public void RemoveAllConection()
{
foreach (var connection in this.connectionControls)
{
connection.Remote(); // 主动更新连线位置
}
}
/// <summary>
/// 更新与该节点有关的数据
/// </summary>
public void UpdateLocationConnections()
{
foreach (var connection in this.connectionControls)
{
connection.RefreshLine(); // 主动更新连线位置
}
}
/// <summary>
/// 设置绑定:
/// Canvas.X and Y 画布位置
/// </summary>
public void SetBinding()
{
// 绑定 Canvas.Left

View File

@@ -1,6 +1,8 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Workbench.Extension;
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
@@ -39,7 +41,7 @@ namespace Serein.Workbench.Node.View
/// <summary>
/// 连接类型
/// </summary>
public ConnectionType Type { get; set; }
public ConnectionInvokeType Type { get; set; }
}
@@ -68,26 +70,26 @@ namespace Serein.Workbench.Node.View
}
/*
* 有1个Execute
* 有1个NextStep
* 有0~65535个入参 ushort
* 有1个ReturnData(void方法返回null)
*
* Execute // 执行这个方法
* 只接受 NextStep 的连接
* ArgData
* 互相之间不能连接,只能接受 Execute、ReturnData 的连接
* Execute表示从 Execute所在节点 获取数据
* ReturnData 表示从对应节点获取数据
* ReturnData:
* 只能发起主动连接,且只能连接到 ArgData
* NextStep
* 只能连接连接 Execute
*
*
*
*/
@@ -102,34 +104,58 @@ namespace Serein.Workbench.Node.View
/// <summary>
/// 连接控件,表示控件的连接关系
/// </summary>
public class ConnectionControl : Shape
public class ConnectionControl
{
private readonly IFlowEnvironment environment;
private readonly Action RemoteCallback;
/// <summary>
/// 初始化连接控件
/// 关于调用
/// </summary>
/// <param name="Canvas"></param>
/// <param name="Type"></param>
public ConnectionControl(IFlowEnvironment environment,
Canvas Canvas,
ConnectionType Type,
NodeControlBase Start,
NodeControlBase End)
public ConnectionControl(Canvas Canvas,
ConnectionInvokeType Type,
JunctionControlBase Start,
JunctionControlBase End,
Action remoteCallback)
{
this.environment = environment;
this.LineType = LineType.Bezier;
this.RemoteCallback = remoteCallback;
this.Canvas = Canvas;
this.Type = Type;
this.Start = Start;
this.End = End;
//this.Start.Background = GetLineColor(Type); // 线条颜色
//this.End.Background = GetLineColor(Type); // 线条颜色
InitElementPoint();
}
/// <summary>
/// 关于入参
/// </summary>
/// <param name="Canvas"></param>
/// <param name="Type"></param>
public ConnectionControl(LineType LineType,
Canvas Canvas,
int argIndex,
ConnectionArgSourceType connectionArgSourceType,
JunctionControlBase Start,
JunctionControlBase End,
Action remoteCallback)
{
this.LineType = LineType;
this.RemoteCallback = remoteCallback;
this.Canvas = Canvas;
this.ArgIndex = ArgIndex;
this.ConnectionArgSourceType = connectionArgSourceType;
this.Start = Start;
this.End = End;
//this.Start.Background = GetLineColor(Type); // 线条颜色
//this.End.Background = GetLineColor(Type); // 线条颜色
InitElementPoint();
}
@@ -139,108 +165,114 @@ namespace Serein.Workbench.Node.View
public Canvas Canvas { get; }
/// <summary>
/// 连接类型
/// 调用方法类型,连接类型
/// </summary>
public ConnectionType Type { get; }
public ConnectionInvokeType Type { get; }
/// <summary>
/// 起始控件
/// 获取参数类型,第几个参数
/// </summary>
public NodeControlBase Start { get; set; }
public int ArgIndex { get; set; } = -1;
/// <summary>
/// 结束控件
/// 参数来源(决定了连接线的样式)
/// </summary>
public NodeControlBase End { get; set; }
public ConnectionArgSourceType ConnectionArgSourceType { get; set; }
/// <summary>
/// 起始控制点
/// </summary>
public JunctionControlBase Start { get; set; }
/// <summary>
/// 目标控制点
/// </summary>
public JunctionControlBase End { get; set; }
/// <summary>
/// 连接线
/// </summary>
private BezierLine BezierLine;
private LineType LineType;
/// <summary>
/// 配置连接曲线的右键菜单
/// </summary>
/// <param name="line"></param>
private void ConfigureLineContextMenu(ConnectionControl connection)
private void ConfigureLineContextMenu()
{
var contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem("删除连线", (s, e) => DeleteConnection(connection)));
connection.ContextMenu = contextMenu;
connection.ContextMenu = contextMenu;
contextMenu.Items.Add(MainWindow.CreateMenuItem("删除连线", (s, e) => this.DeleteConnection()));
BezierLine.ContextMenu = contextMenu;
}
/// <summary>
/// 删除该连线
/// </summary>
/// <param name="line"></param>
private void DeleteConnection(ConnectionControl connection)
public void DeleteConnection()
{
var connectionToRemove = connection;
if (connectionToRemove is null)
if(this.Start is JunctionControlBase startJunctionControlBase)
{
return;
//startJunctionControlBase.Background = Brushes.Transparent;
}
// 获取起始节点与终止节点,消除映射关系
var fromNodeGuid = connectionToRemove.Start.ViewModel.NodeModel.Guid;
var toNodeGuid = connectionToRemove.End.ViewModel.NodeModel.Guid;
environment.RemoveConnectAsync(fromNodeGuid, toNodeGuid, connection.Type);
}
/// <summary>
/// 移除
/// </summary>
public void RemoveFromCanvas()
{
Canvas.Children.Remove(this); // 移除线
if (this.End is JunctionControlBase endJunctionControlBase)
{
//endJunctionControlBase.Background = Brushes.Transparent;
}
Canvas.Children.Remove(BezierLine); // 移除线
RemoteCallback?.Invoke();
}
/// <summary>
/// 重新绘制
/// </summary>
public void AddOrRefreshLine()
public void RefreshLine()
{
this.InvalidateVisual();
(Point startPoint, Point endPoint) = RefreshPoint(Canvas, Start, End);
BezierLine.UpdatePoints(startPoint, endPoint);
//BezierLine.UpdatePoints(startPoint, endPoint);
}
/// <summary>
/// 删除该连线
/// </summary>
public void Remote()
{
Canvas.Children.Remove(BezierLine);
}
/// <summary>
/// 绘制
/// </summary>
public void InitElementPoint()
{
leftCenterOfEndLocation = new Point(0, End.ActualHeight / 2); // 目标节点选择左侧边缘中心
rightCenterOfStartLocation = new Point(Start.ActualWidth, Start.ActualHeight / 2); // 起始节点选择右侧边缘中心
brush = GetLineColor(Type); // 线条颜色
hitVisiblePen = new Pen(Brushes.Transparent, 1.0); // 初始化碰撞检测线
hitVisiblePen.Freeze(); // Freeze以提高性能
visualPen = new Pen(brush, 1.0); // 默认可视化Pen
visualPen.Freeze(); // Freeze以提高性能
ConfigureLineContextMenu(this); // 设置连接右键事件
leftCenterOfEndLocation = Start.MyCenterPoint;
rightCenterOfStartLocation = End.MyCenterPoint;
//leftCenterOfEndLocation = new Point(0, End.ActualHeight / 2); // 目标节点选择左侧边缘中心
//rightCenterOfStartLocation = new Point(Start.ActualWidth, Start.ActualHeight / 2); // 起始节点选择右侧边缘中心
linkSize = 4; // 整线条粗细
Canvas.Children.Add(this); // 添加线
Grid.SetZIndex(this, -9999999); // 置底
(Point startPoint, Point endPoint) = RefreshPoint(Canvas, Start, End);
BezierLine = new BezierLine(LineType, startPoint, endPoint, GetLineColor(Type), linkSize);
Grid.SetZIndex(BezierLine, -9999999); // 置底
Canvas.Children.Add(BezierLine);
ConfigureLineContextMenu(); //配置右键菜单
}
/// <summary>
/// 控件重绘事件
/// </summary>
/// <param name="drawingContext"></param>
protected override void OnRender(DrawingContext drawingContext)
{
RefreshPoint(Canvas, this.Start, this.End); // 刷新坐标
DrawBezierCurve(drawingContext, startPoint, endPoint, linkSize, brush); // 刷新线条显示位置
}
private readonly StreamGeometry streamGeometry = new StreamGeometry();
double linkSize; // 根据缩放比例调整线条粗细
private Point rightCenterOfStartLocation; // 目标节点选择左侧边缘中心
private Point leftCenterOfEndLocation; // 起始节点选择右侧边缘中心
private Pen hitVisiblePen; // 初始化碰撞检测线
private Pen visualPen; // 默认可视化Pen
private Point startPoint; // 连接线的起始节点
private Point endPoint; // 连接线的终点
private Brush brush; // 线条颜色
double linkSize; // 根据缩放比例调整线条粗细
protected override Geometry DefiningGeometry => streamGeometry;
#region
public void RefreshPoint(Canvas canvas, FrameworkElement startElement, FrameworkElement endElement)
private (Point startPoint,Point endPoint) RefreshPoint(Canvas canvas, FrameworkElement startElement, FrameworkElement endElement)
{
endPoint = endElement.TranslatePoint(leftCenterOfEndLocation, canvas); // 计算终点位置
startPoint = startElement.TranslatePoint(rightCenterOfStartLocation, canvas); // 获取起始节点的中心位置
var startPoint = startElement.TranslatePoint(rightCenterOfStartLocation, canvas); // 获取起始节点的中心位置
var endPoint = endElement.TranslatePoint(leftCenterOfEndLocation, canvas); // 计算终点位置
return (startPoint, endPoint);
}
/// <summary>
@@ -249,72 +281,280 @@ namespace Serein.Workbench.Node.View
/// <param name="currentConnectionType"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static SolidColorBrush GetLineColor(ConnectionType currentConnectionType)
public static SolidColorBrush GetLineColor(ConnectionInvokeType currentConnectionType)
{
return currentConnectionType switch
{
ConnectionType.IsSucceed => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")),
ConnectionType.IsFail => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")),
ConnectionType.IsError => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FE1343")),
ConnectionType.Upstream => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4")),
_ => throw new Exception(),
ConnectionInvokeType.IsSucceed => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")),
ConnectionInvokeType.IsFail => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")),
ConnectionInvokeType.IsError => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FE1343")),
ConnectionInvokeType.Upstream => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4")),
_ => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#56CEF6")),
//_ => throw new Exception(),
};
}
private Point c0, c1; // 用于计算贝塞尔曲线控制点逻辑
private Vector axis = new Vector(1, 0);
private Vector startToEnd;
private void DrawBezierCurve(DrawingContext drawingContext,
Point start,
Point end,
double linkSize,
Brush brush,
bool isHitTestVisible = false,
double strokeThickness = 1.0,
bool isMouseOver = false,
double dashOffset = 0.0)
{
// 控制点的计算逻辑
double power = 8 * 8; // 控制贝塞尔曲线的“拉伸”强度
// 计算轴向向量与起点到终点的向量
//var axis = new Vector(1, 0);
startToEnd = (end.ToVector() - start.ToVector()).NormalizeTo();
// 计算拉伸程度k拉伸与水平夹角正相关
var k = 1 - Math.Pow(Math.Max(0, axis.DotProduct(startToEnd)), 10.0);
// 如果起点x大于终点x增加额外的偏移量避免重叠
var bias = start.X > end.X ? Math.Abs(start.X - end.X) * 0.25 : 0;
// 控制点的实际计算
c0 = new Point(+(power + bias) * k + start.X, start.Y);
c1 = new Point(-(power + bias) * k + end.X, end.Y);
// 准备StreamGeometry以用于绘制曲线
streamGeometry.Clear();
using (var context = streamGeometry.Open())
{
context.BeginFigure(start, true, false); // 曲线起点
context.BezierTo(c0, c1, end, true, false); // 画贝塞尔曲线
}
drawingContext.DrawGeometry(null, visualPen, streamGeometry);
// 绘制碰撞检测线
//if (true)
//{
// //hitVisiblePen = new Pen(Brushes.Transparent, linkSize + strokeThickness);
// //hitVisiblePen.Freeze();
// drawingContext.DrawGeometry(null, hitVisiblePen, streamGeometry);
//}
//else
//{
//}
}
#endregion
}
/*
/// <summary>
/// 连接控件,表示控件的连接关系
/// </summary>
public class ConnectionControl : Shape
{
private readonly Action RemoteCallback;
/// <summary>
/// 关于调用
/// </summary>
/// <param name="Canvas"></param>
/// <param name="Type"></param>
public ConnectionControl(Canvas Canvas,
ConnectionInvokeType Type,
JunctionControlBase Start,
JunctionControlBase End,
Action remoteCallback)
{
this.RemoteCallback = remoteCallback;
this.Canvas = Canvas;
this.Type = Type;
this.Start = Start;
this.End = End;
this.Start.Background = GetLineColor(Type); // 线条颜色
this.End.Background = GetLineColor(Type); // 线条颜色
InitElementPoint();
}
/// <summary>
/// 关于入参
/// </summary>
/// <param name="Canvas"></param>
/// <param name="Type"></param>
public ConnectionControl(Canvas Canvas,
int argIndex,
ConnectionArgSourceType connectionArgSourceType,
JunctionControlBase Start,
JunctionControlBase End,
Action remoteCallback)
{
this.RemoteCallback = remoteCallback;
this.Canvas = Canvas;
this.ArgIndex = ArgIndex;
this.ConnectionArgSourceType = connectionArgSourceType;
this.Start = Start;
this.End = End;
this.Start.Background = GetLineColor(Type); // 线条颜色
this.End.Background = GetLineColor(Type); // 线条颜色
InitElementPoint();
}
/// <summary>
/// 所在的画布
/// </summary>
public Canvas Canvas { get; }
/// <summary>
/// 调用方法类型,连接类型
/// </summary>
public ConnectionInvokeType Type { get; }
/// <summary>
/// 获取参数类型,第几个参数
/// </summary>
public int ArgIndex { get; set; } = -1;
/// <summary>
/// 参数来源(决定了连接线的样式)
/// </summary>
public ConnectionArgSourceType ConnectionArgSourceType { get; set; }
/// <summary>
/// 起始控制点
/// </summary>
public JunctionControlBase Start { get; set; }
/// <summary>
/// 目标控制点
/// </summary>
public JunctionControlBase End { get; set; }
/// <summary>
/// 配置连接曲线的右键菜单
/// </summary>
/// <param name="line"></param>
private void ConfigureLineContextMenu(ConnectionControl connection)
{
var contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem("删除连线", (s, e) => DeleteConnection(connection)));
connection.ContextMenu = contextMenu;
}
/// <summary>
/// 删除该连线
/// </summary>
/// <param name="line"></param>
private void DeleteConnection(ConnectionControl connection)
{
var connectionToRemove = connection;
if (connectionToRemove is null)
{
return;
}
if(this.Start is JunctionControlBase startJunctionControlBase)
{
startJunctionControlBase.Background = Brushes.Transparent;
}
if (this.End is JunctionControlBase endJunctionControlBase)
{
endJunctionControlBase.Background = Brushes.Transparent;
}
this.Canvas.g
RemoteCallback?.Invoke();
}
/// <summary>
/// 移除
/// </summary>
public void RemoveFromCanvas()
{
Canvas.Children.Remove(this); // 移除线
}
/// <summary>
/// 重新绘制
/// </summary>
public void AddOrRefreshLine()
{
this.InvalidateVisual();
}
public void InitElementPoint()
{
leftCenterOfEndLocation = new Point(0, End.ActualHeight / 2); // 目标节点选择左侧边缘中心
rightCenterOfStartLocation = new Point(Start.ActualWidth, Start.ActualHeight / 2); // 起始节点选择右侧边缘中心
brush = GetLineColor(Type); // 线条颜色
hitVisiblePen = new Pen(Brushes.Transparent, 1.0); // 初始化碰撞检测线
hitVisiblePen.Freeze(); // Freeze以提高性能
visualPen = new Pen(brush, 2.0); // 默认可视化Pen
visualPen.Freeze(); // Freeze以提高性能
ConfigureLineContextMenu(this); // 设置连接右键事件
linkSize = 4; // 整线条粗细
Canvas.Children.Add(this); // 添加线
Grid.SetZIndex(this, -9999999); // 置底
}
/// <summary>
/// 控件重绘事件
/// </summary>
/// <param name="drawingContext"></param>
protected override void OnRender(DrawingContext drawingContext)
{
RefreshPoint(Canvas, this.Start, this.End); // 刷新坐标
DrawBezierCurve(drawingContext, startPoint, endPoint, linkSize, brush); // 刷新线条显示位置
}
private readonly StreamGeometry streamGeometry = new StreamGeometry();
private Point rightCenterOfStartLocation; // 目标节点选择左侧边缘中心
private Point leftCenterOfEndLocation; // 起始节点选择右侧边缘中心
private Pen hitVisiblePen; // 初始化碰撞检测线
private Pen visualPen; // 默认可视化Pen
private Point startPoint; // 连接线的起始节点
private Point endPoint; // 连接线的终点
private Brush brush; // 线条颜色
double linkSize; // 根据缩放比例调整线条粗细
protected override Geometry DefiningGeometry => streamGeometry;
#region 工具方法
public void RefreshPoint(Canvas canvas, FrameworkElement startElement, FrameworkElement endElement)
{
endPoint = endElement.TranslatePoint(leftCenterOfEndLocation, canvas); // 计算终点位置
startPoint = startElement.TranslatePoint(rightCenterOfStartLocation, canvas); // 获取起始节点的中心位置
}
/// <summary>
/// 根据连接类型指定颜色
/// </summary>
/// <param name="currentConnectionType"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static SolidColorBrush GetLineColor(ConnectionInvokeType currentConnectionType)
{
return currentConnectionType switch
{
ConnectionInvokeType.IsSucceed => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")),
ConnectionInvokeType.IsFail => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")),
ConnectionInvokeType.IsError => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FE1343")),
ConnectionInvokeType.Upstream => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4")),
_ => throw new Exception(),
};
}
private Point c0, c1; // 用于计算贝塞尔曲线控制点逻辑
private Vector axis = new Vector(1, 0);
private Vector startToEnd;
private void DrawBezierCurve(DrawingContext drawingContext,
Point start,
Point end,
double linkSize,
Brush brush,
bool isHitTestVisible = false,
double strokeThickness = 1.0,
bool isMouseOver = false,
double dashOffset = 0.0)
{
// 控制点的计算逻辑
double power = 8 * 8; // 控制贝塞尔曲线的“拉伸”强度
// 计算轴向向量与起点到终点的向量
//var axis = new Vector(1, 0);
startToEnd = (end.ToVector() - start.ToVector()).NormalizeTo();
// 计算拉伸程度k拉伸与水平夹角正相关
var k = 1 - Math.Pow(Math.Max(0, axis.DotProduct(startToEnd)), 10.0);
// 如果起点x大于终点x增加额外的偏移量避免重叠
var bias = start.X > end.X ? Math.Abs(start.X - end.X) * 0.25 : 0;
// 控制点的实际计算
c0 = new Point(+(power + bias) * k + start.X, start.Y);
c1 = new Point(-(power + bias) * k + end.X, end.Y);
// 准备StreamGeometry以用于绘制曲线
streamGeometry.Clear();
using (var context = streamGeometry.Open())
{
context.BeginFigure(start, true, false); // 曲线起点
context.BezierTo(c0, c1, end, true, false); // 画贝塞尔曲线
}
drawingContext.DrawGeometry(null, visualPen, streamGeometry);
// 绘制碰撞检测线
//if (true)
//{
// //hitVisiblePen = new Pen(Brushes.Transparent, linkSize + strokeThickness);
// //hitVisiblePen.Freeze();
// drawingContext.DrawGeometry(null, hitVisiblePen, streamGeometry);
//}
//else
//{
//}
}
#endregion
}
*/
}