更改了dll参数类型,更改了流程执行,添加了异常分支处理

This commit is contained in:
fengjiayi
2024-09-09 16:42:01 +08:00
parent b33c9ba857
commit 10f9738a2c
18 changed files with 541 additions and 490 deletions

4
.editorconfig Normal file
View File

@@ -0,0 +1,4 @@
[*.cs]
# CS8618: 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
dotnet_diagnostic.CS8618.severity = warning

View File

@@ -18,8 +18,8 @@ namespace Serein.Library.IOC
IServiceContainer Register(Type type, params object[] parameters); IServiceContainer Register(Type type, params object[] parameters);
IServiceContainer Register<T>(params object[] parameters); IServiceContainer Register<T>(params object[] parameters);
IServiceContainer Register<TService, TImplementation>(params object[] parameters) where TImplementation : TService; IServiceContainer Register<TService, TImplementation>(params object[] parameters) where TImplementation : TService;
T Get<T>(); T GetOrInstantiate<T>();
object Get(Type type); object GetOrInstantiate(Type type);
/// <summary> /// <summary>
/// 创建目标类型的对象, 并注入依赖项 /// 创建目标类型的对象, 并注入依赖项
@@ -120,7 +120,7 @@ namespace Serein.Library.IOC
return this; return this;
} }
public object Get(Type type) public object GetOrInstantiate(Type type)
{ {
@@ -140,24 +140,16 @@ namespace Serein.Library.IOC
} }
public T Get<T>() public T GetOrInstantiate<T>()
{ {
if(!_dependencies.TryGetValue(typeof(T).FullName, out object value)) if(!_dependencies.TryGetValue(typeof(T).FullName, out object value))
{ {
Register<T>(); Register<T>();
value = Instantiate(typeof(T)); value = Instantiate(typeof(T));
} }
return (T)value; return (T)value;
//throw new InvalidOperationException("目标类型未创建实例"); //throw new InvalidOperationException("目标类型未创建实例");
} }
public IServiceContainer Build() public IServiceContainer Build()
@@ -234,7 +226,7 @@ namespace Serein.Library.IOC
#region run() #region run()
public IServiceContainer Run<T>(Action<T> action) public IServiceContainer Run<T>(Action<T> action)
{ {
var service = Get<T>(); var service = GetOrInstantiate<T>();
if (service != null) if (service != null)
{ {
action(service); action(service);
@@ -244,8 +236,8 @@ namespace Serein.Library.IOC
public IServiceContainer Run<T1, T2>(Action<T1, T2> action) public IServiceContainer Run<T1, T2>(Action<T1, T2> action)
{ {
var service1 = Get<T1>(); var service1 = GetOrInstantiate<T1>();
var service2 = Get<T2>(); var service2 = GetOrInstantiate<T2>();
action(service1, service2); action(service1, service2);
return this; return this;
@@ -253,69 +245,69 @@ namespace Serein.Library.IOC
public IServiceContainer Run<T1, T2, T3>(Action<T1, T2, T3> action) public IServiceContainer Run<T1, T2, T3>(Action<T1, T2, T3> action)
{ {
var service1 = Get<T1>(); var service1 = GetOrInstantiate<T1>();
var service2 = Get<T2>(); var service2 = GetOrInstantiate<T2>();
var service3 = Get<T3>(); var service3 = GetOrInstantiate<T3>();
action(service1, service2, service3); action(service1, service2, service3);
return this; return this;
} }
public IServiceContainer Run<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action) public IServiceContainer Run<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action)
{ {
var service1 = Get<T1>(); var service1 = GetOrInstantiate<T1>();
var service2 = Get<T2>(); var service2 = GetOrInstantiate<T2>();
var service3 = Get<T3>(); var service3 = GetOrInstantiate<T3>();
var service4 = Get<T4>(); var service4 = GetOrInstantiate<T4>();
action(service1, service2, service3, service4); action(service1, service2, service3, service4);
return this; return this;
} }
public IServiceContainer Run<T1, T2, T3, T4, T5>(Action<T1, T2, T3, T4, T5> action) public IServiceContainer Run<T1, T2, T3, T4, T5>(Action<T1, T2, T3, T4, T5> action)
{ {
var service1 = Get<T1>(); var service1 = GetOrInstantiate<T1>();
var service2 = Get<T2>(); var service2 = GetOrInstantiate<T2>();
var service3 = Get<T3>(); var service3 = GetOrInstantiate<T3>();
var service4 = Get<T4>(); var service4 = GetOrInstantiate<T4>();
var service5 = Get<T5>(); var service5 = GetOrInstantiate<T5>();
action(service1, service2, service3, service4, service5); action(service1, service2, service3, service4, service5);
return this; return this;
} }
public IServiceContainer Run<T1, T2, T3, T4, T5, T6>(Action<T1, T2, T3, T4, T5, T6> action) public IServiceContainer Run<T1, T2, T3, T4, T5, T6>(Action<T1, T2, T3, T4, T5, T6> action)
{ {
var service1 = Get<T1>(); var service1 = GetOrInstantiate<T1>();
var service2 = Get<T2>(); var service2 = GetOrInstantiate<T2>();
var service3 = Get<T3>(); var service3 = GetOrInstantiate<T3>();
var service4 = Get<T4>(); var service4 = GetOrInstantiate<T4>();
var service5 = Get<T5>(); var service5 = GetOrInstantiate<T5>();
var service6 = Get<T6>(); var service6 = GetOrInstantiate<T6>();
action(service1, service2, service3, service4, service5, service6); action(service1, service2, service3, service4, service5, service6);
return this; return this;
} }
public IServiceContainer Run<T1, T2, T3, T4, T5, T6, T7>(Action<T1, T2, T3, T4, T5, T6, T7> action) public IServiceContainer Run<T1, T2, T3, T4, T5, T6, T7>(Action<T1, T2, T3, T4, T5, T6, T7> action)
{ {
var service1 = Get<T1>(); var service1 = GetOrInstantiate<T1>();
var service2 = Get<T2>(); var service2 = GetOrInstantiate<T2>();
var service3 = Get<T3>(); var service3 = GetOrInstantiate<T3>();
var service4 = Get<T4>(); var service4 = GetOrInstantiate<T4>();
var service5 = Get<T5>(); var service5 = GetOrInstantiate<T5>();
var service6 = Get<T6>(); var service6 = GetOrInstantiate<T6>();
var service7 = Get<T7>(); var service7 = GetOrInstantiate<T7>();
action(service1, service2, service3, service4, service5, service6, service7); action(service1, service2, service3, service4, service5, service6, service7);
return this; return this;
} }
public IServiceContainer Run<T1, T2, T3, T4, T5, T6, T7, T8>(Action<T1, T2, T3, T4, T5, T6, T7, T8> action) public IServiceContainer Run<T1, T2, T3, T4, T5, T6, T7, T8>(Action<T1, T2, T3, T4, T5, T6, T7, T8> action)
{ {
var service1 = Get<T1>(); var service1 = GetOrInstantiate<T1>();
var service2 = Get<T2>(); var service2 = GetOrInstantiate<T2>();
var service3 = Get<T3>(); var service3 = GetOrInstantiate<T3>();
var service4 = Get<T4>(); var service4 = GetOrInstantiate<T4>();
var service5 = Get<T5>(); var service5 = GetOrInstantiate<T5>();
var service6 = Get<T6>(); var service6 = GetOrInstantiate<T6>();
var service7 = Get<T7>(); var service7 = GetOrInstantiate<T7>();
var service8 = Get<T8>(); var service8 = GetOrInstantiate<T8>();
action(service1, service2, service3, service4, service5, service6, service7, service8); action(service1, service2, service3, service4, service5, service6, service7, service8);
return this; return this;
} }

View File

@@ -8,17 +8,17 @@ using System.Threading.Tasks;
namespace MyDll namespace MyDll
{ {
internal class test internal class IoTClientTest
{ {
private void T() private void T()
{ {
SiemensClient client = new SiemensClient(SiemensVersion.S7_200Smart, "127.0.0.1", 102); SiemensClient client = new SiemensClient(SiemensVersion.S7_200Smart, "127.0.0.1", 102);
//2、写操作 //2、写操作
client.Write("Q1.3", true); //client.Write("Q1.3", true);
client.Write("V2205", (short)11); //client.Write("V2205", (short)11);
client.Write("V2209", 33); //client.Write("V2209", 33);
client.Write("V2305", "orderCode"); //写入字符串 //client.Write("V2305", "orderCode"); //写入字符串
//3、读操作 //3、读操作
var value1 = client.ReadBoolean("Q1.3").Value; var value1 = client.ReadBoolean("Q1.3").Value;

View File

@@ -1,5 +1,6 @@
using Serein.Library.Http; using Serein.Library.Http;
using Serein.NodeFlow; using Serein.NodeFlow;
using Serein.NodeFlow.Model;
using Serein.NodeFlow.Tool; using Serein.NodeFlow.Tool;
using static MyDll.PlcDevice; using static MyDll.PlcDevice;
namespace MyDll namespace MyDll
@@ -135,7 +136,7 @@ namespace MyDll
#region #region
[MethodDetail(DynamicNodeType.Flipflop, "等待信号触发")] [MethodDetail(DynamicNodeType.Flipflop, "等待信号触发")]
public async Task<FlipflopContext> WaitTask([Explicit] SignalType triggerType = SignalType.1) public async Task<FlipflopContext> WaitTask(SignalType triggerType = SignalType.1)
{ {
/*if (!Enum.TryParse(triggerValue, out SignalType triggerType) && Enum.IsDefined(typeof(SignalType), triggerType)) /*if (!Enum.TryParse(triggerValue, out SignalType triggerType) && Enum.IsDefined(typeof(SignalType), triggerType))
{ {
@@ -150,16 +151,16 @@ namespace MyDll
var result = await tcs.Task; var result = await tcs.Task;
//Interlocked.Increment(ref MyPlc.Count); // 原子自增 //Interlocked.Increment(ref MyPlc.Count); // 原子自增
//Console.WriteLine($"信号触发[{triggerType}] : {MyPlc.Count}{Environment.NewLine} thread :{Thread.CurrentThread.ManagedThreadId}{Environment.NewLine}"); //Console.WriteLine($"信号触发[{triggerType}] : {MyPlc.Count}{Environment.NewLine} thread :{Thread.CurrentThread.ManagedThreadId}{Environment.NewLine}");
return new FlipflopContext(FfState.Succeed, MyPlc.Count); return new FlipflopContext(FlowStateType.Succeed, MyPlc.Count);
} }
catch (TcsSignalException) catch (TcsSignalException)
{ {
// await Console.Out.WriteLineAsync($"取消等待信号[{triggerType}]"); // await Console.Out.WriteLineAsync($"取消等待信号[{triggerType}]");
return new FlipflopContext(FfState.Cancel); return new FlipflopContext(FlowStateType.Error);
} }
} }
[MethodDetail(DynamicNodeType.Flipflop, "等待信号触发")] [MethodDetail(DynamicNodeType.Flipflop, "等待信号触发")]
public async Task<FlipflopContext> WaitTask2([Explicit] string triggerValue = nameof(SignalType.1)) public async Task<FlipflopContext> WaitTask2(string triggerValue = nameof(SignalType.1))
{ {
try try
{ {
@@ -173,12 +174,12 @@ namespace MyDll
Interlocked.Increment(ref MyPlc.Count); // 原子自增 Interlocked.Increment(ref MyPlc.Count); // 原子自增
Console.WriteLine($"信号触发[{triggerType}] : {MyPlc.Count}"); Console.WriteLine($"信号触发[{triggerType}] : {MyPlc.Count}");
return new FlipflopContext(FfState.Succeed, MyPlc.Count); return new FlipflopContext(FlowStateType.Succeed, MyPlc.Count);
} }
catch(TcsSignalException ex) catch(TcsSignalException ex)
{ {
// await Console.Out.WriteLineAsync($"取消等待信号[{triggerValue}]"); // await Console.Out.WriteLineAsync($"取消等待信号[{triggerValue}]");
return new FlipflopContext(ex.FfState); return new FlipflopContext(ex.FsState);
} }
} }
@@ -187,15 +188,17 @@ namespace MyDll
#region #region
[MethodDetail(DynamicNodeType.Action, "初始化")] [MethodDetail(DynamicNodeType.Action, "初始化")]
public PlcDevice PlcInit([Explicit] string ip = "192.168.1.1", public PlcDevice PlcInit(string ip = "192.168.1.1",
[Explicit] int port = 6688, int port = 6688,
[Explicit] string tips = "测试") string tips = "测试")
{ {
MyPlc.InitDevice(ip, port, tips); MyPlc.InitDevice(ip, port, tips);
return MyPlc; return MyPlc;
} }
[MethodDetail(DynamicNodeType.Action, "自增")] [MethodDetail(DynamicNodeType.Action, "自增")]
public PlcDevice ([Explicit] int number = 1) public PlcDevice (int number = 1)
{ {
MyPlc.Count += number; MyPlc.Count += number;
return MyPlc; return MyPlc;
@@ -204,9 +207,9 @@ namespace MyDll
[MethodDetail(DynamicNodeType.Action, "模拟循环触发")] [MethodDetail(DynamicNodeType.Action, "模拟循环触发")]
public void (DynamicContext context, public void (DynamicContext context,
[Explicit] int time = 20, int time = 20,
[Explicit] int count = 5, int count = 5,
[Explicit] SignalType signal = SignalType.1) SignalType signal = SignalType.1)
{ {
Action action = () => Action action = () =>
{ {

View File

@@ -65,9 +65,9 @@ namespace Serein.NodeFlow
/// <summary> /// <summary>
/// 是否为显式参数 /// 是否为显式参数
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter)] //[AttributeUsage(AttributeTargets.Parameter)]
public class ExplicitAttribute : Attribute // where TEnum : Enum //public class ExplicitAttribute : Attribute // where TEnum : Enum
{ //{
} //}
} }

View File

@@ -1,4 +1,5 @@
using Serein.Library.IOC; using Serein.Library.IOC;
using Serein.NodeFlow.Model;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@@ -12,23 +13,24 @@ using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Serein.NodeFlow namespace Serein.NodeFlow
{ {
public enum FfState //public enum FfState
{ //{
Succeed, // Succeed,
Cancel, // Cancel,
} // Error,
//}
/// <summary> /// <summary>
/// 触发器上下文 /// 触发器上下文
/// </summary> /// </summary>
public class FlipflopContext public class FlipflopContext
{ {
public FfState State { get; set; } public FlowStateType State { get; set; }
public object? Data { get; set; } public object? Data { get; set; }
/*public FlipflopContext() /*public FlipflopContext()
{ {
State = FfState.Cancel; State = FfState.Cancel;
}*/ }*/
public FlipflopContext(FfState ffState, object? data = null) public FlipflopContext(FlowStateType ffState, object? data = null)
{ {
State = ffState; State = ffState;
Data = data; Data = data;
@@ -144,7 +146,7 @@ namespace Serein.NodeFlow
public NodeRunTcs NodeRunCts { get; set; } public NodeRunTcs NodeRunCts { get; set; }
public Task CreateTimingTask(Action action, int time = 100, int count = -1) public Task CreateTimingTask(Action action, int time = 100, int count = -1)
{ {
NodeRunCts ??= ServiceContainer.Get<NodeRunTcs>(); NodeRunCts ??= ServiceContainer.GetOrInstantiate<NodeRunTcs>();
return Task.Factory.StartNew(async () => return Task.Factory.StartNew(async () =>
{ {
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)

View File

@@ -14,16 +14,22 @@
MethodDetails ??= node.MethodDetails; MethodDetails ??= node.MethodDetails;
} }
/// <summary>
/// 条件节点重写执行方法
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override object? Execute(DynamicContext context) public override object? Execute(DynamicContext context)
{ {
// bool allTrue = ConditionNodes.All(condition => Judge(context,condition.MethodDetails)); // bool allTrue = ConditionNodes.All(condition => Judge(context,condition.MethodDetails));
// bool IsAllTrue = true; // 初始化为 true // bool IsAllTrue = true; // 初始化为 true
FlowState = true; FlowState = FlowStateType.Succeed;
foreach (SingleConditionNode? node in ConditionNodes) foreach (SingleConditionNode? node in ConditionNodes)
{ {
if (!Judge(context, node)) var state = Judge(context, node);
if (state == FlowStateType.Fail || FlowStateType.Fail == FlowStateType.Error)
{ {
FlowState = false; FlowState = state;
break;// 一旦发现条件为假,立即退出循环 break;// 一旦发现条件为假,立即退出循环
} }
} }
@@ -44,7 +50,7 @@
// } // }
//} //}
} }
private bool Judge(DynamicContext context, SingleConditionNode node) private FlowStateType Judge(DynamicContext context, SingleConditionNode node)
{ {
try try
{ {
@@ -54,8 +60,8 @@
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(ex.Message); Console.WriteLine(ex.Message);
return FlowStateType.Error;
} }
return false;
} }

View File

@@ -25,6 +25,22 @@ namespace Serein.NodeFlow.Model
Upstream, Upstream,
} }
public enum FlowStateType
{
/// <summary>
/// 成功(方法成功执行)
/// </summary>
Succeed,
/// <summary>
/// 失败(方法没有成功执行,不过执行时没有发生非预期的错误)
/// </summary>
Fail,
/// <summary>
/// 异常(节点没有
/// </summary>
Error,
}
/// <summary> /// <summary>
/// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域
/// </summary> /// </summary>
@@ -73,21 +89,30 @@ namespace Serein.NodeFlow.Model
/// <summary> /// <summary>
/// 当前状态(进入真分支还是假分支,异常分支在异常中确定) /// 当前状态(进入真分支还是假分支,异常分支在异常中确定)
/// </summary> /// </summary>
public bool FlowState { get; set; } = true; public FlowStateType FlowState { get; set; } = FlowStateType.Succeed;
//public ConnectionType NextType { get; set; } = ConnectionType.IsTrue; public Exception Exception { get; set; } = null;
/// <summary> /// <summary>
/// 当前传递数据 /// 当前传递数据
/// </summary> /// </summary>
public object? FlowData { get; set; } = null; public object? FlowData { get; set; } = null;
// 正常流程节点调用 /// <summary>
/// 执行节点对应的方法
/// </summary>
/// <param name="context">流程上下文</param>
/// <returns>节点传回数据对象</returns>
public virtual object? Execute(DynamicContext context) public virtual object? Execute(DynamicContext context)
{ {
MethodDetails md = MethodDetails; MethodDetails md = MethodDetails;
object? result = null; object? result = null;
if (!DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del))
{
return result;
}
if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) try
{ {
if (md.ExplicitDatas.Length == 0) if (md.ExplicitDatas.Length == 0)
{ {
@@ -110,89 +135,76 @@ namespace Serein.NodeFlow.Model
else else
{ {
result = ((Func<object, object[], object>)del).Invoke(md.ActingInstance, parameters); result = ((Func<object, object[], object>)del).Invoke(md.ActingInstance, parameters);
} }
} }
// context.SetFlowData(result);
// CurrentData = result; return result;
}
catch (Exception ex)
{
FlowState = FlowStateType.Error;
Exception = ex;
} }
return result; return result;
} }
// 触发器调用 /// <summary>
/// 执行等待触发器的方法
/// </summary>
/// <param name="context"></param>
/// <returns>节点传回数据对象</returns>
/// <exception cref="Exception"></exception>
public virtual async Task<object?> ExecuteAsync(DynamicContext context) public virtual async Task<object?> ExecuteAsync(DynamicContext context)
{ {
MethodDetails md = MethodDetails; MethodDetails md = MethodDetails;
object? result = null; object? result = null;
if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del)) if (!DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del))
{ {
return result;
}
FlipflopContext flipflopContext = null;
try
{
// 调用委托并获取结果
if (md.ExplicitDatas.Length == 0) if (md.ExplicitDatas.Length == 0)
{ {
// 调用委托并获取结果 flipflopContext = await ((Func<object, Task<FlipflopContext>>)del).Invoke(MethodDetails.ActingInstance);
FlipflopContext flipflopContext = await ((Func<object, Task<FlipflopContext>>)del).Invoke(MethodDetails.ActingInstance);
if (flipflopContext != null)
{
if (flipflopContext.State == FfState.Cancel)
{
throw new Exception("this async task is cancel.");
}
else
{
if (flipflopContext.State == FfState.Succeed)
{
FlowState = true;
result = flipflopContext.Data;
}
else
{
FlowState = false;
}
}
}
} }
else else
{ {
object?[]? parameters = GetParameters(context, MethodDetails); object?[]? parameters = GetParameters(context, MethodDetails);
// 调用委托并获取结果 flipflopContext = await ((Func<object, object[], Task<FlipflopContext>>)del).Invoke(MethodDetails.ActingInstance, parameters);
}
if (flipflopContext != null)
FlipflopContext flipflopContext = await ((Func<object, object[], Task<FlipflopContext>>)del).Invoke(MethodDetails.ActingInstance, parameters); {
FlowState = flipflopContext.State;
if (flipflopContext.State == FlowStateType.Succeed)
if (flipflopContext != null)
{ {
if (flipflopContext.State == FfState.Cancel) result = flipflopContext.Data;
{
throw new Exception("取消此异步");
}
else
{
FlowState = flipflopContext.State == FfState.Succeed;
result = flipflopContext.Data;
}
} }
} }
} }
catch (Exception ex)
{
FlowState = FlowStateType.Error;
Exception = ex;
}
return result; return result;
} }
public async Task ExecuteStack(DynamicContext context) /// <summary>
/// 开始执行
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task StartExecution(DynamicContext context)
{ {
await Task.Run(async () => var cts = context.ServiceContainer.GetOrInstantiate<CancellationTokenSource>();
{
await ExecuteStackTmp(context);
});
}
public async Task ExecuteStackTmp(DynamicContext context)
{
var cts = context.ServiceContainer.Get<CancellationTokenSource>();
Stack<NodeBase> stack = []; Stack<NodeBase> stack = [];
stack.Push(this); stack.Push(this);
@@ -202,32 +214,38 @@ namespace Serein.NodeFlow.Model
// 从栈中弹出一个节点作为当前节点进行处理 // 从栈中弹出一个节点作为当前节点进行处理
var currentNode = stack.Pop(); var currentNode = stack.Pop();
// 设置方法执行的对象
if (currentNode.MethodDetails != null) if (currentNode.MethodDetails != null)
{ {
currentNode.MethodDetails.ActingInstance ??= context.ServiceContainer.Get(MethodDetails.ActingInstanceType); currentNode.MethodDetails.ActingInstance ??= context.ServiceContainer.GetOrInstantiate(MethodDetails.ActingInstanceType);
} // 设置方法执行的对象 }
// 获取上游分支,首先执行一次 // 获取上游分支,首先执行一次
var upstreamNodes = currentNode.UpstreamBranch; var upstreamNodes = currentNode.UpstreamBranch;
for (int i = upstreamNodes.Count - 1; i >= 0; i--) for (int i = upstreamNodes.Count - 1; i >= 0; i--)
{ {
upstreamNodes[i].PreviousNode = currentNode; upstreamNodes[i].PreviousNode = currentNode;
await upstreamNodes[i].ExecuteStack(context); await upstreamNodes[i].StartExecution(context);
} }
if (currentNode.MethodDetails != null && currentNode.MethodDetails.MethodDynamicType == DynamicNodeType.Flipflop) if (currentNode.MethodDetails != null && currentNode.MethodDetails.MethodDynamicType == DynamicNodeType.Flipflop)
{ {
// 触发器节点
currentNode.FlowData = await currentNode.ExecuteAsync(context); currentNode.FlowData = await currentNode.ExecuteAsync(context);
} }
else else
{ {
// 动作节点
currentNode.FlowData = currentNode.Execute(context); currentNode.FlowData = currentNode.Execute(context);
} }
var nextNodes = currentNode.FlowState switch
var nextNodes = currentNode.FlowState ? currentNode.SucceedBranch {
: currentNode.FailBranch; FlowStateType.Succeed => currentNode.SucceedBranch,
FlowStateType.Fail => currentNode.FailBranch,
FlowStateType.Error => currentNode.ErrorBranch,
_ => throw new Exception("非预期的枚举值")
};
// 将下一个节点集合中的所有节点逆序推入栈中 // 将下一个节点集合中的所有节点逆序推入栈中
for (int i = nextNodes.Count - 1; i >= 0; i--) for (int i = nextNodes.Count - 1; i >= 0; i--)
@@ -238,7 +256,9 @@ namespace Serein.NodeFlow.Model
} }
} }
/// <summary>
/// 获取对应的参数数组
/// </summary>
public object[]? GetParameters(DynamicContext context, MethodDetails md) public object[]? GetParameters(DynamicContext context, MethodDetails md)
{ {
// 用正确的大小初始化参数数组 // 用正确的大小初始化参数数组
@@ -355,7 +375,12 @@ namespace Serein.NodeFlow.Model
return parameters; return parameters;
} }
/// <summary>
/// json文本反序列化为对象
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <returns></returns>
private dynamic? ConvertValue(string value, Type targetType) private dynamic? ConvertValue(string value, Type targetType)
{ {
try try

View File

@@ -36,7 +36,17 @@ namespace Serein.NodeFlow.Model
{ {
result = PreviousNode?.FlowData; result = PreviousNode?.FlowData;
} }
FlowState = SerinConditionParser.To(result, Expression); try
{
var isPass = SerinConditionParser.To(result, Expression);
FlowState = isPass ? FlowStateType.Succeed : FlowStateType.Fail;
}
catch (Exception ex)
{
FlowState = FlowStateType.Error;
Exception = ex;
}
Console.WriteLine($"{result} {Expression} -> " + FlowState); Console.WriteLine($"{result} {Expression} -> " + FlowState);
return result; return result;
} }

View File

@@ -25,7 +25,7 @@ namespace Serein.NodeFlow.Model
var newData = SerinExpressionEvaluator.Evaluate(Expression, data, out bool isChange); var newData = SerinExpressionEvaluator.Evaluate(Expression, data, out bool isChange);
FlowState = true; FlowState = FlowStateType.Succeed;
Console.WriteLine(newData); Console.WriteLine(newData);
if (isChange) if (isChange)
{ {

View File

@@ -102,7 +102,10 @@ namespace Serein.NodeFlow
try try
{ {
await Task.WhenAll([startNode.ExecuteStack(context), .. tasks]); await Task.Run(async () =>
{
await Task.WhenAll([startNode.StartExecution(context), .. tasks]);
});
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -133,24 +136,27 @@ namespace Serein.NodeFlow
FlipflopContext flipflopContext = await func.Invoke(md.ActingInstance, parameters); FlipflopContext flipflopContext = await func.Invoke(md.ActingInstance, parameters);
if (flipflopContext == null) if (flipflopContext == null)
{ {
break; break;
} }
else if (flipflopContext.State == FfState.Cancel) else if (flipflopContext.State == FlowStateType.Error)
{ {
break; break;
} }
else if (flipflopContext.State == FfState.Succeed) else if (flipflopContext.State == FlowStateType.Fail)
{ {
singleFlipFlopNode.FlowState = true; break;
}
else if (flipflopContext.State == FlowStateType.Succeed)
{
singleFlipFlopNode.FlowState = FlowStateType.Succeed;
singleFlipFlopNode.FlowData = flipflopContext.Data; singleFlipFlopNode.FlowData = flipflopContext.Data;
var tasks = singleFlipFlopNode.SucceedBranch.Select(nextNode => var tasks = singleFlipFlopNode.SucceedBranch.Select(nextNode =>
{ {
var context = new DynamicContext(ServiceContainer); var context = new DynamicContext(ServiceContainer);
nextNode.PreviousNode = singleFlipFlopNode; nextNode.PreviousNode = singleFlipFlopNode;
return nextNode.ExecuteStack(context); return nextNode.StartExecution(context);
}).ToArray(); }).ToArray();
Task.WaitAll(tasks); Task.WaitAll(tasks);
} }

View File

@@ -105,7 +105,7 @@ public static class DelegateGenerator
return new ExplicitData return new ExplicitData
{ {
IsExplicitData = it.GetCustomAttribute(typeof(ExplicitAttribute)) is ExplicitAttribute, IsExplicitData = it.HasDefaultValue,
Index = index, Index = index,
ExplicitType = it.ParameterType, ExplicitType = it.ParameterType,
ExplicitTypeName = explicitTypeName, ExplicitTypeName = explicitTypeName,

View File

@@ -1,14 +1,15 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using Serein.NodeFlow; using Serein.NodeFlow;
using Serein.NodeFlow.Model;
namespace Serein.NodeFlow.Tool namespace Serein.NodeFlow.Tool
{ {
public class TcsSignalException : Exception public class TcsSignalException : Exception
{ {
public FfState FfState { get; set; } public FlowStateType FsState { get; set; }
public TcsSignalException(string? message) : base(message) public TcsSignalException(string? message) : base(message)
{ {
FfState = FfState.Cancel; FsState = FlowStateType.Error;
} }
} }

View File

@@ -13,6 +13,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Module.WAT", "Serein
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.NodeFlow", "NodeFlow\Serein.NodeFlow.csproj", "{7B51A19A-88AB-471E-BCE3-3888C67C936D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.NodeFlow", "NodeFlow\Serein.NodeFlow.csproj", "{7B51A19A-88AB-471E-BCE3-3888C67C936D}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{938F25F8-E497-4FCF-A028-9309C155A8EF}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU

View File

@@ -67,13 +67,13 @@ namespace Serein.Module
#endregion #endregion
[MethodDetail(DynamicNodeType.Action,"等待")] [MethodDetail(DynamicNodeType.Action,"等待")]
public void Wait([Explicit]int time = 1000) public void Wait(int time = 1000)
{ {
Thread.Sleep(time); Thread.Sleep(time);
} }
[MethodDetail(DynamicNodeType.Action,"启动浏览器")] [MethodDetail(DynamicNodeType.Action,"启动浏览器")]
public WebDriver OpenDriver([Explicit] bool isVisible = true,[Explicit] DriverType driverType = DriverType.Chrome) public WebDriver OpenDriver( bool isVisible = true, DriverType driverType = DriverType.Chrome)
{ {
if(driverType == DriverType.Chrome) if(driverType == DriverType.Chrome)
{ {
@@ -127,7 +127,7 @@ namespace Serein.Module
} }
[MethodDetail(DynamicNodeType.Action,"进入网页")] [MethodDetail(DynamicNodeType.Action,"进入网页")]
public void ToPage([Explicit] string url) public void ToPage( string url)
{ {
if (url.StartsWith("https://") || url.StartsWith("http://")) if (url.StartsWith("https://") || url.StartsWith("http://"))
{ {
@@ -141,7 +141,7 @@ namespace Serein.Module
[MethodDetail(DynamicNodeType.Action,"定位元素")] [MethodDetail(DynamicNodeType.Action,"定位元素")]
public IWebElement FindElement([Explicit] string key = "", [Explicit] ByType byType = ByType.XPath, [Explicit] int index = 0) public IWebElement FindElement( string key = "", ByType byType = ByType.XPath, int index = 0)
{ {
By by = byType switch By by = byType switch
{ {
@@ -163,12 +163,12 @@ namespace Serein.Module
} }
[MethodDetail(DynamicNodeType.Action, "定位并操作元素")] [MethodDetail(DynamicNodeType.Action, "定位并操作元素")]
public IWebElement FindAndUseElement([Explicit] ByType byType = ByType.XPath, public IWebElement FindAndUseElement( ByType byType = ByType.XPath,
[Explicit] string key = "", string key = "",
[Explicit] ActionType actionType = ActionType.Click, ActionType actionType = ActionType.Click,
[Explicit] string text = "", string text = "",
[Explicit] int index = 0, int index = 0,
[Explicit] int waitTime = 0) int waitTime = 0)
{ {
Thread.Sleep(waitTime); Thread.Sleep(waitTime);
By by = byType switch By by = byType switch
@@ -205,7 +205,7 @@ namespace Serein.Module
} }
[MethodDetail(DynamicNodeType.Action, "操作元素")] [MethodDetail(DynamicNodeType.Action, "操作元素")]
public void PerformAction(IWebElement element, [Explicit] ActionType actionType = ActionType.Click, [Explicit] string text = "") public void PerformAction(IWebElement element, ActionType actionType = ActionType.Click, string text = "")
{ {
var actions = new OpenQA.Selenium.Interactions.Actions(WebDriver); var actions = new OpenQA.Selenium.Interactions.Actions(WebDriver);
@@ -230,7 +230,7 @@ namespace Serein.Module
[MethodDetail(DynamicNodeType.Action,"Js操作元素属性")] [MethodDetail(DynamicNodeType.Action,"Js操作元素属性")]
public void AddAttribute(IWebElement element, [Explicit] ScriptOp scriptOp = ScriptOp.Modify,[Explicit] string attributeName = "", [Explicit] string value = "") public void AddAttribute(IWebElement element, ScriptOp scriptOp = ScriptOp.Modify, string attributeName = "", string value = "")
{ {
if(scriptOp == ScriptOp.Add) if(scriptOp == ScriptOp.Add)
{ {
@@ -252,7 +252,7 @@ namespace Serein.Module
[MethodDetail(DynamicNodeType.Action, "Js获取元素属性")] [MethodDetail(DynamicNodeType.Action, "Js获取元素属性")]
public string GetAttribute(IWebElement element, [Explicit] string attributeName = "") public string GetAttribute(IWebElement element, string attributeName = "")
{ {
return element.GetAttribute(attributeName); return element.GetAttribute(attributeName);
} }

View File

@@ -120,10 +120,6 @@ namespace Serein.WorkBench
public static string FileDataPath = ""; public static string FileDataPath = "";
private void Application_Startup(object sender, StartupEventArgs e) private void Application_Startup(object sender, StartupEventArgs e)
{ {
// 示例:写入调试信息
Debug.WriteLine("应用程序启动");
// 检查是否传入了参数 // 检查是否传入了参数
if (e.Args.Length == 1) if (e.Args.Length == 1)
{ {

View File

@@ -74,7 +74,9 @@
</StackPanel> </StackPanel>
<ScrollViewer Grid.Row="1" <ScrollViewer Grid.Row="1"
x:Name="FlowChartScrollViewer"> x:Name="FlowChartScrollViewer"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<Canvas x:Name="FlowChartCanvas" <Canvas x:Name="FlowChartCanvas"
Background="#F2EEE8" Background="#F2EEE8"

View File

@@ -145,34 +145,20 @@ namespace Serein.WorkBench
/// 存储所有方法信息 /// 存储所有方法信息
/// </summary> /// </summary>
ConcurrentDictionary<string, MethodDetails> DictMethodDetail = []; ConcurrentDictionary<string, MethodDetails> DictMethodDetail = [];
/// <summary> /// <summary>
/// 存储所有与节点有关的控件 /// 存储所有与节点有关的控件
/// </summary> /// </summary>
private readonly List<NodeControlBase> nodeControls = []; private readonly List<NodeControlBase> nodeControls = [];
// private readonly List<NodeBase> nodeBases = [];
/// <summary> /// <summary>
/// 存储所有的连接 /// 存储所有的连接
/// </summary> /// </summary>
private readonly List<Connection> connections = []; private readonly List<Connection> connections = [];
/// <summary> /// <summary>
/// 存放触发器节点(运行时全部调用) /// 存放触发器节点(运行时全部调用)
/// </summary> /// </summary>
private readonly List<SingleFlipflopNode> flipflopNodes = []; private readonly List<SingleFlipflopNode> flipflopNodes = [];
///// <summary>
///// 运行前的初始化(实例化类型)
///// </summary>
//private readonly List<MethodDetails> loadingMethods = [];
///// <summary>
///// 初始化后属性注入以及某些需要设置的状态(注入依赖项)
///// </summary>
//private readonly List<MethodDetails> initMethods = [];
///// <summary>
///// 结束运行时需要调用的方法
///// </summary>
//private readonly List<MethodDetails> exitMethods = [];
/// <summary> /// <summary>
/// 记录拖动开始时的鼠标位置 /// 记录拖动开始时的鼠标位置
/// </summary> /// </summary>
@@ -196,20 +182,32 @@ namespace Serein.WorkBench
/// <summary> /// <summary>
/// 标记是否正在进行连接操作 /// 标记是否正在进行连接操作
/// </summary> /// </summary>
private bool IsConnecting { get; set; } private bool IsConnecting;
/// <summary> /// <summary>
/// 标记是否正在拖动控件 /// 标记是否正在拖动控件
/// </summary> /// </summary>
private bool IsDragging; private bool IsControlDragging;
/// <summary>
/// 标记是否正在拖动画布
/// </summary>
private bool IsCanvasDragging; private bool IsCanvasDragging;
private Point startMousePosition; /// <summary>
private TranslateTransform transform1; /// 组合变换容器
/// </summary>
private TransformGroup canvasTransformGroup; private TransformGroup canvasTransformGroup;
/// <summary>
/// 缩放画布
/// </summary>
private ScaleTransform scaleTransform; private ScaleTransform scaleTransform;
/// <summary>
/// 平移画布
/// </summary>
private TranslateTransform translateTransform; private TranslateTransform translateTransform;
/// <summary>
/// 流程起点
/// </summary>
private NodeFlowStarter nodeFlowStarter;
public MainWindow() public MainWindow()
@@ -225,15 +223,15 @@ namespace Serein.WorkBench
//transform = new TranslateTransform(); //transform = new TranslateTransform();
//FlowChartCanvas.RenderTransform = transform; //FlowChartCanvas.RenderTransform = transform;
canvasTransformGroup = new TransformGroup(); canvasTransformGroup = new TransformGroup();
scaleTransform = new ScaleTransform(); scaleTransform = new ScaleTransform();
translateTransform = new TranslateTransform(); translateTransform = new TranslateTransform();
canvasTransformGroup.Children.Add(scaleTransform); canvasTransformGroup.Children.Add(scaleTransform);
canvasTransformGroup.Children.Add(translateTransform); canvasTransformGroup.Children.Add(translateTransform);
FlowChartCanvas.RenderTransform = canvasTransformGroup; FlowChartCanvas.RenderTransform = canvasTransformGroup;
FlowChartCanvas.RenderTransformOrigin = new Point(0.5, 0.5); FlowChartCanvas.RenderTransformOrigin = new Point(0.5, 0.5);
} }
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
@@ -1166,7 +1164,7 @@ namespace Serein.WorkBench
if (IsConnecting) if (IsConnecting)
return; return;
IsDragging = true; IsControlDragging = true;
startPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置 startPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置
((UIElement)sender).CaptureMouse(); // 捕获鼠标 ((UIElement)sender).CaptureMouse(); // 捕获鼠标
} }
@@ -1179,7 +1177,7 @@ namespace Serein.WorkBench
/// </summary> /// </summary>
private void Block_MouseMove(object sender, MouseEventArgs e) private void Block_MouseMove(object sender, MouseEventArgs e)
{ {
if (IsDragging) if (IsControlDragging)
{ {
Point currentPosition = e.GetPosition(FlowChartCanvas); // 获取当前鼠标位置 Point currentPosition = e.GetPosition(FlowChartCanvas); // 获取当前鼠标位置
// 获取引发事件的控件 // 获取引发事件的控件
@@ -1296,7 +1294,7 @@ namespace Serein.WorkBench
if (e.MiddleButton == MouseButtonState.Pressed) if (e.MiddleButton == MouseButtonState.Pressed)
{ {
IsCanvasDragging = true; IsCanvasDragging = true;
startMousePosition = e.GetPosition(this); startPoint = e.GetPosition(this);
FlowChartCanvas.CaptureMouse(); FlowChartCanvas.CaptureMouse();
} }
} }
@@ -1317,6 +1315,7 @@ namespace Serein.WorkBench
private void FlowChartCanvas_MouseWheel(object sender, MouseWheelEventArgs e) private void FlowChartCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
{ {
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{ {
double scale = e.Delta > 0 ? 1.1 : 0.9; double scale = e.Delta > 0 ? 1.1 : 0.9;
@@ -1403,13 +1402,13 @@ namespace Serein.WorkBench
if (IsCanvasDragging) if (IsCanvasDragging)
{ {
Point currentMousePosition = e.GetPosition(this); Point currentMousePosition = e.GetPosition(this);
double deltaX = currentMousePosition.X - startMousePosition.X; double deltaX = currentMousePosition.X - startPoint.X;
double deltaY = currentMousePosition.Y - startMousePosition.Y; double deltaY = currentMousePosition.Y - startPoint.Y;
translateTransform.X += deltaX; translateTransform.X += deltaX;
translateTransform.Y += deltaY; translateTransform.Y += deltaY;
startMousePosition = currentMousePosition; startPoint = currentMousePosition;
// Adjust canvas size and content if necessary // Adjust canvas size and content if necessary
AdjustCanvasSizeAndContent(deltaX, deltaY); AdjustCanvasSizeAndContent(deltaX, deltaY);
@@ -1421,9 +1420,9 @@ namespace Serein.WorkBench
/// </summary> /// </summary>
private void Block_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) private void Block_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{ {
if (IsDragging) if (IsControlDragging)
{ {
IsDragging = false; IsControlDragging = false;
((UIElement)sender).ReleaseMouseCapture(); // 释放鼠标捕获 ((UIElement)sender).ReleaseMouseCapture(); // 释放鼠标捕获
} }
else if (IsConnecting) else if (IsConnecting)
@@ -1787,7 +1786,7 @@ namespace Serein.WorkBench
} }
private NodeFlowStarter nodeFlowStarter;
/// <summary> /// <summary>
/// 运行测试 /// 运行测试
@@ -1804,8 +1803,11 @@ namespace Serein.WorkBench
WriteLog("----------------\r\n"); WriteLog("----------------\r\n");
} }
/// <summary>
/// 退出
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonDebugFlipflopNode_Click(object sender, RoutedEventArgs e) private void ButtonDebugFlipflopNode_Click(object sender, RoutedEventArgs e)
{ {
nodeFlowStarter?.Exit(); nodeFlowStarter?.Exit();
@@ -2080,7 +2082,6 @@ namespace Serein.WorkBench
} }
} }
public static string? SaveContentToFile(string content) public static string? SaveContentToFile(string content)
{ {
// 创建一个新的保存文件对话框 // 创建一个新的保存文件对话框
@@ -2115,7 +2116,6 @@ namespace Serein.WorkBench
} }
return null; return null;
} }
// 计算相对路径的方法
public static string GetRelativePath(string baseDirectory, string fullPath) public static string GetRelativePath(string baseDirectory, string fullPath)
{ {
Uri baseUri = new(baseDirectory + System.IO.Path.DirectorySeparatorChar); Uri baseUri = new(baseDirectory + System.IO.Path.DirectorySeparatorChar);
@@ -2124,297 +2124,296 @@ namespace Serein.WorkBench
return Uri.UnescapeDataString(relativeUri.ToString().Replace('/', System.IO.Path.DirectorySeparatorChar)); return Uri.UnescapeDataString(relativeUri.ToString().Replace('/', System.IO.Path.DirectorySeparatorChar));
} }
#region UI层面上显示为 线
}
#region UI层面上显示为 线
public static class BsControl public static class BsControl
{
public static Connection Draw(Canvas canvas, Connection connection)
{ {
public static Connection Draw(Canvas canvas, Connection connection) connection.Canvas = canvas;
{ UpdateBezierLine(canvas, connection);
connection.Canvas = canvas; //MakeDraggable(canvas, connection, connection.Start);
UpdateBezierLine(canvas, connection); //MakeDraggable(canvas, connection, connection.End);
//MakeDraggable(canvas, connection, connection.Start);
//MakeDraggable(canvas, connection, connection.End);
if (connection.BezierPath == null) if (connection.BezierPath == null)
{
connection.BezierPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 };
Canvas.SetZIndex(connection.BezierPath, -1);
canvas.Children.Add(connection.BezierPath);
}
if (connection.ArrowPath == null)
{
connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), Fill = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 };
Canvas.SetZIndex(connection.ArrowPath, -1);
canvas.Children.Add(connection.ArrowPath);
}
BezierLineDrawer.UpdateBezierLine(canvas, connection.Start, connection.End, connection.BezierPath, connection.ArrowPath);
return connection;
}
private static bool isUpdating = false; // 是否正在更新线条显示
// 拖动时重新绘制
public static void UpdateBezierLine(Canvas canvas, Connection connection)
{
if (isUpdating)
return;
isUpdating = true;
canvas.Dispatcher.InvokeAsync(() =>
{
if (connection != null && connection.BezierPath == null)
{ {
connection.BezierPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 }; connection.BezierPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 };
Canvas.SetZIndex(connection.BezierPath, -1); //Canvas.SetZIndex(connection.BezierPath, -1);
canvas.Children.Add(connection.BezierPath); canvas.Children.Add(connection.BezierPath);
} }
if (connection.ArrowPath == null)
if (connection != null && connection.ArrowPath == null)
{ {
connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), Fill = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 }; connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), Fill = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 };
Canvas.SetZIndex(connection.ArrowPath, -1); //Canvas.SetZIndex(connection.ArrowPath, -1);
canvas.Children.Add(connection.ArrowPath); canvas.Children.Add(connection.ArrowPath);
} }
BezierLineDrawer.UpdateBezierLine(canvas, connection.Start, connection.End, connection.BezierPath, connection.ArrowPath); BezierLineDrawer.UpdateBezierLine(canvas, connection.Start, connection.End, connection.BezierPath, connection.ArrowPath);
return connection; isUpdating = false;
} });
private static bool isUpdating = false; // 是否正在更新线条显示
// 拖动时重新绘制
public static void UpdateBezierLine(Canvas canvas, Connection connection)
{
if (isUpdating)
return;
isUpdating = true;
canvas.Dispatcher.InvokeAsync(() =>
{
if (connection != null && connection.BezierPath == null)
{
connection.BezierPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 };
//Canvas.SetZIndex(connection.BezierPath, -1);
canvas.Children.Add(connection.BezierPath);
}
if (connection != null && connection.ArrowPath == null)
{
connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), Fill = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 };
//Canvas.SetZIndex(connection.ArrowPath, -1);
canvas.Children.Add(connection.ArrowPath);
}
BezierLineDrawer.UpdateBezierLine(canvas, connection.Start, connection.End, connection.BezierPath, connection.ArrowPath);
isUpdating = false;
});
}
// private static Point clickPosition; // 当前点击事件
// private static bool isDragging = false; // 是否正在移动控件
//private static void MakeDraggable(Canvas canvas, Connection connection, UIElement element)
//{
// if (connection.IsSetEven)
// {
// return;
// }
// element.MouseLeftButtonDown += (sender, e) =>
// {
// isDragging = true;
// //clickPosition = e.GetPosition(element);
// //element.CaptureMouse();
// };
// element.MouseLeftButtonUp += (sender, e) =>
// {
// isDragging = false;
// //element.ReleaseMouseCapture();
// };
// element.MouseMove += (sender, e) =>
// {
// if (isDragging)
// {
// if (VisualTreeHelper.GetParent(element) is Canvas canvas)
// {
// Point currentPosition = e.GetPosition(canvas);
// double newLeft = currentPosition.X - clickPosition.X;
// double newTop = currentPosition.Y - clickPosition.Y;
// Canvas.SetLeft(element, newLeft);
// Canvas.SetTop(element, newTop);
// UpdateBezierLine(canvas, connection);
// }
// }
// };
//}
} }
// private static Point clickPosition; // 当前点击事件
// private static bool isDragging = false; // 是否正在移动控件
//private static void MakeDraggable(Canvas canvas, Connection connection, UIElement element)
//{
// if (connection.IsSetEven)
// {
// return;
// }
public class Connection // element.MouseLeftButtonDown += (sender, e) =>
{ // {
public ConnectionType Type { get; set; } // isDragging = true;
public Canvas Canvas { get; set; }// 贝塞尔曲线所在画布 // //clickPosition = e.GetPosition(element);
// //element.CaptureMouse();
// };
// element.MouseLeftButtonUp += (sender, e) =>
// {
// isDragging = false;
// //element.ReleaseMouseCapture();
// };
public System.Windows.Shapes.Path BezierPath { get; set; }// 贝塞尔曲线路径 // element.MouseMove += (sender, e) =>
public System.Windows.Shapes.Path ArrowPath { get; set; } // 箭头路径 // {
// if (isDragging)
// {
// if (VisualTreeHelper.GetParent(element) is Canvas canvas)
// {
// Point currentPosition = e.GetPosition(canvas);
// double newLeft = currentPosition.X - clickPosition.X;
// double newTop = currentPosition.Y - clickPosition.Y;
public required NodeControlBase Start { get; set; } // 起始 // Canvas.SetLeft(element, newLeft);
public required NodeControlBase End { get; set; } // 结束 // Canvas.SetTop(element, newTop);
// UpdateBezierLine(canvas, connection);
private Storyboard? _animationStoryboard; // 动画Storyboard // }
// }
public void RemoveFromCanvas(Canvas canvas) // };
{
canvas.Children.Remove(BezierPath); // 移除线
canvas.Children.Remove(ArrowPath); // 移除线
_animationStoryboard?.Stop(); // 停止动画
}
public void Refresh()
{
BsControl.Draw(Canvas,this);
}
}
public static class BezierLineDrawer
{
public enum Localhost
{
Left,
Right,
Top,
Bottom,
}
// 绘制曲线
public static void UpdateBezierLine(Canvas canvas,
FrameworkElement startElement,
FrameworkElement endElement,
System.Windows.Shapes.Path bezierPath,
System.Windows.Shapes.Path arrowPath)
{
Point startPoint = startElement.TranslatePoint(new Point(startElement.ActualWidth / 2, startElement.ActualHeight / 2), canvas);
Point endPoint = CalculateEndpointOutsideElement(endElement, canvas, startPoint, out Localhost localhost);
PathFigure pathFigure = new PathFigure { StartPoint = startPoint };
BezierSegment bezierSegment;
if (localhost == Localhost.Left || localhost == Localhost.Right)
{
bezierSegment = new BezierSegment
{
Point1 = new Point((startPoint.X + endPoint.X) / 2, startPoint.Y),
Point2 = new Point((startPoint.X + endPoint.X) / 2, endPoint.Y),
Point3 = endPoint,
};
}
else // if (localhost == Localhost.Top || localhost == Localhost.Bottom)
{
bezierSegment = new BezierSegment
{
Point1 = new Point(startPoint.X, (startPoint.Y + endPoint.Y) / 2),
Point2 = new Point(endPoint.X, (startPoint.Y + endPoint.Y) / 2),
Point3 = endPoint,
};
}
pathFigure.Segments.Add(bezierSegment);
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures.Add(pathFigure);
bezierPath.Data = pathGeometry;
Point arrowStartPoint = CalculateBezierTangent(startPoint, bezierSegment.Point3, bezierSegment.Point2, endPoint);
UpdateArrowPath(endPoint, arrowStartPoint, arrowPath);
}
private static Point CalculateBezierTangent(Point startPoint, Point controlPoint1, Point controlPoint2, Point endPoint)
{
double t = 10.0; // 末端点
// 计算贝塞尔曲线在 t = 1 处的一阶导数
double dx = 3 * Math.Pow(1 - t, 2) * (controlPoint1.X - startPoint.X) +
6 * (1 - t) * t * (controlPoint2.X - controlPoint1.X) +
3 * Math.Pow(t, 2) * (endPoint.X - controlPoint2.X);
double dy = 3 * Math.Pow(1 - t, 2) * (controlPoint1.Y - startPoint.Y) +
6 * (1 - t) * t * (controlPoint2.Y - controlPoint1.Y) +
3 * Math.Pow(t, 2) * (endPoint.Y - controlPoint2.Y);
// 返回切线向量
return new Point(dx, dy);
}
// 绘制箭头
private static void UpdateArrowPath(Point endPoint,
Point controlPoint,
System.Windows.Shapes.Path arrowPath)
{
double arrowLength = 10;
double arrowWidth = 5;
Vector direction = endPoint - controlPoint;
direction.Normalize();
Point arrowPoint1 = endPoint + direction * arrowLength + new Vector(-direction.Y, direction.X) * arrowWidth;
Point arrowPoint2 = endPoint + direction * arrowLength + new Vector(direction.Y, -direction.X) * arrowWidth;
PathFigure arrowFigure = new PathFigure { StartPoint = endPoint };
arrowFigure.Segments.Add(new LineSegment(arrowPoint1, true));
arrowFigure.Segments.Add(new LineSegment(arrowPoint2, true));
arrowFigure.Segments.Add(new LineSegment(endPoint, true));
PathGeometry arrowGeometry = new PathGeometry();
arrowGeometry.Figures.Add(arrowFigure);
arrowPath.Data = arrowGeometry;
}
// 计算终点落点位置
private static Point CalculateEndpointOutsideElement(FrameworkElement element, Canvas canvas, Point startPoint, out Localhost localhost)
{
Point centerPoint = element.TranslatePoint(new Point(element.ActualWidth/2, element.ActualHeight/2), canvas);
Vector direction = centerPoint - startPoint;
direction.Normalize();
var tx = centerPoint.X - startPoint.X;
var ty = startPoint.Y - centerPoint.Y;
localhost = (tx < ty, Math.Abs(tx) > Math.Abs(ty)) switch
{
(true, true) => Localhost.Right,
(true, false) => Localhost.Bottom,
(false, true) => Localhost.Left,
(false, false) => Localhost.Top,
};
double halfWidth = element.ActualWidth / 2 + 6;
double halfHeight = element.ActualHeight / 2 + 6;
double margin = 0;
if (localhost == Localhost.Left)
{
centerPoint.X -= halfWidth;
centerPoint.Y -= direction.Y / Math.Abs(direction.X) * halfHeight - margin;
}
else if (localhost == Localhost.Right)
{
centerPoint.X -= -halfWidth;
centerPoint.Y -= direction.Y / Math.Abs(direction.X) * halfHeight - margin;
}
else if (localhost == Localhost.Top)
{
centerPoint.Y -= halfHeight;
centerPoint.X -= direction.X / Math.Abs(direction.Y) * halfWidth - margin;
}
else if (localhost == Localhost.Bottom)
{
centerPoint.Y -= -halfHeight;
centerPoint.X -= direction.X / Math.Abs(direction.Y) * halfWidth - margin;
}
return centerPoint;
}
public static SolidColorBrush GetStroke(ConnectionType 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(),
};
}
}
#endregion
//}
} }
public class Connection
{
public ConnectionType Type { get; set; }
public Canvas Canvas { get; set; }// 贝塞尔曲线所在画布
public System.Windows.Shapes.Path BezierPath { get; set; }// 贝塞尔曲线路径
public System.Windows.Shapes.Path ArrowPath { get; set; } // 箭头路径
public required NodeControlBase Start { get; set; } // 起始
public required NodeControlBase End { get; set; } // 结束
private Storyboard? _animationStoryboard; // 动画Storyboard
public void RemoveFromCanvas(Canvas canvas)
{
canvas.Children.Remove(BezierPath); // 移除线
canvas.Children.Remove(ArrowPath); // 移除线
_animationStoryboard?.Stop(); // 停止动画
}
public void Refresh()
{
BsControl.Draw(Canvas, this);
}
}
public static class BezierLineDrawer
{
public enum Localhost
{
Left,
Right,
Top,
Bottom,
}
// 绘制曲线
public static void UpdateBezierLine(Canvas canvas,
FrameworkElement startElement,
FrameworkElement endElement,
System.Windows.Shapes.Path bezierPath,
System.Windows.Shapes.Path arrowPath)
{
Point startPoint = startElement.TranslatePoint(new Point(startElement.ActualWidth / 2, startElement.ActualHeight / 2), canvas);
Point endPoint = CalculateEndpointOutsideElement(endElement, canvas, startPoint, out Localhost localhost);
PathFigure pathFigure = new PathFigure { StartPoint = startPoint };
BezierSegment bezierSegment;
if (localhost == Localhost.Left || localhost == Localhost.Right)
{
bezierSegment = new BezierSegment
{
Point1 = new Point((startPoint.X + endPoint.X) / 2, startPoint.Y),
Point2 = new Point((startPoint.X + endPoint.X) / 2, endPoint.Y),
Point3 = endPoint,
};
}
else // if (localhost == Localhost.Top || localhost == Localhost.Bottom)
{
bezierSegment = new BezierSegment
{
Point1 = new Point(startPoint.X, (startPoint.Y + endPoint.Y) / 2),
Point2 = new Point(endPoint.X, (startPoint.Y + endPoint.Y) / 2),
Point3 = endPoint,
};
}
pathFigure.Segments.Add(bezierSegment);
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures.Add(pathFigure);
bezierPath.Data = pathGeometry;
Point arrowStartPoint = CalculateBezierTangent(startPoint, bezierSegment.Point3, bezierSegment.Point2, endPoint);
UpdateArrowPath(endPoint, arrowStartPoint, arrowPath);
}
private static Point CalculateBezierTangent(Point startPoint, Point controlPoint1, Point controlPoint2, Point endPoint)
{
double t = 10.0; // 末端点
// 计算贝塞尔曲线在 t = 1 处的一阶导数
double dx = 3 * Math.Pow(1 - t, 2) * (controlPoint1.X - startPoint.X) +
6 * (1 - t) * t * (controlPoint2.X - controlPoint1.X) +
3 * Math.Pow(t, 2) * (endPoint.X - controlPoint2.X);
double dy = 3 * Math.Pow(1 - t, 2) * (controlPoint1.Y - startPoint.Y) +
6 * (1 - t) * t * (controlPoint2.Y - controlPoint1.Y) +
3 * Math.Pow(t, 2) * (endPoint.Y - controlPoint2.Y);
// 返回切线向量
return new Point(dx, dy);
}
// 绘制箭头
private static void UpdateArrowPath(Point endPoint,
Point controlPoint,
System.Windows.Shapes.Path arrowPath)
{
double arrowLength = 10;
double arrowWidth = 5;
Vector direction = endPoint - controlPoint;
direction.Normalize();
Point arrowPoint1 = endPoint + direction * arrowLength + new Vector(-direction.Y, direction.X) * arrowWidth;
Point arrowPoint2 = endPoint + direction * arrowLength + new Vector(direction.Y, -direction.X) * arrowWidth;
PathFigure arrowFigure = new PathFigure { StartPoint = endPoint };
arrowFigure.Segments.Add(new LineSegment(arrowPoint1, true));
arrowFigure.Segments.Add(new LineSegment(arrowPoint2, true));
arrowFigure.Segments.Add(new LineSegment(endPoint, true));
PathGeometry arrowGeometry = new PathGeometry();
arrowGeometry.Figures.Add(arrowFigure);
arrowPath.Data = arrowGeometry;
}
// 计算终点落点位置
private static Point CalculateEndpointOutsideElement(FrameworkElement element, Canvas canvas, Point startPoint, out Localhost localhost)
{
Point centerPoint = element.TranslatePoint(new Point(element.ActualWidth / 2, element.ActualHeight / 2), canvas);
Vector direction = centerPoint - startPoint;
direction.Normalize();
var tx = centerPoint.X - startPoint.X;
var ty = startPoint.Y - centerPoint.Y;
localhost = (tx < ty, Math.Abs(tx) > Math.Abs(ty)) switch
{
(true, true) => Localhost.Right,
(true, false) => Localhost.Bottom,
(false, true) => Localhost.Left,
(false, false) => Localhost.Top,
};
double halfWidth = element.ActualWidth / 2 + 6;
double halfHeight = element.ActualHeight / 2 + 6;
double margin = 0;
if (localhost == Localhost.Left)
{
centerPoint.X -= halfWidth;
centerPoint.Y -= direction.Y / Math.Abs(direction.X) * halfHeight - margin;
}
else if (localhost == Localhost.Right)
{
centerPoint.X -= -halfWidth;
centerPoint.Y -= direction.Y / Math.Abs(direction.X) * halfHeight - margin;
}
else if (localhost == Localhost.Top)
{
centerPoint.Y -= halfHeight;
centerPoint.X -= direction.X / Math.Abs(direction.Y) * halfWidth - margin;
}
else if (localhost == Localhost.Bottom)
{
centerPoint.Y -= -halfHeight;
centerPoint.X -= direction.X / Math.Abs(direction.Y) * halfWidth - margin;
}
return centerPoint;
}
public static SolidColorBrush GetStroke(ConnectionType 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(),
};
}
}
#endregion
} }