diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..091dadd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CS8618: 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 +dotnet_diagnostic.CS8618.severity = warning diff --git a/Library/IOC/ServiceContainer.cs b/Library/IOC/ServiceContainer.cs index 8af5029..92a83bc 100644 --- a/Library/IOC/ServiceContainer.cs +++ b/Library/IOC/ServiceContainer.cs @@ -18,8 +18,8 @@ namespace Serein.Library.IOC IServiceContainer Register(Type type, params object[] parameters); IServiceContainer Register(params object[] parameters); IServiceContainer Register(params object[] parameters) where TImplementation : TService; - T Get(); - object Get(Type type); + T GetOrInstantiate(); + object GetOrInstantiate(Type type); /// /// 创建目标类型的对象, 并注入依赖项 @@ -120,7 +120,7 @@ namespace Serein.Library.IOC return this; } - public object Get(Type type) + public object GetOrInstantiate(Type type) { @@ -140,24 +140,16 @@ namespace Serein.Library.IOC } - public T Get() + public T GetOrInstantiate() { - - if(!_dependencies.TryGetValue(typeof(T).FullName, out object value)) { Register(); value = Instantiate(typeof(T)); - } - - - return (T)value; - - //throw new InvalidOperationException("目标类型未创建实例"); } public IServiceContainer Build() @@ -234,7 +226,7 @@ namespace Serein.Library.IOC #region run() public IServiceContainer Run(Action action) { - var service = Get(); + var service = GetOrInstantiate(); if (service != null) { action(service); @@ -244,8 +236,8 @@ namespace Serein.Library.IOC public IServiceContainer Run(Action action) { - var service1 = Get(); - var service2 = Get(); + var service1 = GetOrInstantiate(); + var service2 = GetOrInstantiate(); action(service1, service2); return this; @@ -253,69 +245,69 @@ namespace Serein.Library.IOC public IServiceContainer Run(Action action) { - var service1 = Get(); - var service2 = Get(); - var service3 = Get(); + var service1 = GetOrInstantiate(); + var service2 = GetOrInstantiate(); + var service3 = GetOrInstantiate(); action(service1, service2, service3); return this; } public IServiceContainer Run(Action action) { - var service1 = Get(); - var service2 = Get(); - var service3 = Get(); - var service4 = Get(); + var service1 = GetOrInstantiate(); + var service2 = GetOrInstantiate(); + var service3 = GetOrInstantiate(); + var service4 = GetOrInstantiate(); action(service1, service2, service3, service4); return this; } public IServiceContainer Run(Action action) { - var service1 = Get(); - var service2 = Get(); - var service3 = Get(); - var service4 = Get(); - var service5 = Get(); + var service1 = GetOrInstantiate(); + var service2 = GetOrInstantiate(); + var service3 = GetOrInstantiate(); + var service4 = GetOrInstantiate(); + var service5 = GetOrInstantiate(); action(service1, service2, service3, service4, service5); return this; } public IServiceContainer Run(Action action) { - var service1 = Get(); - var service2 = Get(); - var service3 = Get(); - var service4 = Get(); - var service5 = Get(); - var service6 = Get(); + var service1 = GetOrInstantiate(); + var service2 = GetOrInstantiate(); + var service3 = GetOrInstantiate(); + var service4 = GetOrInstantiate(); + var service5 = GetOrInstantiate(); + var service6 = GetOrInstantiate(); action(service1, service2, service3, service4, service5, service6); return this; } public IServiceContainer Run(Action action) { - var service1 = Get(); - var service2 = Get(); - var service3 = Get(); - var service4 = Get(); - var service5 = Get(); - var service6 = Get(); - var service7 = Get(); + var service1 = GetOrInstantiate(); + var service2 = GetOrInstantiate(); + var service3 = GetOrInstantiate(); + var service4 = GetOrInstantiate(); + var service5 = GetOrInstantiate(); + var service6 = GetOrInstantiate(); + var service7 = GetOrInstantiate(); action(service1, service2, service3, service4, service5, service6, service7); return this; } public IServiceContainer Run(Action action) { - var service1 = Get(); - var service2 = Get(); - var service3 = Get(); - var service4 = Get(); - var service5 = Get(); - var service6 = Get(); - var service7 = Get(); - var service8 = Get(); + var service1 = GetOrInstantiate(); + var service2 = GetOrInstantiate(); + var service3 = GetOrInstantiate(); + var service4 = GetOrInstantiate(); + var service5 = GetOrInstantiate(); + var service6 = GetOrInstantiate(); + var service7 = GetOrInstantiate(); + var service8 = GetOrInstantiate(); action(service1, service2, service3, service4, service5, service6, service7, service8); return this; } diff --git a/MyDll/test.cs b/MyDll/IoTClientTest.cs similarity index 85% rename from MyDll/test.cs rename to MyDll/IoTClientTest.cs index ac269d7..0bc9ff0 100644 --- a/MyDll/test.cs +++ b/MyDll/IoTClientTest.cs @@ -8,17 +8,17 @@ using System.Threading.Tasks; namespace MyDll { - internal class test + internal class IoTClientTest { private void T() { SiemensClient client = new SiemensClient(SiemensVersion.S7_200Smart, "127.0.0.1", 102); //2、写操作 - client.Write("Q1.3", true); - client.Write("V2205", (short)11); - client.Write("V2209", 33); - client.Write("V2305", "orderCode"); //写入字符串 + //client.Write("Q1.3", true); + //client.Write("V2205", (short)11); + //client.Write("V2209", 33); + //client.Write("V2305", "orderCode"); //写入字符串 //3、读操作 var value1 = client.ReadBoolean("Q1.3").Value; diff --git a/MyDll/SampleCondition.cs b/MyDll/SampleCondition.cs index 70ca914..9357607 100644 --- a/MyDll/SampleCondition.cs +++ b/MyDll/SampleCondition.cs @@ -1,5 +1,6 @@ using Serein.Library.Http; using Serein.NodeFlow; +using Serein.NodeFlow.Model; using Serein.NodeFlow.Tool; using static MyDll.PlcDevice; namespace MyDll @@ -135,7 +136,7 @@ namespace MyDll #region 触发器 [MethodDetail(DynamicNodeType.Flipflop, "等待信号触发")] - public async Task WaitTask([Explicit] SignalType triggerType = SignalType.光电1) + public async Task WaitTask(SignalType triggerType = SignalType.光电1) { /*if (!Enum.TryParse(triggerValue, out SignalType triggerType) && Enum.IsDefined(typeof(SignalType), triggerType)) { @@ -150,16 +151,16 @@ namespace MyDll var result = await tcs.Task; //Interlocked.Increment(ref MyPlc.Count); // 原子自增 //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) { // await Console.Out.WriteLineAsync($"取消等待信号[{triggerType}]"); - return new FlipflopContext(FfState.Cancel); + return new FlipflopContext(FlowStateType.Error); } } [MethodDetail(DynamicNodeType.Flipflop, "等待信号触发")] - public async Task WaitTask2([Explicit] string triggerValue = nameof(SignalType.光电1)) + public async Task WaitTask2(string triggerValue = nameof(SignalType.光电1)) { try { @@ -173,12 +174,12 @@ namespace MyDll Interlocked.Increment(ref MyPlc.Count); // 原子自增 Console.WriteLine($"信号触发[{triggerType}] : {MyPlc.Count}"); - return new FlipflopContext(FfState.Succeed, MyPlc.Count); + return new FlipflopContext(FlowStateType.Succeed, MyPlc.Count); } catch(TcsSignalException ex) { // await Console.Out.WriteLineAsync($"取消等待信号[{triggerValue}]"); - return new FlipflopContext(ex.FfState); + return new FlipflopContext(ex.FsState); } } @@ -187,15 +188,17 @@ namespace MyDll #region 动作 [MethodDetail(DynamicNodeType.Action, "初始化")] - public PlcDevice PlcInit([Explicit] string ip = "192.168.1.1", - [Explicit] int port = 6688, - [Explicit] string tips = "测试") + public PlcDevice PlcInit(string ip = "192.168.1.1", + int port = 6688, + string tips = "测试") { MyPlc.InitDevice(ip, port, tips); return MyPlc; } + + [MethodDetail(DynamicNodeType.Action, "自增")] - public PlcDevice 自增([Explicit] int number = 1) + public PlcDevice 自增(int number = 1) { MyPlc.Count += number; return MyPlc; @@ -204,9 +207,9 @@ namespace MyDll [MethodDetail(DynamicNodeType.Action, "模拟循环触发")] public void 模拟循环触发(DynamicContext context, - [Explicit] int time = 20, - [Explicit] int count = 5, - [Explicit] SignalType signal = SignalType.光电1) + int time = 20, + int count = 5, + SignalType signal = SignalType.光电1) { Action action = () => { diff --git a/NodeFlow/Attribute.cs b/NodeFlow/Attribute.cs index 8e564fb..ddccad4 100644 --- a/NodeFlow/Attribute.cs +++ b/NodeFlow/Attribute.cs @@ -65,9 +65,9 @@ namespace Serein.NodeFlow /// /// 是否为显式参数 /// - [AttributeUsage(AttributeTargets.Parameter)] - public class ExplicitAttribute : Attribute // where TEnum : Enum - { - } + //[AttributeUsage(AttributeTargets.Parameter)] + //public class ExplicitAttribute : Attribute // where TEnum : Enum + //{ + //} } diff --git a/NodeFlow/DynamicContext.cs b/NodeFlow/DynamicContext.cs index 2d47e3a..82d9e94 100644 --- a/NodeFlow/DynamicContext.cs +++ b/NodeFlow/DynamicContext.cs @@ -1,4 +1,5 @@ using Serein.Library.IOC; +using Serein.NodeFlow.Model; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -12,23 +13,24 @@ using static System.Runtime.InteropServices.JavaScript.JSType; namespace Serein.NodeFlow { - public enum FfState - { - Succeed, - Cancel, - } + //public enum FfState + //{ + // Succeed, + // Cancel, + // Error, + //} /// /// 触发器上下文 /// public class FlipflopContext { - public FfState State { get; set; } + public FlowStateType State { get; set; } public object? Data { get; set; } /*public FlipflopContext() { State = FfState.Cancel; }*/ - public FlipflopContext(FfState ffState, object? data = null) + public FlipflopContext(FlowStateType ffState, object? data = null) { State = ffState; Data = data; @@ -144,7 +146,7 @@ namespace Serein.NodeFlow public NodeRunTcs NodeRunCts { get; set; } public Task CreateTimingTask(Action action, int time = 100, int count = -1) { - NodeRunCts ??= ServiceContainer.Get(); + NodeRunCts ??= ServiceContainer.GetOrInstantiate(); return Task.Factory.StartNew(async () => { for (int i = 0; i < count; i++) diff --git a/NodeFlow/Model/CompositeConditionNode.cs b/NodeFlow/Model/CompositeConditionNode.cs index 1160167..7d2119d 100644 --- a/NodeFlow/Model/CompositeConditionNode.cs +++ b/NodeFlow/Model/CompositeConditionNode.cs @@ -14,16 +14,22 @@ MethodDetails ??= node.MethodDetails; } + /// + /// 条件节点重写执行方法 + /// + /// + /// public override object? Execute(DynamicContext context) { // bool allTrue = ConditionNodes.All(condition => Judge(context,condition.MethodDetails)); // bool IsAllTrue = true; // 初始化为 true - FlowState = true; + FlowState = FlowStateType.Succeed; 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;// 一旦发现条件为假,立即退出循环 } } @@ -44,7 +50,7 @@ // } //} } - private bool Judge(DynamicContext context, SingleConditionNode node) + private FlowStateType Judge(DynamicContext context, SingleConditionNode node) { try { @@ -54,8 +60,8 @@ catch (Exception ex) { Console.WriteLine(ex.Message); + return FlowStateType.Error; } - return false; } diff --git a/NodeFlow/Model/NodeBase.cs b/NodeFlow/Model/NodeBase.cs index 3f14e93..402dfaf 100644 --- a/NodeFlow/Model/NodeBase.cs +++ b/NodeFlow/Model/NodeBase.cs @@ -25,6 +25,22 @@ namespace Serein.NodeFlow.Model Upstream, } + public enum FlowStateType + { + /// + /// 成功(方法成功执行) + /// + Succeed, + /// + /// 失败(方法没有成功执行,不过执行时没有发生非预期的错误) + /// + Fail, + /// + /// 异常(节点没有 + /// + Error, + } + /// /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 /// @@ -73,21 +89,30 @@ namespace Serein.NodeFlow.Model /// /// 当前状态(进入真分支还是假分支,异常分支在异常中确定) /// - public bool FlowState { get; set; } = true; - //public ConnectionType NextType { get; set; } = ConnectionType.IsTrue; + public FlowStateType FlowState { get; set; } = FlowStateType.Succeed; + public Exception Exception { get; set; } = null; + /// /// 当前传递数据 /// public object? FlowData { get; set; } = null; - // 正常流程节点调用 + /// + /// 执行节点对应的方法 + /// + /// 流程上下文 + /// 节点传回数据对象 public virtual object? Execute(DynamicContext context) { MethodDetails md = MethodDetails; 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) { @@ -110,89 +135,76 @@ namespace Serein.NodeFlow.Model else { result = ((Func)del).Invoke(md.ActingInstance, parameters); - - } } - // context.SetFlowData(result); - // CurrentData = result; + + return result; + } + catch (Exception ex) + { + FlowState = FlowStateType.Error; + Exception = ex; } return result; } - // 触发器调用 + /// + /// 执行等待触发器的方法 + /// + /// + /// 节点传回数据对象 + /// public virtual async Task ExecuteAsync(DynamicContext context) { MethodDetails md = MethodDetails; 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) { - // 调用委托并获取结果 - FlipflopContext flipflopContext = await ((Func>)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; - } - } - } + flipflopContext = await ((Func>)del).Invoke(MethodDetails.ActingInstance); } else { object?[]? parameters = GetParameters(context, MethodDetails); - // 调用委托并获取结果 + flipflopContext = await ((Func>)del).Invoke(MethodDetails.ActingInstance, parameters); + } - - FlipflopContext flipflopContext = await ((Func>)del).Invoke(MethodDetails.ActingInstance, parameters); - - - - if (flipflopContext != null) + if (flipflopContext != null) + { + FlowState = flipflopContext.State; + if (flipflopContext.State == FlowStateType.Succeed) { - if (flipflopContext.State == FfState.Cancel) - { - throw new Exception("取消此异步"); - } - else - { - FlowState = flipflopContext.State == FfState.Succeed; - result = flipflopContext.Data; - } + result = flipflopContext.Data; } } } + catch (Exception ex) + { + FlowState = FlowStateType.Error; + Exception = ex; + } return result; } - public async Task ExecuteStack(DynamicContext context) + /// + /// 开始执行 + /// + /// + /// + public async Task StartExecution(DynamicContext context) { - await Task.Run(async () => - { - await ExecuteStackTmp(context); - }); - } - - public async Task ExecuteStackTmp(DynamicContext context) - { - var cts = context.ServiceContainer.Get(); + var cts = context.ServiceContainer.GetOrInstantiate(); Stack stack = []; stack.Push(this); @@ -202,32 +214,38 @@ namespace Serein.NodeFlow.Model // 从栈中弹出一个节点作为当前节点进行处理 var currentNode = stack.Pop(); + // 设置方法执行的对象 if (currentNode.MethodDetails != null) { - currentNode.MethodDetails.ActingInstance ??= context.ServiceContainer.Get(MethodDetails.ActingInstanceType); - } // 设置方法执行的对象 + currentNode.MethodDetails.ActingInstance ??= context.ServiceContainer.GetOrInstantiate(MethodDetails.ActingInstanceType); + } // 获取上游分支,首先执行一次 var upstreamNodes = currentNode.UpstreamBranch; for (int i = upstreamNodes.Count - 1; i >= 0; i--) { upstreamNodes[i].PreviousNode = currentNode; - await upstreamNodes[i].ExecuteStack(context); + await upstreamNodes[i].StartExecution(context); } - if (currentNode.MethodDetails != null && currentNode.MethodDetails.MethodDynamicType == DynamicNodeType.Flipflop) { + // 触发器节点 currentNode.FlowData = await currentNode.ExecuteAsync(context); } else { + // 动作节点 currentNode.FlowData = currentNode.Execute(context); } - - var nextNodes = currentNode.FlowState ? currentNode.SucceedBranch - : currentNode.FailBranch; + var nextNodes = currentNode.FlowState switch + { + FlowStateType.Succeed => currentNode.SucceedBranch, + FlowStateType.Fail => currentNode.FailBranch, + FlowStateType.Error => currentNode.ErrorBranch, + _ => throw new Exception("非预期的枚举值") + }; // 将下一个节点集合中的所有节点逆序推入栈中 for (int i = nextNodes.Count - 1; i >= 0; i--) @@ -238,7 +256,9 @@ namespace Serein.NodeFlow.Model } } - + /// + /// 获取对应的参数数组 + /// public object[]? GetParameters(DynamicContext context, MethodDetails md) { // 用正确的大小初始化参数数组 @@ -355,7 +375,12 @@ namespace Serein.NodeFlow.Model return parameters; } - + /// + /// json文本反序列化为对象 + /// + /// + /// + /// private dynamic? ConvertValue(string value, Type targetType) { try diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs index 9bb4727..8a23224 100644 --- a/NodeFlow/Model/SingleConditionNode.cs +++ b/NodeFlow/Model/SingleConditionNode.cs @@ -36,7 +36,17 @@ namespace Serein.NodeFlow.Model { 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); return result; } diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs index 041a7e1..df3e679 100644 --- a/NodeFlow/Model/SingleExpOpNode.cs +++ b/NodeFlow/Model/SingleExpOpNode.cs @@ -25,7 +25,7 @@ namespace Serein.NodeFlow.Model var newData = SerinExpressionEvaluator.Evaluate(Expression, data, out bool isChange); - FlowState = true; + FlowState = FlowStateType.Succeed; Console.WriteLine(newData); if (isChange) { diff --git a/NodeFlow/NodeFlowStarter.cs b/NodeFlow/NodeFlowStarter.cs index f75db86..f4df7a9 100644 --- a/NodeFlow/NodeFlowStarter.cs +++ b/NodeFlow/NodeFlowStarter.cs @@ -102,7 +102,10 @@ namespace Serein.NodeFlow try { - await Task.WhenAll([startNode.ExecuteStack(context), .. tasks]); + await Task.Run(async () => + { + await Task.WhenAll([startNode.StartExecution(context), .. tasks]); + }); } catch (Exception ex) { @@ -133,24 +136,27 @@ namespace Serein.NodeFlow FlipflopContext flipflopContext = await func.Invoke(md.ActingInstance, parameters); - if (flipflopContext == null) { break; } - else if (flipflopContext.State == FfState.Cancel) + else if (flipflopContext.State == FlowStateType.Error) { 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; var tasks = singleFlipFlopNode.SucceedBranch.Select(nextNode => { var context = new DynamicContext(ServiceContainer); nextNode.PreviousNode = singleFlipFlopNode; - return nextNode.ExecuteStack(context); + return nextNode.StartExecution(context); }).ToArray(); Task.WaitAll(tasks); } diff --git a/NodeFlow/Tool/DelegateGenerator.cs b/NodeFlow/Tool/DelegateGenerator.cs index a3dbee9..cc49738 100644 --- a/NodeFlow/Tool/DelegateGenerator.cs +++ b/NodeFlow/Tool/DelegateGenerator.cs @@ -105,7 +105,7 @@ public static class DelegateGenerator return new ExplicitData { - IsExplicitData = it.GetCustomAttribute(typeof(ExplicitAttribute)) is ExplicitAttribute, + IsExplicitData = it.HasDefaultValue, Index = index, ExplicitType = it.ParameterType, ExplicitTypeName = explicitTypeName, diff --git a/NodeFlow/Tool/TcsSignal.cs b/NodeFlow/Tool/TcsSignal.cs index f03147f..1678906 100644 --- a/NodeFlow/Tool/TcsSignal.cs +++ b/NodeFlow/Tool/TcsSignal.cs @@ -1,14 +1,15 @@ using System.Collections.Concurrent; using Serein.NodeFlow; +using Serein.NodeFlow.Model; namespace Serein.NodeFlow.Tool { public class TcsSignalException : Exception { - public FfState FfState { get; set; } + public FlowStateType FsState { get; set; } public TcsSignalException(string? message) : base(message) { - FfState = FfState.Cancel; + FsState = FlowStateType.Error; } } diff --git a/SereinFlow.sln b/SereinFlow.sln index 872f6fb..28d15fd 100644 --- a/SereinFlow.sln +++ b/SereinFlow.sln @@ -13,6 +13,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Module.WAT", "Serein EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.NodeFlow", "NodeFlow\Serein.NodeFlow.csproj", "{7B51A19A-88AB-471E-BCE3-3888C67C936D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{938F25F8-E497-4FCF-A028-9309C155A8EF}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/SereinWAT/SereinWAT.cs b/SereinWAT/SereinWAT.cs index 3ab76f3..8366aa4 100644 --- a/SereinWAT/SereinWAT.cs +++ b/SereinWAT/SereinWAT.cs @@ -67,13 +67,13 @@ namespace Serein.Module #endregion [MethodDetail(DynamicNodeType.Action,"等待")] - public void Wait([Explicit]int time = 1000) + public void Wait(int time = 1000) { Thread.Sleep(time); } [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) { @@ -127,7 +127,7 @@ namespace Serein.Module } [MethodDetail(DynamicNodeType.Action,"进入网页")] - public void ToPage([Explicit] string url) + public void ToPage( string url) { if (url.StartsWith("https://") || url.StartsWith("http://")) { @@ -141,7 +141,7 @@ namespace Serein.Module [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 { @@ -163,12 +163,12 @@ namespace Serein.Module } [MethodDetail(DynamicNodeType.Action, "定位并操作元素")] - public IWebElement FindAndUseElement([Explicit] ByType byType = ByType.XPath, - [Explicit] string key = "", - [Explicit] ActionType actionType = ActionType.Click, - [Explicit] string text = "", - [Explicit] int index = 0, - [Explicit] int waitTime = 0) + public IWebElement FindAndUseElement( ByType byType = ByType.XPath, + string key = "", + ActionType actionType = ActionType.Click, + string text = "", + int index = 0, + int waitTime = 0) { Thread.Sleep(waitTime); By by = byType switch @@ -205,7 +205,7 @@ namespace Serein.Module } [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); @@ -230,7 +230,7 @@ namespace Serein.Module [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) { @@ -252,7 +252,7 @@ namespace Serein.Module [MethodDetail(DynamicNodeType.Action, "Js获取元素属性")] - public string GetAttribute(IWebElement element, [Explicit] string attributeName = "") + public string GetAttribute(IWebElement element, string attributeName = "") { return element.GetAttribute(attributeName); } diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs index 952e1c6..cc08f5d 100644 --- a/WorkBench/App.xaml.cs +++ b/WorkBench/App.xaml.cs @@ -120,10 +120,6 @@ namespace Serein.WorkBench public static string FileDataPath = ""; private void Application_Startup(object sender, StartupEventArgs e) { - - // 示例:写入调试信息 - Debug.WriteLine("应用程序启动"); - // 检查是否传入了参数 if (e.Args.Length == 1) { diff --git a/WorkBench/MainWindow.xaml b/WorkBench/MainWindow.xaml index c15f300..83e8212 100644 --- a/WorkBench/MainWindow.xaml +++ b/WorkBench/MainWindow.xaml @@ -74,7 +74,9 @@ + x:Name="FlowChartScrollViewer" + HorizontalScrollBarVisibility="Auto" + VerticalScrollBarVisibility="Auto"> ConcurrentDictionary DictMethodDetail = []; + /// /// 存储所有与节点有关的控件 /// private readonly List nodeControls = []; - // private readonly List nodeBases = []; /// /// 存储所有的连接 /// private readonly List connections = []; - /// /// 存放触发器节点(运行时全部调用) /// private readonly List flipflopNodes = []; - ///// - ///// 运行前的初始化(实例化类型) - ///// - //private readonly List loadingMethods = []; - ///// - ///// 初始化后属性注入以及某些需要设置的状态(注入依赖项) - ///// - //private readonly List initMethods = []; - ///// - ///// 结束运行时需要调用的方法 - ///// - //private readonly List exitMethods = []; - /// /// 记录拖动开始时的鼠标位置 /// @@ -196,20 +182,32 @@ namespace Serein.WorkBench /// /// 标记是否正在进行连接操作 /// - private bool IsConnecting { get; set; } + private bool IsConnecting; /// /// 标记是否正在拖动控件 /// - private bool IsDragging; + private bool IsControlDragging; + /// + /// 标记是否正在拖动画布 + /// private bool IsCanvasDragging; - private Point startMousePosition; - private TranslateTransform transform1; - + /// + /// 组合变换容器 + /// private TransformGroup canvasTransformGroup; + /// + /// 缩放画布 + /// private ScaleTransform scaleTransform; + /// + /// 平移画布 + /// private TranslateTransform translateTransform; - + /// + /// 流程起点 + /// + private NodeFlowStarter nodeFlowStarter; public MainWindow() @@ -225,15 +223,15 @@ namespace Serein.WorkBench //transform = new TranslateTransform(); //FlowChartCanvas.RenderTransform = transform; - canvasTransformGroup = new TransformGroup(); - scaleTransform = new ScaleTransform(); - translateTransform = new TranslateTransform(); + canvasTransformGroup = new TransformGroup(); + scaleTransform = new ScaleTransform(); + translateTransform = new TranslateTransform(); - canvasTransformGroup.Children.Add(scaleTransform); - canvasTransformGroup.Children.Add(translateTransform); + canvasTransformGroup.Children.Add(scaleTransform); + canvasTransformGroup.Children.Add(translateTransform); - FlowChartCanvas.RenderTransform = canvasTransformGroup; - FlowChartCanvas.RenderTransformOrigin = new Point(0.5, 0.5); + FlowChartCanvas.RenderTransform = canvasTransformGroup; + FlowChartCanvas.RenderTransformOrigin = new Point(0.5, 0.5); } private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) @@ -1166,7 +1164,7 @@ namespace Serein.WorkBench if (IsConnecting) return; - IsDragging = true; + IsControlDragging = true; startPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置 ((UIElement)sender).CaptureMouse(); // 捕获鼠标 } @@ -1179,7 +1177,7 @@ namespace Serein.WorkBench /// private void Block_MouseMove(object sender, MouseEventArgs e) { - if (IsDragging) + if (IsControlDragging) { Point currentPosition = e.GetPosition(FlowChartCanvas); // 获取当前鼠标位置 // 获取引发事件的控件 @@ -1296,7 +1294,7 @@ namespace Serein.WorkBench if (e.MiddleButton == MouseButtonState.Pressed) { IsCanvasDragging = true; - startMousePosition = e.GetPosition(this); + startPoint = e.GetPosition(this); FlowChartCanvas.CaptureMouse(); } } @@ -1317,6 +1315,7 @@ namespace Serein.WorkBench private void FlowChartCanvas_MouseWheel(object sender, MouseWheelEventArgs e) { + if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) { double scale = e.Delta > 0 ? 1.1 : 0.9; @@ -1403,13 +1402,13 @@ namespace Serein.WorkBench if (IsCanvasDragging) { Point currentMousePosition = e.GetPosition(this); - double deltaX = currentMousePosition.X - startMousePosition.X; - double deltaY = currentMousePosition.Y - startMousePosition.Y; + double deltaX = currentMousePosition.X - startPoint.X; + double deltaY = currentMousePosition.Y - startPoint.Y; translateTransform.X += deltaX; translateTransform.Y += deltaY; - startMousePosition = currentMousePosition; + startPoint = currentMousePosition; // Adjust canvas size and content if necessary AdjustCanvasSizeAndContent(deltaX, deltaY); @@ -1421,9 +1420,9 @@ namespace Serein.WorkBench /// private void Block_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { - if (IsDragging) + if (IsControlDragging) { - IsDragging = false; + IsControlDragging = false; ((UIElement)sender).ReleaseMouseCapture(); // 释放鼠标捕获 } else if (IsConnecting) @@ -1787,7 +1786,7 @@ namespace Serein.WorkBench } - private NodeFlowStarter nodeFlowStarter; + /// /// 运行测试 @@ -1804,8 +1803,11 @@ namespace Serein.WorkBench WriteLog("----------------\r\n"); } - - + /// + /// 退出 + /// + /// + /// private void ButtonDebugFlipflopNode_Click(object sender, RoutedEventArgs e) { nodeFlowStarter?.Exit(); @@ -2080,7 +2082,6 @@ namespace Serein.WorkBench } } - public static string? SaveContentToFile(string content) { // 创建一个新的保存文件对话框 @@ -2115,7 +2116,6 @@ namespace Serein.WorkBench } return null; } - // 计算相对路径的方法 public static string GetRelativePath(string baseDirectory, string fullPath) { 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)); } - #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); - //MakeDraggable(canvas, connection, connection.Start); - //MakeDraggable(canvas, connection, connection.End); + connection.Canvas = canvas; + UpdateBezierLine(canvas, connection); + //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 }; - Canvas.SetZIndex(connection.BezierPath, -1); + //Canvas.SetZIndex(connection.BezierPath, -1); 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 }; - Canvas.SetZIndex(connection.ArrowPath, -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 }; - //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); - // } - // } - // }; - - - //} + 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; + // } - public class Connection - { - public ConnectionType Type { get; set; } - public Canvas Canvas { get; set; }// 贝塞尔曲线所在画布 + // element.MouseLeftButtonDown += (sender, e) => + // { + // isDragging = true; + // //clickPosition = e.GetPosition(element); + // //element.CaptureMouse(); + // }; + // element.MouseLeftButtonUp += (sender, e) => + // { + // isDragging = false; + // //element.ReleaseMouseCapture(); + // }; - public System.Windows.Shapes.Path BezierPath { get; set; }// 贝塞尔曲线路径 - public System.Windows.Shapes.Path ArrowPath { get; set; } // 箭头路径 + // 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; - 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 + // Canvas.SetLeft(element, newLeft); + // Canvas.SetTop(element, newTop); + // UpdateBezierLine(canvas, connection); + // } + // } + // }; + //} } + + 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 + } \ No newline at end of file