mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-05-01 12:59:30 +08:00
更改了dll参数类型,更改了流程执行,添加了异常分支处理
This commit is contained in:
4
.editorconfig
Normal file
4
.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[*.cs]
|
||||||
|
|
||||||
|
# CS8618: 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
|
||||||
|
dotnet_diagnostic.CS8618.severity = warning
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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 = () =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
//{
|
||||||
}
|
//}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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++)
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user