mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-02 15:50:47 +08:00
实现了拖拽式设置方法调用顺序、方法入参参数来源
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
/// 移除节点/区域/基础控件
|
||||
|
||||
28
Library/Enums/ConnectionArgSourceType.cs
Normal file
28
Library/Enums/ConnectionArgSourceType.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
27
Library/Enums/JunctionOfConnectionType.cs
Normal file
27
Library/Enums/JunctionOfConnectionType.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
56
Library/FlowNode/JunctionModel.cs
Normal file
56
Library/FlowNode/JunctionModel.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
82
Library/Utils/ObjectConvertHelper.cs
Normal file
82
Library/Utils/ObjectConvertHelper.cs
Normal 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("输入对象不是集合或目标类型不支持");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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); // 完成创建后注入实例需要的特性依赖项
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
|
||||
@@ -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--)
|
||||
{
|
||||
// 筛选出启用的节点
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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="*"/>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
34
Workbench/Node/INodeJunction.cs
Normal file
34
Workbench/Node/INodeJunction.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
222
Workbench/Node/Junction/BezierLine.cs
Normal file
222
Workbench/Node/Junction/BezierLine.cs
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
134
Workbench/Node/Junction/JunctionControlBase.cs
Normal file
134
Workbench/Node/Junction/JunctionControlBase.cs
Normal 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); // 起始节点选择右侧边缘中心
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user