From f5924aa31ef7c7d3bc347cf12aa09206fa436277 Mon Sep 17 00:00:00 2001
From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com>
Date: Fri, 20 Sep 2024 10:50:32 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E5=BC=82=E6=AD=A5=E9=87=8D?=
=?UTF-8?q?=E6=9E=84=E4=BA=86=E8=8A=82=E7=82=B9=E6=89=A7=E8=A1=8C=E6=96=B9?=
=?UTF-8?q?=E6=B3=95=EF=BC=8C=E5=B0=86=E8=A7=A6=E5=8F=91=E5=99=A8=E8=8A=82?=
=?UTF-8?q?=E7=82=B9=E4=B8=8E=E5=85=B6=E4=BB=96=E8=8A=82=E7=82=B9=E7=BB=9F?=
=?UTF-8?q?=E4=B8=80=E3=80=82=E4=BD=BF=E7=94=A8Channel=E4=BB=A3=E6=9B=BFTc?=
=?UTF-8?q?s=E6=9B=B4=E6=94=B9=E4=BA=86=E4=BF=A1=E5=8F=B7=E8=A7=A6?=
=?UTF-8?q?=E5=8F=91=EF=BC=8C=E4=BD=BF=E5=85=B6=E7=AC=A6=E5=90=88=E5=BC=82?=
=?UTF-8?q?=E6=AD=A5=E7=BC=96=E7=A8=8B=E7=9A=84=E4=B9=A0=E6=83=AF=E3=80=82?=
=?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E8=8A=82=E7=82=B9=E6=98=AF=E5=90=A6?=
=?UTF-8?q?=E5=90=AF=E7=94=A8=E5=8B=BE=E9=80=89=E6=A1=86=E3=80=81=E5=8F=82?=
=?UTF-8?q?=E6=95=B0=E9=81=AE=E7=BD=A9=E5=8B=BE=E9=80=89=E6=A1=86=EF=BC=8C?=
=?UTF-8?q?=E8=8A=82=E7=82=B9=E5=8F=B3=E9=94=AE=E9=9D=A2=E6=9D=BF=E5=A2=9E?=
=?UTF-8?q?=E5=8A=A0=E4=B8=AD=E6=96=AD=E5=8A=9F=E8=83=BD=EF=BC=88=E8=AF=95?=
=?UTF-8?q?=E9=AA=8C=EF=BC=89=E3=80=82=E5=A2=9E=E5=8A=A0=E4=BA=86=E9=80=89?=
=?UTF-8?q?=E6=8B=A9=E5=90=8E=E8=A2=AB=E9=80=89=E6=8B=A9=E7=9A=84=E8=8A=82?=
=?UTF-8?q?=E7=82=B9=E7=9A=84=E8=A7=86=E8=A7=89=E6=95=88=E6=9E=9C=E3=80=82?=
=?UTF-8?q?=E6=9B=B4=E6=94=B9=E5=B9=B3=E7=A7=BB=E7=BC=A9=E6=94=BE=E9=80=BB?=
=?UTF-8?q?=E8=BE=91=EF=BC=8C=E4=BD=BF=E5=85=B6=E6=9B=B4=E5=8A=A0=E7=AC=A6?=
=?UTF-8?q?=E5=90=88=E4=B8=80=E8=88=AC=E7=9A=84=E4=BD=BF=E7=94=A8=E4=B9=A0?=
=?UTF-8?q?=E6=83=AF=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Library.Core/NodeFlow/FlipflopContext.cs | 7 +-
Library.Framework/NodeFlow/FlipflopContext.cs | 9 +-
Library/Api/IFlipflopContext.cs | 3 +-
Library/Api/IFlowEnvironment.cs | 4 +-
Library/Entity/ExplicitData.cs | 9 +-
Library/Entity/MethodDetails.cs | 7 +
Library/Entity/NodeDebugSetting.cs | 47 ++
Library/Serein.Library.csproj | 1 +
Library/Utils/ChannelFlowInterrupt.cs | 122 +++++
Library/Utils/ChannelFlowTrigger.cs | 115 +++++
Library/Utils/TcsSignalFlipflop.cs | 64 ---
NodeFlow/Base/NodeModelBaseData.cs | 14 +-
NodeFlow/Base/NodeModelBaseFunc.cs | 232 ++++++----
NodeFlow/FlowEnvironment.cs | 70 ++-
NodeFlow/FlowStarter.cs | 94 ++--
NodeFlow/Model/CompositeActionNode.cs | 3 +-
NodeFlow/Model/CompositeConditionNode.cs | 7 +-
NodeFlow/Model/SingleConditionNode.cs | 5 +-
NodeFlow/Model/SingleExpOpNode.cs | 7 +-
NodeFlow/Model/SingleFlipflopNode.cs | 57 ++-
WorkBench/LogWindow.xaml.cs | 8 +
WorkBench/MainWindow.xaml | 4 +-
WorkBench/MainWindow.xaml.cs | 428 ++++++++++--------
WorkBench/Node/NodeControlViewModelBase.cs | 64 ++-
WorkBench/Node/View/ActionNodeControl.xaml | 88 ++--
WorkBench/Node/View/ActionRegionControl.xaml | 4 +-
WorkBench/Node/View/ConditionNodeControl.xaml | 2 -
WorkBench/Themes/TypeViewerWindow.xaml.cs | 257 +++++++++--
.../InvertableBooleanToVisibilityConverter.cs | 38 ++
WorkBench/tool/LogTextWriter.cs | 67 ++-
30 files changed, 1298 insertions(+), 539 deletions(-)
create mode 100644 Library/Entity/NodeDebugSetting.cs
create mode 100644 Library/Utils/ChannelFlowInterrupt.cs
create mode 100644 Library/Utils/ChannelFlowTrigger.cs
delete mode 100644 Library/Utils/TcsSignalFlipflop.cs
create mode 100644 WorkBench/Tool/Converters/InvertableBooleanToVisibilityConverter.cs
diff --git a/Library.Core/NodeFlow/FlipflopContext.cs b/Library.Core/NodeFlow/FlipflopContext.cs
index 8dd9884..f59edf6 100644
--- a/Library.Core/NodeFlow/FlipflopContext.cs
+++ b/Library.Core/NodeFlow/FlipflopContext.cs
@@ -1,5 +1,6 @@
using Serein.Library.Api;
using Serein.Library.Enums;
+using Serein.Library.NodeFlow.Tool;
namespace Serein.Library.Core.NodeFlow
{
@@ -67,16 +68,16 @@ namespace Serein.Library.Core.NodeFlow
{
public FlipflopStateType State { get; set; }
- public object Data { get; set; }
+ public TriggerData TriggerData { get; set; }
public FlipflopContext(FlipflopStateType ffState)
{
State = ffState;
}
- public FlipflopContext(FlipflopStateType ffState, object data)
+ public FlipflopContext(FlipflopStateType ffState, TriggerData data)
{
State = ffState;
- Data = data;
+ TriggerData = data;
}
diff --git a/Library.Framework/NodeFlow/FlipflopContext.cs b/Library.Framework/NodeFlow/FlipflopContext.cs
index 5557d63..cf39e0c 100644
--- a/Library.Framework/NodeFlow/FlipflopContext.cs
+++ b/Library.Framework/NodeFlow/FlipflopContext.cs
@@ -1,5 +1,6 @@
using Serein.Library.Api;
using Serein.Library.Enums;
+using Serein.Library.NodeFlow.Tool;
using System;
using System.Threading.Tasks;
@@ -59,7 +60,7 @@ namespace Serein.Library.Framework.NodeFlow
{
public FlipflopStateType State { get; set; }
//public TResult? Data { get; set; }
- public object Data { get; set; }
+ public TriggerData TriggerData { get; set; }
public FlipflopContext(FlipflopStateType ffState)
{
State = ffState;
@@ -67,7 +68,11 @@ namespace Serein.Library.Framework.NodeFlow
public FlipflopContext(FlipflopStateType ffState, object data)
{
State = ffState;
- Data = data;
+ TriggerData = new TriggerData
+ {
+ Type = TriggerType.External,
+ Value = data
+ };
}
}
diff --git a/Library/Api/IFlipflopContext.cs b/Library/Api/IFlipflopContext.cs
index 5f6b8a8..88e8947 100644
--- a/Library/Api/IFlipflopContext.cs
+++ b/Library/Api/IFlipflopContext.cs
@@ -1,10 +1,11 @@
using Serein.Library.Enums;
+using Serein.Library.NodeFlow.Tool;
namespace Serein.Library.Api
{
public interface IFlipflopContext
{
FlipflopStateType State { get; set; }
- object Data { get; set; }
+ TriggerData TriggerData { get; set; }
}
}
diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs
index 57e5bf1..1d83946 100644
--- a/Library/Api/IFlowEnvironment.cs
+++ b/Library/Api/IFlowEnvironment.cs
@@ -1,5 +1,6 @@
using Serein.Library.Entity;
using Serein.Library.Enums;
+using Serein.Library.Utils;
using System;
using System.Collections.Generic;
using System.Reflection;
@@ -212,6 +213,8 @@ namespace Serein.Library.Api
public interface IFlowEnvironment
{
+ ChannelFlowInterrupt ChannelFlowInterrupt { get; set; }
+
event FlowRunCompleteHandler OnFlowRunComplete;
event ProjectLoadedHandler OnProjectLoaded;
@@ -290,7 +293,6 @@ namespace Serein.Library.Api
void RemoteNode(string nodeGuid);
-
}
}
diff --git a/Library/Entity/ExplicitData.cs b/Library/Entity/ExplicitData.cs
index 7067977..f80e68d 100644
--- a/Library/Entity/ExplicitData.cs
+++ b/Library/Entity/ExplicitData.cs
@@ -16,7 +16,7 @@ namespace Serein.Library.Entity
///
public int Index { get; set; }
///
- /// 是否为显式参数
+ /// 是否为显式参数(固定值/表达式)
///
public bool IsExplicitData { get; set; }
/////
@@ -45,21 +45,16 @@ namespace Serein.Library.Entity
public string DataValue { get; set; }
-
-
public string[] Items { get; set; }
-
-
-
public ExplicitData Clone() => new ExplicitData()
{
Index = Index,
IsExplicitData = IsExplicitData,
// ExplicitType = ExplicitType,
+ ExplicitTypeName = ExplicitTypeName,
DataType = DataType,
ParameterName = ParameterName,
- ExplicitTypeName = ExplicitTypeName,
DataValue = string.IsNullOrEmpty(DataValue) ? string.Empty : DataValue,
Items = Items.Select(it => it).ToArray(),
};
diff --git a/Library/Entity/MethodDetails.cs b/Library/Entity/MethodDetails.cs
index 71c3d61..7edafcf 100644
--- a/Library/Entity/MethodDetails.cs
+++ b/Library/Entity/MethodDetails.cs
@@ -28,10 +28,17 @@ namespace Serein.Library.Entity
MethodName = MethodName,
MethodLockName = MethodLockName,
IsNetFramework = IsNetFramework,
+ IsProtectionParameter = IsProtectionParameter,
ExplicitDatas = ExplicitDatas?.Select(it => it.Clone()).ToArray(),
};
}
+
+ ///
+ /// 是否保护参数
+ ///
+ public bool IsProtectionParameter { get; set; }
+
///
/// 作用实例的类型
///
diff --git a/Library/Entity/NodeDebugSetting.cs b/Library/Entity/NodeDebugSetting.cs
new file mode 100644
index 0000000..c605267
--- /dev/null
+++ b/Library/Entity/NodeDebugSetting.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Serein.Library.Entity
+{
+ public class NodeDebugSetting
+ {
+ ///
+ /// 是否使能(调试中断功能)
+ ///
+ public bool IsEnable { get; set; } = true;
+
+ ///
+ /// 是否中断(调试中断功能)
+ ///
+ public bool IsInterrupt { get; set; } = false;
+
+ ///
+ /// 中断级别,暂时停止继续执行后继分支。
+ ///
+ public InterruptClass InterruptClass { get; set; } = InterruptClass.None;
+ }
+
+ ///
+ /// 中断级别,暂时停止继续执行后继分支。
+ ///
+ public enum InterruptClass
+ {
+ ///
+ /// 不中断
+ ///
+ None,
+ ///
+ /// 分支中断,当前节点。
+ ///
+ Branch,
+ ///
+ /// 分组中断,相同中断分组的节点。
+ ///
+ Group,
+ ///
+ /// 全局中断,其它所有节点。
+ ///
+ Global,
+ }
+}
diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj
index bd3f82b..8f12865 100644
--- a/Library/Serein.Library.csproj
+++ b/Library/Serein.Library.csproj
@@ -16,6 +16,7 @@
+
diff --git a/Library/Utils/ChannelFlowInterrupt.cs b/Library/Utils/ChannelFlowInterrupt.cs
new file mode 100644
index 0000000..1391444
--- /dev/null
+++ b/Library/Utils/ChannelFlowInterrupt.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+
+namespace Serein.Library.Utils
+{
+ public class ChannelFlowInterrupt
+ {
+ ///
+ /// 中断取消类型
+ ///
+ public enum CancelType
+ {
+ Manual,
+ Overtime
+ }
+
+ // 使用并发字典管理每个信号对应的 Channel
+ private readonly ConcurrentDictionary> _channels = new ConcurrentDictionary>();
+
+ ///
+ /// 创建信号并指定超时时间,到期后自动触发(异步方法)
+ ///
+ /// 信号标识符
+ /// 超时时间
+ /// 等待任务
+ public async Task CreateChannelWithTimeoutAsync(string signal, TimeSpan outTime)
+ {
+ var channel = GetOrCreateChannel(signal);
+ var cts = new CancellationTokenSource();
+
+ // 异步任务:超时后自动触发信号
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ await Task.Delay(outTime, cts.Token);
+ await channel.Writer.WriteAsync(CancelType.Overtime);
+ }
+ catch (OperationCanceledException)
+ {
+ // 超时任务被取消
+ }
+ }, cts.Token);
+
+ // 等待信号传入(超时或手动触发)
+ var result = await channel.Reader.ReadAsync();
+ return result;
+ }
+
+ ///
+ /// 创建信号并指定超时时间,到期后自动触发(同步阻塞方法)
+ ///
+ /// 信号标识符
+ /// 超时时间
+ public CancelType CreateChannelWithTimeoutSync(string signal, TimeSpan timeout)
+ {
+ var channel = GetOrCreateChannel(signal);
+ var cts = new CancellationTokenSource();
+ CancellationToken token = cts.Token;
+
+ // 异步任务:超时后自动触发信号
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ await Task.Delay(timeout, token);
+ await channel.Writer.WriteAsync(CancelType.Overtime);
+ }
+ catch (OperationCanceledException)
+ {
+ // 任务被取消
+ }
+ });
+
+ // 同步阻塞直到信号触发或超时
+ var result = channel.Reader.ReadAsync().AsTask().GetAwaiter().GetResult();
+ return result;
+ }
+
+ ///
+ /// 触发信号
+ ///
+ /// 信号字符串
+ /// 是否成功触发
+ public bool TriggerSignal(string signal)
+ {
+ if (_channels.TryGetValue(signal, out var channel))
+ {
+ // 手动触发信号
+ channel.Writer.TryWrite(CancelType.Manual);
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// 取消所有任务
+ ///
+ public void CancelAllTasks()
+ {
+ foreach (var channel in _channels.Values)
+ {
+ channel.Writer.Complete();
+ }
+ _channels.Clear();
+ }
+
+ ///
+ /// 获取或创建指定信号的 Channel
+ ///
+ /// 信号字符串
+ /// 对应的 Channel
+ private Channel GetOrCreateChannel(string signal)
+ {
+ return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded());
+ }
+ }
+}
+
diff --git a/Library/Utils/ChannelFlowTrigger.cs b/Library/Utils/ChannelFlowTrigger.cs
new file mode 100644
index 0000000..4ec726d
--- /dev/null
+++ b/Library/Utils/ChannelFlowTrigger.cs
@@ -0,0 +1,115 @@
+
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+
+
+namespace Serein.Library.NodeFlow.Tool
+{
+ ///
+ /// 触发类型
+ ///
+ public enum TriggerType
+ {
+ ///
+ /// 外部触发
+ ///
+ External,
+ ///
+ /// 超时触发
+ ///
+ Overtime
+ }
+ public class TriggerData
+ {
+ public TriggerType Type { get; set; }
+ public object Value { get; set; }
+ }
+
+
+ public class ChannelFlowTrigger where TSignal : struct, Enum
+ {
+ // 使用并发字典管理每个枚举信号对应的 Channel
+ private readonly ConcurrentDictionary> _channels = new ConcurrentDictionary>();
+
+ ///
+ /// 创建信号并指定超时时间,到期后自动触发(异步方法)
+ ///
+ /// 枚举信号标识符
+ /// 超时时间
+ /// 等待任务
+ public async Task CreateChannelWithTimeoutAsync(TSignal signal, TimeSpan outTime, TResult outValue)
+ {
+ var channel = GetOrCreateChannel(signal);
+ var cts = new CancellationTokenSource();
+
+ // 异步任务:超时后自动触发信号
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ await Task.Delay(outTime, cts.Token);
+ TriggerData triggerData = new TriggerData()
+ {
+ Value = outValue,
+ Type = TriggerType.Overtime,
+ };
+ await channel.Writer.WriteAsync(triggerData);
+ }
+ catch (OperationCanceledException)
+ {
+ // 超时任务被取消
+ }
+ }, cts.Token);
+
+ // 等待信号传入(超时或手动触发)
+ var result = await channel.Reader.ReadAsync();
+ return result;
+ }
+
+ ///
+ /// 触发信号
+ ///
+ /// 枚举信号标识符
+ /// 是否成功触发
+ public bool TriggerSignal(TSignal signal, TResult value)
+ {
+ if (_channels.TryGetValue(signal, out var channel))
+ {
+ TriggerData triggerData = new TriggerData()
+ {
+ Value = value,
+ Type = TriggerType.External,
+ };
+ // 手动触发信号
+ channel.Writer.TryWrite(triggerData);
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// 取消所有任务
+ ///
+ public void CancelAllTasks()
+ {
+ foreach (var channel in _channels.Values)
+ {
+ channel.Writer.Complete();
+ }
+ _channels.Clear();
+ }
+
+ ///
+ /// 获取或创建指定信号的 Channel
+ ///
+ /// 枚举信号标识符
+ /// 对应的 Channel
+ private Channel GetOrCreateChannel(TSignal signal)
+ {
+ return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded());
+ }
+ }
+}
diff --git a/Library/Utils/TcsSignalFlipflop.cs b/Library/Utils/TcsSignalFlipflop.cs
deleted file mode 100644
index fa98d9d..0000000
--- a/Library/Utils/TcsSignalFlipflop.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using Serein.Library.Ex;
-using System;
-using System.Collections.Concurrent;
-using System.Threading.Tasks;
-
-namespace Serein.Library.Core.NodeFlow.Tool
-{
- //public class TcsSignalException : Exception
- //{
- // public FlowStateType FsState { get; set; }
- // public TcsSignalException(string? message) : base(message)
- // {
- // FsState = FlowStateType.Error;
- // }
- //}
-
- public class TcsSignalFlipflop where TSignal : struct, Enum
- {
- public ConcurrentDictionary> TcsEvent { get; } = new ConcurrentDictionary>();
-
- public ConcurrentDictionary TcsLock { get; } = new ConcurrentDictionary();
-
- ///
- /// 触发信号
- ///
- ///
- /// 信号
- /// 传递的参数
- /// 是否成功触发
- public bool TriggerSignal(TSignal signal, T value)
- {
- var tcsLock = TcsLock.GetOrAdd(signal, new object());
- lock (tcsLock)
- {
- if (TcsEvent.TryRemove(signal, out var waitTcs))
- {
- waitTcs.SetResult(value);
- return true;
- }
- return false;
- }
- }
-
- public TaskCompletionSource
///
///
- public override object? Execute(IDynamicContext context)
+ //public override object? Executing(IDynamicContext context)
+ public override Task ExecutingAsync(IDynamicContext context)
{
// 条件区域中遍历每个条件节点
foreach (SingleConditionNode? node in ConditionNodes)
@@ -37,7 +38,7 @@ namespace Serein.NodeFlow.Model
break;
}
}
- return PreviousNode?.FlowData;
+ return Task.FromResult( PreviousNode?.FlowData);
}
@@ -45,7 +46,7 @@ namespace Serein.NodeFlow.Model
{
try
{
- node.Execute(context);
+ node.ExecutingAsync(context);
return node.NextOrientation;
}
catch (Exception ex)
diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs
index 8082fa1..8f82ffe 100644
--- a/NodeFlow/Model/SingleConditionNode.cs
+++ b/NodeFlow/Model/SingleConditionNode.cs
@@ -28,7 +28,8 @@ namespace Serein.NodeFlow.Model
public string Expression { get; set; }
- public override object? Execute(IDynamicContext context)
+ //public override object? Executing(IDynamicContext context)
+ public override Task ExecutingAsync(IDynamicContext context)
{
// 接收上一节点参数or自定义参数内容
object? result;
@@ -52,7 +53,7 @@ namespace Serein.NodeFlow.Model
}
Console.WriteLine($"{result} {Expression} -> " + NextOrientation);
- return result;
+ return Task.FromResult(result);
}
internal override Parameterdata[] GetParameterdatas()
diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs
index 0900e5a..3faaaf5 100644
--- a/NodeFlow/Model/SingleExpOpNode.cs
+++ b/NodeFlow/Model/SingleExpOpNode.cs
@@ -18,7 +18,8 @@ namespace Serein.NodeFlow.Model
public string Expression { get; set; }
- public override object? Execute(IDynamicContext context)
+ //public override async Task Executing(IDynamicContext context)
+ public override Task ExecutingAsync(IDynamicContext context)
{
var data = PreviousNode?.FlowData;
@@ -37,13 +38,13 @@ namespace Serein.NodeFlow.Model
}
NextOrientation = ConnectionType.IsSucceed;
- return result;
+ return Task.FromResult(result);
}
catch (Exception ex)
{
NextOrientation = ConnectionType.IsError;
RuningException = ex;
- return PreviousNode?.FlowData;
+ return Task.FromResult(PreviousNode?.FlowData);
}
}
diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs
index 76ad23b..edd0b8f 100644
--- a/NodeFlow/Model/SingleFlipflopNode.cs
+++ b/NodeFlow/Model/SingleFlipflopNode.cs
@@ -1,18 +1,67 @@
using Serein.Library.Api;
using Serein.Library.Entity;
+using Serein.Library.Enums;
using Serein.Library.Ex;
using Serein.NodeFlow.Base;
+using static Serein.Library.Utils.ChannelFlowInterrupt;
namespace Serein.NodeFlow.Model
{
public class SingleFlipflopNode : NodeModelBase
{
- public override object? Execute(IDynamicContext context)
+ //public override async Task Executing(IDynamicContext context)
+ //public override Task ExecutingAsync(IDynamicContext context)
+ //{
+ // NextOrientation = Library.Enums.ConnectionType.IsError;
+ // RuningException = new FlipflopException ("无法以非await/async的形式调用触发器");
+ // return null;
+ //}
+
+
+ public override async Task ExecutingAsync(IDynamicContext context)
{
- NextOrientation = Library.Enums.ConnectionType.IsError;
- RuningException = new FlipflopException ("无法以非await/async的形式调用触发器");
- return null;
+ #region 执行前中断
+ if (TryCreateInterruptTask(context, this, out Task? task))
+ {
+ var cancelType = await task!;
+ await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支");
+ }
+ #endregion
+
+ MethodDetails md = MethodDetails;
+ Delegate del = md.MethodDelegate;
+ object instance = md.ActingInstance;
+ var haveParameter = md.ExplicitDatas.Length >= 0;
+ try
+ {
+ // 调用委托并获取结果
+ Task flipflopTask = haveParameter switch
+ {
+ true => ((Func>)del).Invoke(instance, GetParameters(context, md)), // 执行流程中的触发器方法时获取入参参数
+ false => ((Func>)del).Invoke(instance),
+ };
+
+ IFlipflopContext flipflopContext = (await flipflopTask) ?? throw new FlipflopException("没有返回上下文");
+ NextOrientation = flipflopContext.State.ToContentType();
+ if(flipflopContext.TriggerData.Type == Library.NodeFlow.Tool.TriggerType.Overtime)
+ {
+ throw new FlipflopException("");
+ }
+ return flipflopContext.TriggerData.Value;
+ }
+ catch (FlipflopException ex)
+ {
+ NextOrientation = ConnectionType.None;
+ RuningException = ex;
+ throw;
+ }
+ catch (Exception ex)
+ {
+ NextOrientation = ConnectionType.IsError;
+ RuningException = ex;
+ return null;
+ }
}
internal override Parameterdata[] GetParameterdatas()
diff --git a/WorkBench/LogWindow.xaml.cs b/WorkBench/LogWindow.xaml.cs
index 4595c67..85e7e16 100644
--- a/WorkBench/LogWindow.xaml.cs
+++ b/WorkBench/LogWindow.xaml.cs
@@ -11,6 +11,7 @@ namespace Serein.WorkBench
{
InitializeComponent();
}
+
public void AppendText(string text)
{
Dispatcher.BeginInvoke(() =>
@@ -19,6 +20,13 @@ namespace Serein.WorkBench
LogTextBox.ScrollToEnd();
});
}
+ public void Clear()
+ {
+ Dispatcher.BeginInvoke(() =>
+ {
+ LogTextBox.Clear();
+ });
+ }
private void ClearLog_Click(object sender, RoutedEventArgs e)
{
LogTextBox.Clear();
diff --git a/WorkBench/MainWindow.xaml b/WorkBench/MainWindow.xaml
index c6cdcac..9f18b27 100644
--- a/WorkBench/MainWindow.xaml
+++ b/WorkBench/MainWindow.xaml
@@ -7,6 +7,8 @@
AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver"
Loaded="Window_Loaded"
ContentRendered="Window_ContentRendered"
+ PreviewKeyDown="Window_PreviewKeyDown"
+ PreviewTextInput="Window_PreviewTextInput"
Closing="Window_Closing">
@@ -108,7 +110,7 @@
Stroke="Blue"
StrokeThickness="2"
Fill="LightBlue"
- Opacity="0.5"
+ Opacity="0.2"
Panel.ZIndex="999999"
Visibility="Collapsed"/>
diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs
index c249a58..6bd27d7 100644
--- a/WorkBench/MainWindow.xaml.cs
+++ b/WorkBench/MainWindow.xaml.cs
@@ -92,9 +92,17 @@ namespace Serein.WorkBench
private readonly List selectNodeControls = [];
///
- /// 记录拖动开始时的鼠标位置
+ /// 记录开始拖动节点控件时的鼠标位置
///
- private Point startPoint;
+ private Point startControlDragPoint;
+ ///
+ /// 记录移动画布开始时的鼠标位置
+ ///
+ private Point startCanvasDragPoint;
+ ///
+ /// 记录开始选取节点控件时的鼠标位置
+ ///
+ private Point startSelectControolPoint;
///
/// 记录开始连接的文本块
@@ -134,8 +142,10 @@ namespace Serein.WorkBench
logWindow = new LogWindow();
logWindow.Show();
// 重定向 Console 输出
- var logTextWriter = new LogTextWriter(WriteLog);
+ var logTextWriter = new LogTextWriter(WriteLog,() => logWindow.Clear());;
Console.SetOut(logTextWriter);
+
+
InitUI();
var project = App.FData;
@@ -202,7 +212,7 @@ namespace Serein.WorkBench
//{
// connection.Refresh();
//}
- Console.WriteLine((FlowChartStackPanel.ActualWidth, FlowChartStackPanel.ActualHeight));
+ //Console.WriteLine((FlowChartStackPanel.ActualWidth, FlowChartStackPanel.ActualHeight));
}
///
@@ -517,62 +527,6 @@ namespace Serein.WorkBench
#region 节点控件的创建
- private static TControl CreateNodeControl(NodeModelBase model)
- where TControl : NodeControlBase
- where TViewModel : NodeControlViewModelBase
- {
-
- if (model == null)
- {
- throw new Exception("无法创建节点控件");
- }
-
- var viewModel = Activator.CreateInstance(typeof(TViewModel), [model]);
- var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]);
- if (controlObj is TControl control)
- {
- return control;
- }
- else
- {
- throw new Exception("无法创建节点控件");
- }
- }
-
- private static TControl CreateNodeControl(MethodDetails? methodDetails = null)
- where TNode : NodeModelBase
- where TControl : NodeControlBase
- where TViewModel : NodeControlViewModelBase
- {
-
- var nodeObj = Activator.CreateInstance(typeof(TNode));
- var nodeBase = nodeObj as NodeModelBase;
- if (nodeBase == null)
- {
- throw new Exception("无法创建节点控件");
- }
-
-
- nodeBase.Guid = Guid.NewGuid().ToString();
-
- if (methodDetails != null)
- {
- var md = methodDetails.Clone();
- nodeBase.DisplayName = md.MethodTips;
- nodeBase.MethodDetails = md;
- }
-
- var viewModel = Activator.CreateInstance(typeof(TViewModel), [nodeObj]);
- var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel] );
- if(controlObj is TControl control)
- {
- return control;
- }
- else
- {
- throw new Exception("无法创建节点控件");
- }
- }
///
/// 创建了节点,添加到画布。配置默认事件
@@ -593,50 +547,6 @@ namespace Serein.WorkBench
});
}
- ///
- /// 配置节点右键菜单
- ///
- ///
- private void ConfigureContextMenu(NodeControlBase nodeControl)
- {
- var contextMenu = new ContextMenu();
-
- // var nodeModel = nodeControl.ViewModel.Node;
-
- if (nodeControl.ViewModel.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void))
- {
- contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) =>
- {
- DisplayReturnTypeTreeViewer(returnType);
- }));
- }
- var nodeGuid = nodeControl?.ViewModel?.Node?.Guid;
- contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid)));
- contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoteNode(nodeGuid)));
-
- contextMenu.Items.Add(CreateMenuItem("添加 真分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsSucceed)));
- contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsFail)));
- contextMenu.Items.Add(CreateMenuItem("添加 异常分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsError)));
- contextMenu.Items.Add(CreateMenuItem("添加 上游分支", (s, e) => StartConnection(nodeControl, ConnectionType.Upstream)));
-
-
- nodeControl.ContextMenu = contextMenu;
- }
-
- ///
- /// 创建菜单子项
- ///
- ///
- ///
- ///
- private static MenuItem CreateMenuItem(string header, RoutedEventHandler handler)
- {
- var menuItem = new MenuItem { Header = header };
- menuItem.Click += handler;
- return menuItem;
- }
-
-
///
/// 配置节点事件
///
@@ -682,6 +592,55 @@ namespace Serein.WorkBench
#region 右键菜单事件
+ ///
+ /// 配置节点右键菜单
+ ///
+ ///
+ private void ConfigureContextMenu(NodeControlBase nodeControl)
+ {
+ var contextMenu = new ContextMenu();
+
+ // var nodeModel = nodeControl.ViewModel.Node;
+
+ if (nodeControl.ViewModel.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void))
+ {
+ contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) =>
+ {
+ DisplayReturnTypeTreeViewer(returnType);
+ }));
+ }
+ var nodeGuid = nodeControl?.ViewModel?.Node?.Guid;
+
+ MenuItem? debugMenu = null;
+ debugMenu = CreateMenuItem("在此中断", (s, e) =>
+ {
+ if (nodeControl!.ViewModel.DebugSetting.IsInterrupt)
+ {
+ nodeControl.ViewModel.IsInterrupt = false;
+ debugMenu!.Header = "在此中断";
+ }
+ else
+ {
+ nodeControl.ViewModel.IsInterrupt = true;
+ debugMenu!.Header = "取消中断";
+ }
+ });
+ contextMenu.Items.Add(debugMenu);
+
+
+ contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid)));
+ contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoteNode(nodeGuid)));
+
+
+ contextMenu.Items.Add(CreateMenuItem("添加 真分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsSucceed)));
+ contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsFail)));
+ contextMenu.Items.Add(CreateMenuItem("添加 异常分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsError)));
+ contextMenu.Items.Add(CreateMenuItem("添加 上游分支", (s, e) => StartConnection(nodeControl, ConnectionType.Upstream)));
+
+
+ nodeControl.ContextMenu = contextMenu;
+ }
+
///
/// 配置连接曲线的右键菜单
///
@@ -693,6 +652,7 @@ namespace Serein.WorkBench
connection.ArrowPath.ContextMenu = contextMenu;
connection.BezierPath.ContextMenu = contextMenu;
}
+
///
/// 删除该连线
///
@@ -776,24 +736,7 @@ namespace Serein.WorkBench
///
private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e)
{
- if (IsSelectControl && e.LeftButton == MouseButtonState.Pressed) // 正在选取节点
- {
- // 获取当前鼠标位置
- Point currentPoint = e.GetPosition(FlowChartCanvas);
-
- // 更新选取矩形的位置和大小
- double x = Math.Min(currentPoint.X, startPoint.X);
- double y = Math.Min(currentPoint.Y, startPoint.Y);
- double width = Math.Abs(currentPoint.X - startPoint.X);
- double height = Math.Abs(currentPoint.Y - startPoint.Y);
-
- Canvas.SetLeft(SelectionRectangle, x);
- Canvas.SetTop(SelectionRectangle, y);
- SelectionRectangle.Width = width;
- SelectionRectangle.Height = height;
- }
-
-
+
if (IsConnecting) // 正在连接节点
{
Point position = e.GetPosition(FlowChartCanvas);
@@ -809,22 +752,41 @@ namespace Serein.WorkBench
if (IsCanvasDragging) // 正在移动画布
{
Point currentMousePosition = e.GetPosition(this);
- double deltaX = currentMousePosition.X - startPoint.X;
- double deltaY = currentMousePosition.Y - startPoint.Y;
+ double deltaX = currentMousePosition.X - startCanvasDragPoint.X;
+ double deltaY = currentMousePosition.Y - startCanvasDragPoint.Y;
translateTransform.X += deltaX;
translateTransform.Y += deltaY;
- startPoint = currentMousePosition;
+ startCanvasDragPoint = currentMousePosition;
foreach (var line in Connections)
{
line.Refresh();
}
-
- e.Handled = true; // 防止事件传播影响其他控件
}
+ if (IsSelectControl && e.LeftButton == MouseButtonState.Pressed) // 正在选取节点
+ {
+ // 获取当前鼠标位置
+ Point currentPoint = e.GetPosition(FlowChartCanvas);
+
+ // 更新选取矩形的位置和大小
+ double x = Math.Min(currentPoint.X, startSelectControolPoint.X);
+ double y = Math.Min(currentPoint.Y, startSelectControolPoint.Y);
+ double width = Math.Abs(currentPoint.X - startSelectControolPoint.X);
+ double height = Math.Abs(currentPoint.Y - startSelectControolPoint.Y);
+ /*double x = Math.Min(currentPoint.X, startControlDragPoint.X);
+ double y = Math.Min(currentPoint.Y, startControlDragPoint.Y);
+ double width = Math.Abs(currentPoint.X - startControlDragPoint.X);
+ double height = Math.Abs(currentPoint.Y - startControlDragPoint.Y);*/
+
+ Canvas.SetLeft(SelectionRectangle, x);
+ Canvas.SetTop(SelectionRectangle, y);
+ SelectionRectangle.Width = width;
+ SelectionRectangle.Height = height;
+
+ }
}
///
@@ -910,25 +872,6 @@ namespace Serein.WorkBench
return false;
}
- ///
- /// 穿透元素获取区域容器
- ///
- ///
- ///
- ///
- private static T GetParentOfType(DependencyObject element) where T : DependencyObject
- {
- while (element != null)
- {
- if (element is T)
- {
- return element as T;
- }
- element = VisualTreeHelper.GetParent(element);
- }
- return null;
- }
-
///
/// 将节点放在目标区域中
///
@@ -972,12 +915,10 @@ namespace Serein.WorkBench
///
private void Block_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
- if (IsConnecting)
- return;
-
IsControlDragging = true;
- startPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置
+ startControlDragPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置
((UIElement)sender).CaptureMouse(); // 捕获鼠标
+ e.Handled = true; // 防止事件传播影响其他控件
}
///
@@ -985,7 +926,15 @@ namespace Serein.WorkBench
///
private void Block_MouseMove(object sender, MouseEventArgs e)
{
- if (IsControlDragging) // 如果正在拖动控件
+ if (IsConnecting)
+ return;
+ if (IsCanvasDragging)
+ return;
+ if (IsSelectControl)
+ return;
+
+ var IsSelect = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
+ if (!IsSelect && IsControlDragging) // 如果正在拖动控件
{
Point currentPosition = e.GetPosition(FlowChartCanvas); // 获取当前鼠标位置
// 获取引发事件的控件
@@ -994,8 +943,8 @@ namespace Serein.WorkBench
return;
}
- double deltaX = currentPosition.X - startPoint.X; // 计算X轴方向的偏移量
- double deltaY = currentPosition.Y - startPoint.Y; // 计算Y轴方向的偏移量
+ double deltaX = currentPosition.X - startControlDragPoint.X; // 计算X轴方向的偏移量
+ double deltaY = currentPosition.Y - startControlDragPoint.Y; // 计算Y轴方向的偏移量
double newLeft = Canvas.GetLeft(block) + deltaX; // 新的左边距
double newTop = Canvas.GetTop(block) + deltaY; // 新的上边距
@@ -1012,7 +961,7 @@ namespace Serein.WorkBench
UpdateConnections(block);
- startPoint = currentPosition; // 更新起始点位置
+ startControlDragPoint = currentPosition; // 更新起始点位置
}
}
@@ -1159,13 +1108,14 @@ namespace Serein.WorkBench
#region 拖动画布实现缩放平移效果
private void FlowChartCanvas_MouseDown(object sender, MouseButtonEventArgs e)
{
- if (e.MiddleButton == MouseButtonState.Pressed)
- {
- IsCanvasDragging = true;
- startPoint = e.GetPosition(this);
- FlowChartCanvas.CaptureMouse();
- e.Handled = true; // 防止事件传播影响其他控件
- }
+ IsCanvasDragging = true;
+ startCanvasDragPoint = e.GetPosition(this);
+ FlowChartCanvas.CaptureMouse();
+ e.Handled = true; // 防止事件传播影响其他控件
+ //if (e.MiddleButton == MouseButtonState.Pressed)
+ //{
+
+ //}
}
private void FlowChartCanvas_MouseUp(object sender, MouseButtonEventArgs e)
@@ -1180,7 +1130,7 @@ namespace Serein.WorkBench
// 单纯缩放画布,不改变画布大小
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))
{
if (e.Delta < 0 && scaleTransform.ScaleX < 0.2) return;
if (e.Delta > 0 && scaleTransform.ScaleY > 1.5) return;
@@ -1355,26 +1305,29 @@ namespace Serein.WorkBench
///
private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
- if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
- {
- IsSelectControl = true;
+ IsSelectControl = true;
- // 开始选取时,记录鼠标起始点
- startPoint = e.GetPosition(FlowChartCanvas);
+ // 开始选取时,记录鼠标起始点
+ startSelectControolPoint = e.GetPosition(FlowChartCanvas);
- // 初始化选取矩形的位置和大小
- Canvas.SetLeft(SelectionRectangle, startPoint.X);
- Canvas.SetTop(SelectionRectangle, startPoint.Y);
- SelectionRectangle.Width = 0;
- SelectionRectangle.Height = 0;
+ // 初始化选取矩形的位置和大小
+ Canvas.SetLeft(SelectionRectangle, startSelectControolPoint.X);
+ Canvas.SetTop(SelectionRectangle, startSelectControolPoint.Y);
+ SelectionRectangle.Width = 0;
+ SelectionRectangle.Height = 0;
- // 显示选取矩形
- SelectionRectangle.Visibility = Visibility.Visible;
- SelectionRectangle.ContextMenu ??= ConfiguerSelectionRectangle();
+ // 显示选取矩形
+ SelectionRectangle.Visibility = Visibility.Visible;
+ SelectionRectangle.ContextMenu ??= ConfiguerSelectionRectangle();
- // 捕获鼠标,以便在鼠标移动到Canvas外部时仍能处理事件
- FlowChartCanvas.CaptureMouse();
- }
+ // 捕获鼠标,以便在鼠标移动到Canvas外部时仍能处理事件
+ FlowChartCanvas.CaptureMouse();
+
+ //if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
+ //{
+
+ //}
+ e.Handled = true; // 防止事件传播影响其他控件
}
private ContextMenu ConfiguerSelectionRectangle()
@@ -1448,6 +1401,7 @@ namespace Serein.WorkBench
if(selectNodeControls.Count == 0)
{
Console.WriteLine($"没有选择控件");
+ SelectionRectangle.Visibility = Visibility.Collapsed;
return;
}
Console.WriteLine($"一共选取了{selectNodeControls.Count}个控件");
@@ -1455,6 +1409,8 @@ namespace Serein.WorkBench
{
node.ViewModel.Selected();
node.ViewModel.CancelSelect();
+ node.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFC700"));
+ node.BorderThickness = new Thickness(4);
}
}
private void CancelSelectNode()
@@ -1462,13 +1418,109 @@ namespace Serein.WorkBench
foreach (var node in selectNodeControls)
{
node.ViewModel.CancelSelect();
+ node.BorderBrush = Brushes.Black;
+ node.BorderThickness = new Thickness(0);
}
selectNodeControls.Clear();
}
#endregion
+ #region 窗体静态方法
+ private static TControl CreateNodeControl(NodeModelBase model)
+ where TControl : NodeControlBase
+ where TViewModel : NodeControlViewModelBase
+ {
+
+ if (model == null)
+ {
+ throw new Exception("无法创建节点控件");
+ }
+
+ var viewModel = Activator.CreateInstance(typeof(TViewModel), [model]);
+ var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]);
+ if (controlObj is TControl control)
+ {
+ return control;
+ }
+ else
+ {
+ throw new Exception("无法创建节点控件");
+ }
+ }
+
+ private static TControl CreateNodeControl(MethodDetails? methodDetails = null)
+ where TNode : NodeModelBase
+ where TControl : NodeControlBase
+ where TViewModel : NodeControlViewModelBase
+ {
+
+ var nodeObj = Activator.CreateInstance(typeof(TNode));
+ var nodeBase = nodeObj as NodeModelBase;
+ if (nodeBase == null)
+ {
+ throw new Exception("无法创建节点控件");
+ }
+
+
+ nodeBase.Guid = Guid.NewGuid().ToString();
+
+ if (methodDetails != null)
+ {
+ var md = methodDetails.Clone();
+ nodeBase.DisplayName = md.MethodTips;
+ nodeBase.MethodDetails = md;
+ }
+
+ var viewModel = Activator.CreateInstance(typeof(TViewModel), [nodeObj]);
+ var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]);
+ if (controlObj is TControl control)
+ {
+ return control;
+ }
+ else
+ {
+ throw new Exception("无法创建节点控件");
+ }
+ }
+
+
+ ///
+ /// 创建菜单子项
+ ///
+ ///
+ ///
+ ///
+ public static MenuItem CreateMenuItem(string header, RoutedEventHandler handler)
+ {
+ var menuItem = new MenuItem { Header = header };
+ menuItem.Click += handler;
+ return menuItem;
+ }
+
+
+
+ ///
+ /// 穿透元素获取区域容器
+ ///
+ ///
+ ///
+ ///
+ private static T GetParentOfType(DependencyObject element) where T : DependencyObject
+ {
+ while (element != null)
+ {
+ if (element is T)
+ {
+ return element as T;
+ }
+ element = VisualTreeHelper.GetParent(element);
+ }
+ return null;
+ }
+
+ #endregion
@@ -1505,7 +1557,11 @@ namespace Serein.WorkBench
private async void ButtonDebugRun_Click(object sender, RoutedEventArgs e)
{
logWindow?.Show();
- await FlowEnvironment.StartAsync();
+
+ await FlowEnvironment.StartAsync(); // 快
+
+ //await Task.Run( FlowEnvironment.StartAsync); // 上下文多次切换的场景中吗慢了1/5
+ //await Task.Factory.StartNew(FlowEnvironment.StartAsync); // 慢了1/5
}
///
@@ -1614,6 +1670,20 @@ namespace Serein.WorkBench
Uri relativeUri = baseUri.MakeRelativeUri(fullUri);
return Uri.UnescapeDataString(relativeUri.ToString().Replace('/', System.IO.Path.DirectorySeparatorChar));
}
+
+ private void Window_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ {
+
+ }
+
+ private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
+ {
+ //if (e.KeyStates == Keyboard.GetKeyStates(Key.D8) && Keyboard.Modifiers == ModifierKeys.Shift)
+ //if (Keyboard.Modifiers == ModifierKeys.Shift)
+ //{
+ // startSelectControolPoint = e.GetPosition(FlowChartCanvas);
+ //}
+ }
}
#region 创建两个控件之间的连接关系,在UI层面上显示为 带箭头指向的贝塞尔曲线
diff --git a/WorkBench/Node/NodeControlViewModelBase.cs b/WorkBench/Node/NodeControlViewModelBase.cs
index 5976375..0e8d1e5 100644
--- a/WorkBench/Node/NodeControlViewModelBase.cs
+++ b/WorkBench/Node/NodeControlViewModelBase.cs
@@ -23,6 +23,9 @@ namespace Serein.WorkBench.Node.ViewModel
///
internal NodeModelBase Node { get; }
+
+
+
private bool isSelect;
///
/// 表示节点控件是否被选中
@@ -37,29 +40,68 @@ namespace Serein.WorkBench.Node.ViewModel
}
}
- private MethodDetails methodDetails;
-
- public MethodDetails MethodDetails
+ public NodeDebugSetting DebugSetting
{
- get => methodDetails;
+ get => Node.DebugSetting;
set
{
- methodDetails = value;
- OnPropertyChanged();
+ if (value != null)
+ {
+ Node.DebugSetting = value;
+ OnPropertyChanged(nameof(DebugSetting));
+ }
}
}
+ public MethodDetails MethodDetails
+ {
+ get => Node.MethodDetails;
+ set
+ {
+ if(value != null)
+ {
+ Node.MethodDetails = value;
+ OnPropertyChanged(nameof(MethodDetails));
+ }
+ }
+ }
+
+ public bool IsInterrupt
+ {
+ get => Node.DebugSetting.IsInterrupt;
+ set
+ {
+ if (value)
+ {
+ Node.Interrupt();
+ }
+ else
+ {
+ Node.CancelInterrupt();
+ }
+ OnPropertyChanged(nameof(IsInterrupt));
+ }
+ }
+
+ //public bool IsProtectionParameter
+ //{
+ // get => MethodDetails.IsProtectionParameter;
+ // set
+ // {
+ // MethodDetails.IsProtectionParameter = value;
+ // OnPropertyChanged(nameof(IsInterrupt));
+ // }
+ //}
+
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
-
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
- ///
- ///
- ///
+
+
public void Selected()
{
IsSelect = true;
@@ -69,5 +111,7 @@ namespace Serein.WorkBench.Node.ViewModel
{
IsSelect = false;
}
+
+
}
}
diff --git a/WorkBench/Node/View/ActionNodeControl.xaml b/WorkBench/Node/View/ActionNodeControl.xaml
index 6c968fe..b6c1ae5 100644
--- a/WorkBench/Node/View/ActionNodeControl.xaml
+++ b/WorkBench/Node/View/ActionNodeControl.xaml
@@ -5,43 +5,73 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
xmlns:vm="clr-namespace:Serein.WorkBench.Node.ViewModel"
+ xmlns:Converters="clr-namespace:Serein.WorkBench.Tool.Converters"
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
MaxWidth="300">
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WorkBench/Node/View/ActionRegionControl.xaml b/WorkBench/Node/View/ActionRegionControl.xaml
index 9040307..236d16d 100644
--- a/WorkBench/Node/View/ActionRegionControl.xaml
+++ b/WorkBench/Node/View/ActionRegionControl.xaml
@@ -6,7 +6,7 @@
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
MaxWidth="300">
-
+
diff --git a/WorkBench/Node/View/ConditionNodeControl.xaml b/WorkBench/Node/View/ConditionNodeControl.xaml
index 2b09c93..92adc6d 100644
--- a/WorkBench/Node/View/ConditionNodeControl.xaml
+++ b/WorkBench/Node/View/ConditionNodeControl.xaml
@@ -8,9 +8,7 @@
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
MaxWidth="300">
-
-
diff --git a/WorkBench/Themes/TypeViewerWindow.xaml.cs b/WorkBench/Themes/TypeViewerWindow.xaml.cs
index 631d5aa..3354ff3 100644
--- a/WorkBench/Themes/TypeViewerWindow.xaml.cs
+++ b/WorkBench/Themes/TypeViewerWindow.xaml.cs
@@ -20,9 +20,7 @@ namespace Serein.WorkBench.Themes
///
public partial class TypeViewerWindow : Window
{
-
public TypeViewerWindow()
-
{
InitializeComponent();
}
@@ -34,55 +32,244 @@ namespace Serein.WorkBench.Themes
if (Type == null)
return;
- var rootNode = new TreeViewItem { Header = Type.Name };
- AddMembersToTreeNode(rootNode, Type);
+ TypeNodeDetails typeNodeDetails = new TypeNodeDetails
+ {
+ Name = Type.Name,
+ DataType = Type,
+ };
+ var rootNode = new TreeViewItem { Header = Type.Name, Tag = typeNodeDetails };
+ AddPlaceholderNode(rootNode); // 添加占位符节点
TypeTreeView.Items.Clear();
TypeTreeView.Items.Add(rootNode);
+
+ rootNode.Expanded += TreeViewItem_Expanded; // 监听节点展开事件
+ }
+
+ ///
+ /// 添加占位符节点
+ ///
+ private void AddPlaceholderNode(TreeViewItem node)
+ {
+ node.Items.Add(new TreeViewItem { Header = "Loading..." });
+ }
+
+ ///
+ /// 节点展开事件,延迟加载子节点
+ ///
+ private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
+ {
+ var item = (TreeViewItem)sender;
+
+ // 如果已经加载过子节点,则不再重复加载
+ if (item.Items.Count == 1 && item.Items[0] is TreeViewItem placeholder && placeholder.Header.ToString() == "Loading...")
+ {
+ item.Items.Clear();
+ if (item.Tag is TypeNodeDetails typeNodeDetails)
+ {
+ AddMembersToTreeNode(item, typeNodeDetails.DataType);
+ }
+
+ }
+
+
}
///
/// 添加属性节点
///
- ///
- ///
private void AddMembersToTreeNode(TreeViewItem node, Type type)
{
var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
foreach (var member in members)
{
- TreeViewItem memberNode;
- try
+ TreeViewItem memberNode = ConfigureTreeViewItem(member); // 生成类型节点的子项
+ if (ConfigureTreeItemMenu(memberNode,member, out ContextMenu? contextMenu))
{
- memberNode = new TreeViewItem { Header = member.Name };
- }
- catch
- {
- return;
- }
-
- if (member is PropertyInfo property)
- {
- var propertyType = property.PropertyType;
- memberNode.Header = $"{member.Name} : {propertyType.Name}";
- if (!propertyType.IsPrimitive && propertyType != typeof(string))
- {
- // 递归显示类型属性的节点
- AddMembersToTreeNode(memberNode, propertyType);
- }
- }
- else if (member is MethodInfo method)
- {
- var parameters = method.GetParameters();
- var paramStr = string.Join(", ", parameters.Select(p => $"{p.ParameterType.Name} {p.Name}"));
- memberNode.Header = $"{member.Name}({paramStr})";
- }
- else if (member is FieldInfo field)
- {
- memberNode.Header = $"{member.Name} : {field.FieldType.Name}";
+ memberNode.ContextMenu = contextMenu; // 设置子项节点的事件
}
- node.Items.Add(memberNode);
+ node.Items.Add(memberNode); // 添加到父节点中
}
}
+
+
+ ///
+ /// 生成类型节点的子项
+ ///
+ ///
+ ///
+ private TreeViewItem ConfigureTreeViewItem(MemberInfo member)
+ {
+ TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
+ if (member is PropertyInfo property)
+ {
+ TypeNodeDetails typeNodeDetails = new TypeNodeDetails
+ {
+ ItemType = TreeItemType.Property,
+ DataType = property.PropertyType,
+ Name = property.Name,
+ DataValue = property,
+ };
+ memberNode.Tag = typeNodeDetails;
+
+ var propertyType = typeNodeDetails.DataType;
+ memberNode.Header = $"{member.Name} : {propertyType.Name}";
+
+ if (!propertyType.IsPrimitive && propertyType != typeof(string))
+ {
+ // 延迟加载类型的子属性,添加占位符节点
+ AddPlaceholderNode(memberNode);
+ memberNode.Expanded += TreeViewItem_Expanded; // 监听展开事件
+ }
+ }
+ else if (member is MethodInfo method)
+ {
+ TypeNodeDetails typeNodeDetails = new TypeNodeDetails
+ {
+ ItemType = TreeItemType.Method,
+ DataType = typeof(MethodInfo),
+ Name = method.Name,
+ DataValue = null,
+ };
+ memberNode.Tag = typeNodeDetails;
+
+ var parameters = method.GetParameters();
+ var paramStr = string.Join(", ", parameters.Select(p => $"{p.ParameterType.Name} {p.Name}"));
+ memberNode.Header = $"{member.Name}({paramStr})";
+ }
+ else if (member is FieldInfo field)
+ {
+ TypeNodeDetails typeNodeDetails = new TypeNodeDetails
+ {
+ ItemType = TreeItemType.Field,
+ DataType = field.FieldType,
+ Name = field.Name,
+ DataValue = field,
+ };
+ memberNode.Tag = typeNodeDetails;
+ memberNode.Header = $"{member.Name} : {field.FieldType.Name}";
+ }
+ return memberNode;
+ }
+
+
+ ///
+ /// 设置子项节点的事件
+ ///
+ ///
+ ///
+ private bool ConfigureTreeItemMenu(TreeViewItem memberNode, MemberInfo member,out ContextMenu? contextMenu)
+ {
+ bool isChange = false;
+ if (member is PropertyInfo property)
+ {
+ //isChange = true;
+ contextMenu = new ContextMenu();
+ }
+ else if (member is MethodInfo method)
+ {
+ //isChange = true;
+ contextMenu = new ContextMenu();
+ }
+ else if (member is FieldInfo field)
+ {
+ isChange = true;
+ contextMenu = new ContextMenu();
+ contextMenu.Items.Add(MainWindow.CreateMenuItem($"取值表达式", (s, e) =>
+ {
+ string fullPath = GetNodeFullPath(memberNode);
+ string copyValue = "@Get " + fullPath;
+ Clipboard.SetDataObject(copyValue);
+ }));
+ }
+ else
+ {
+ contextMenu = new ContextMenu();
+ }
+ return isChange;
+ }
+
+
+
+ ///
+ /// 获取当前节点的完整路径,例如 "node1.node2.node3.node4"
+ ///
+ /// 目标节点
+ /// 节点路径
+ private string GetNodeFullPath(TreeViewItem node)
+ {
+ if (node == null)
+ return string.Empty;
+
+ TypeNodeDetails typeNodeDetails = (TypeNodeDetails)node.Tag;
+ var parent = GetParentTreeViewItem(node);
+ if (parent != null)
+ {
+ // 递归获取父节点的路径,并拼接当前节点的 Header
+ return $"{GetNodeFullPath(parent)}.{typeNodeDetails.Name}";
+ }
+ else
+ {
+ return "";
+
+
+ // 没有父节点,则说明这是根节点,直接返回 Header
+ // return typeNodeDetails.Name.ToString();
+ }
+ }
+
+ ///
+ /// 获取指定节点的父级节点
+ ///
+ /// 目标节点
+ /// 父节点
+ private TreeViewItem GetParentTreeViewItem(TreeViewItem node)
+ {
+ DependencyObject parent = VisualTreeHelper.GetParent(node);
+ while (parent != null && !(parent is TreeViewItem))
+ {
+ parent = VisualTreeHelper.GetParent(parent);
+ }
+ return parent as TreeViewItem;
+ }
+
+
+
+ public class TypeNodeDetails
+ {
+ ///
+ /// 属性名称
+ ///
+ public string Name { get; set; }
+ ///
+ /// 属性类型
+ ///
+ public TreeItemType ItemType { get; set; }
+
+
+ ///
+ /// 数据类型
+ ///
+ public Type DataType { get; set; }
+ ///
+ /// 数据(调试用?)
+ ///
+ public object DataValue { get; set; }
+ ///
+ /// 数据路径
+ ///
+ public string DataPath { get; set; }
+ }
+
+ public enum TreeItemType
+ {
+ Property,
+ Method,
+ Field
+ }
+
+
+
}
+
}
diff --git a/WorkBench/Tool/Converters/InvertableBooleanToVisibilityConverter.cs b/WorkBench/Tool/Converters/InvertableBooleanToVisibilityConverter.cs
new file mode 100644
index 0000000..39b5d18
--- /dev/null
+++ b/WorkBench/Tool/Converters/InvertableBooleanToVisibilityConverter.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+using System.Windows;
+
+namespace Serein.WorkBench.Tool.Converters
+{
+ [ValueConversion(typeof(bool), typeof(Visibility))]
+ public class InvertableBooleanToVisibilityConverter : IValueConverter
+ {
+ enum Parameters
+ {
+ Normal, Inverted
+ }
+
+ public object Convert(object value, Type targetType,
+ object parameter, CultureInfo culture)
+ {
+ var boolValue = (bool)value;
+ var direction = (Parameters)Enum.Parse(typeof(Parameters), (string)parameter);
+
+ if (direction == Parameters.Inverted)
+ return !boolValue ? Visibility.Visible : Visibility.Collapsed;
+
+ return boolValue ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ public object ConvertBack(object value, Type targetType,
+ object parameter, CultureInfo culture)
+ {
+ return null;
+ }
+ }
+}
diff --git a/WorkBench/tool/LogTextWriter.cs b/WorkBench/tool/LogTextWriter.cs
index 0608e31..13d0575 100644
--- a/WorkBench/tool/LogTextWriter.cs
+++ b/WorkBench/tool/LogTextWriter.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System.Collections.Concurrent;
+using System.IO;
using System.Text;
namespace Serein.WorkBench.tool
@@ -6,10 +7,24 @@ namespace Serein.WorkBench.tool
///
/// 可以捕获类库输出的打印输出
///
- public class LogTextWriter(Action logAction) : TextWriter
+ public class LogTextWriter : TextWriter
{
- private readonly Action logAction = logAction;
+ private readonly Action logAction;
private readonly StringWriter stringWriter = new();
+ private readonly BlockingCollection logQueue = new();
+ private readonly Task logTask;
+
+ // 用于计数的字段
+ private int writeCount = 0;
+ private const int maxWrites = 500;
+ private readonly Action clearTextBoxAction;
+
+ public LogTextWriter(Action logAction, Action clearTextBoxAction)
+ {
+ this.logAction = logAction;
+ this.clearTextBoxAction = clearTextBoxAction;
+ logTask = Task.Run(ProcessLogQueue); // 异步处理日志
+ }
public override Encoding Encoding => Encoding.UTF8;
@@ -18,28 +33,60 @@ namespace Serein.WorkBench.tool
stringWriter.Write(value);
if (value == '\n')
{
- logAction(stringWriter.ToString());
- stringWriter.GetStringBuilder().Clear();
+ EnqueueLog();
}
}
public override void Write(string? value)
{
- if(string.IsNullOrWhiteSpace(value)) { return; }
+ if (string.IsNullOrWhiteSpace(value)) return;
stringWriter.Write(value);
if (value.Contains('\n'))
{
- logAction(stringWriter.ToString());
- stringWriter.GetStringBuilder().Clear();
+ EnqueueLog();
}
}
public override void WriteLine(string? value)
{
- if (string.IsNullOrWhiteSpace(value)) { return; }
+ if (string.IsNullOrWhiteSpace(value)) return;
stringWriter.WriteLine(value);
- logAction(stringWriter.ToString());
+ EnqueueLog();
+ }
+
+ private void EnqueueLog()
+ {
+ logQueue.Add(stringWriter.ToString());
stringWriter.GetStringBuilder().Clear();
}
+
+ private async Task ProcessLogQueue()
+ {
+ foreach (var log in logQueue.GetConsumingEnumerable())
+ {
+ // 异步执行日志输出操作
+ await Task.Run(() =>
+ {
+ logAction(log);
+
+ // 计数器增加
+ writeCount++;
+ if (writeCount >= maxWrites)
+ {
+ // 计数器达到50,清空文本框
+ clearTextBoxAction?.Invoke();
+ writeCount = 0; // 重置计数器
+ }
+ });
+ }
+ }
+
+ public new void Dispose()
+ {
+ logQueue.CompleteAdding();
+ logTask.Wait();
+ base.Dispose();
+ }
}
+
}