mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-04-24 00:46:35 +08:00
使用异步重构了节点执行方法,将触发器节点与其他节点统一。使用Channel代替Tcs更改了信号触发,使其符合异步编程的习惯。增加了节点是否启用勾选框、参数遮罩勾选框,节点右键面板增加中断功能(试验)。增加了选择后被选择的节点的视觉效果。更改平移缩放逻辑,使其更加符合一般的使用习惯。
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using Serein.Library.Api;
|
using Serein.Library.Api;
|
||||||
using Serein.Library.Enums;
|
using Serein.Library.Enums;
|
||||||
|
using Serein.Library.NodeFlow.Tool;
|
||||||
|
|
||||||
namespace Serein.Library.Core.NodeFlow
|
namespace Serein.Library.Core.NodeFlow
|
||||||
{
|
{
|
||||||
@@ -67,16 +68,16 @@ namespace Serein.Library.Core.NodeFlow
|
|||||||
{
|
{
|
||||||
public FlipflopStateType State { get; set; }
|
public FlipflopStateType State { get; set; }
|
||||||
|
|
||||||
public object Data { get; set; }
|
public TriggerData TriggerData { get; set; }
|
||||||
|
|
||||||
public FlipflopContext(FlipflopStateType ffState)
|
public FlipflopContext(FlipflopStateType ffState)
|
||||||
{
|
{
|
||||||
State = ffState;
|
State = ffState;
|
||||||
}
|
}
|
||||||
public FlipflopContext(FlipflopStateType ffState, object data)
|
public FlipflopContext(FlipflopStateType ffState, TriggerData data)
|
||||||
{
|
{
|
||||||
State = ffState;
|
State = ffState;
|
||||||
Data = data;
|
TriggerData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Serein.Library.Api;
|
using Serein.Library.Api;
|
||||||
using Serein.Library.Enums;
|
using Serein.Library.Enums;
|
||||||
|
using Serein.Library.NodeFlow.Tool;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ namespace Serein.Library.Framework.NodeFlow
|
|||||||
{
|
{
|
||||||
public FlipflopStateType State { get; set; }
|
public FlipflopStateType State { get; set; }
|
||||||
//public TResult? Data { get; set; }
|
//public TResult? Data { get; set; }
|
||||||
public object Data { get; set; }
|
public TriggerData TriggerData { get; set; }
|
||||||
public FlipflopContext(FlipflopStateType ffState)
|
public FlipflopContext(FlipflopStateType ffState)
|
||||||
{
|
{
|
||||||
State = ffState;
|
State = ffState;
|
||||||
@@ -67,7 +68,11 @@ namespace Serein.Library.Framework.NodeFlow
|
|||||||
public FlipflopContext(FlipflopStateType ffState, object data)
|
public FlipflopContext(FlipflopStateType ffState, object data)
|
||||||
{
|
{
|
||||||
State = ffState;
|
State = ffState;
|
||||||
Data = data;
|
TriggerData = new TriggerData
|
||||||
|
{
|
||||||
|
Type = TriggerType.External,
|
||||||
|
Value = data
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
using Serein.Library.Enums;
|
using Serein.Library.Enums;
|
||||||
|
using Serein.Library.NodeFlow.Tool;
|
||||||
|
|
||||||
namespace Serein.Library.Api
|
namespace Serein.Library.Api
|
||||||
{
|
{
|
||||||
public interface IFlipflopContext
|
public interface IFlipflopContext
|
||||||
{
|
{
|
||||||
FlipflopStateType State { get; set; }
|
FlipflopStateType State { get; set; }
|
||||||
object Data { get; set; }
|
TriggerData TriggerData { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Serein.Library.Entity;
|
using Serein.Library.Entity;
|
||||||
using Serein.Library.Enums;
|
using Serein.Library.Enums;
|
||||||
|
using Serein.Library.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@@ -212,6 +213,8 @@ namespace Serein.Library.Api
|
|||||||
|
|
||||||
public interface IFlowEnvironment
|
public interface IFlowEnvironment
|
||||||
{
|
{
|
||||||
|
ChannelFlowInterrupt ChannelFlowInterrupt { get; set; }
|
||||||
|
|
||||||
|
|
||||||
event FlowRunCompleteHandler OnFlowRunComplete;
|
event FlowRunCompleteHandler OnFlowRunComplete;
|
||||||
event ProjectLoadedHandler OnProjectLoaded;
|
event ProjectLoadedHandler OnProjectLoaded;
|
||||||
@@ -290,7 +293,6 @@ namespace Serein.Library.Api
|
|||||||
void RemoteNode(string nodeGuid);
|
void RemoteNode(string nodeGuid);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Serein.Library.Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int Index { get; set; }
|
public int Index { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否为显式参数
|
/// 是否为显式参数(固定值/表达式)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsExplicitData { get; set; }
|
public bool IsExplicitData { get; set; }
|
||||||
///// <summary>
|
///// <summary>
|
||||||
@@ -45,21 +45,16 @@ namespace Serein.Library.Entity
|
|||||||
|
|
||||||
public string DataValue { get; set; }
|
public string DataValue { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public string[] Items { get; set; }
|
public string[] Items { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public ExplicitData Clone() => new ExplicitData()
|
public ExplicitData Clone() => new ExplicitData()
|
||||||
{
|
{
|
||||||
Index = Index,
|
Index = Index,
|
||||||
IsExplicitData = IsExplicitData,
|
IsExplicitData = IsExplicitData,
|
||||||
// ExplicitType = ExplicitType,
|
// ExplicitType = ExplicitType,
|
||||||
|
ExplicitTypeName = ExplicitTypeName,
|
||||||
DataType = DataType,
|
DataType = DataType,
|
||||||
ParameterName = ParameterName,
|
ParameterName = ParameterName,
|
||||||
ExplicitTypeName = ExplicitTypeName,
|
|
||||||
DataValue = string.IsNullOrEmpty(DataValue) ? string.Empty : DataValue,
|
DataValue = string.IsNullOrEmpty(DataValue) ? string.Empty : DataValue,
|
||||||
Items = Items.Select(it => it).ToArray(),
|
Items = Items.Select(it => it).ToArray(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,10 +28,17 @@ namespace Serein.Library.Entity
|
|||||||
MethodName = MethodName,
|
MethodName = MethodName,
|
||||||
MethodLockName = MethodLockName,
|
MethodLockName = MethodLockName,
|
||||||
IsNetFramework = IsNetFramework,
|
IsNetFramework = IsNetFramework,
|
||||||
|
IsProtectionParameter = IsProtectionParameter,
|
||||||
ExplicitDatas = ExplicitDatas?.Select(it => it.Clone()).ToArray(),
|
ExplicitDatas = ExplicitDatas?.Select(it => it.Clone()).ToArray(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否保护参数
|
||||||
|
/// </summary>
|
||||||
|
public bool IsProtectionParameter { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 作用实例的类型
|
/// 作用实例的类型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
47
Library/Entity/NodeDebugSetting.cs
Normal file
47
Library/Entity/NodeDebugSetting.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Serein.Library.Entity
|
||||||
|
{
|
||||||
|
public class NodeDebugSetting
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 是否使能(调试中断功能)
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEnable { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否中断(调试中断功能)
|
||||||
|
/// </summary>
|
||||||
|
public bool IsInterrupt { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 中断级别,暂时停止继续执行后继分支。
|
||||||
|
/// </summary>
|
||||||
|
public InterruptClass InterruptClass { get; set; } = InterruptClass.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 中断级别,暂时停止继续执行后继分支。
|
||||||
|
/// </summary>
|
||||||
|
public enum InterruptClass
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 不中断
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
/// <summary>
|
||||||
|
/// 分支中断,当前节点。
|
||||||
|
/// </summary>
|
||||||
|
Branch,
|
||||||
|
/// <summary>
|
||||||
|
/// 分组中断,相同中断分组的节点。
|
||||||
|
/// </summary>
|
||||||
|
Group,
|
||||||
|
/// <summary>
|
||||||
|
/// 全局中断,其它所有节点。
|
||||||
|
/// </summary>
|
||||||
|
Global,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
122
Library/Utils/ChannelFlowInterrupt.cs
Normal file
122
Library/Utils/ChannelFlowInterrupt.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 中断取消类型
|
||||||
|
/// </summary>
|
||||||
|
public enum CancelType
|
||||||
|
{
|
||||||
|
Manual,
|
||||||
|
Overtime
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用并发字典管理每个信号对应的 Channel
|
||||||
|
private readonly ConcurrentDictionary<string, Channel<CancelType>> _channels = new ConcurrentDictionary<string, Channel<CancelType>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建信号并指定超时时间,到期后自动触发(异步方法)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="signal">信号标识符</param>
|
||||||
|
/// <param name="outTime">超时时间</param>
|
||||||
|
/// <returns>等待任务</returns>
|
||||||
|
public async Task<CancelType> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建信号并指定超时时间,到期后自动触发(同步阻塞方法)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="signal">信号标识符</param>
|
||||||
|
/// <param name="timeout">超时时间</param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 触发信号
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="signal">信号字符串</param>
|
||||||
|
/// <returns>是否成功触发</returns>
|
||||||
|
public bool TriggerSignal(string signal)
|
||||||
|
{
|
||||||
|
if (_channels.TryGetValue(signal, out var channel))
|
||||||
|
{
|
||||||
|
// 手动触发信号
|
||||||
|
channel.Writer.TryWrite(CancelType.Manual);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 取消所有任务
|
||||||
|
/// </summary>
|
||||||
|
public void CancelAllTasks()
|
||||||
|
{
|
||||||
|
foreach (var channel in _channels.Values)
|
||||||
|
{
|
||||||
|
channel.Writer.Complete();
|
||||||
|
}
|
||||||
|
_channels.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或创建指定信号的 Channel
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="signal">信号字符串</param>
|
||||||
|
/// <returns>对应的 Channel</returns>
|
||||||
|
private Channel<CancelType> GetOrCreateChannel(string signal)
|
||||||
|
{
|
||||||
|
return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded<CancelType>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
115
Library/Utils/ChannelFlowTrigger.cs
Normal file
115
Library/Utils/ChannelFlowTrigger.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 触发类型
|
||||||
|
/// </summary>
|
||||||
|
public enum TriggerType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 外部触发
|
||||||
|
/// </summary>
|
||||||
|
External,
|
||||||
|
/// <summary>
|
||||||
|
/// 超时触发
|
||||||
|
/// </summary>
|
||||||
|
Overtime
|
||||||
|
}
|
||||||
|
public class TriggerData
|
||||||
|
{
|
||||||
|
public TriggerType Type { get; set; }
|
||||||
|
public object Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class ChannelFlowTrigger<TSignal> where TSignal : struct, Enum
|
||||||
|
{
|
||||||
|
// 使用并发字典管理每个枚举信号对应的 Channel
|
||||||
|
private readonly ConcurrentDictionary<TSignal, Channel<TriggerData>> _channels = new ConcurrentDictionary<TSignal, Channel<TriggerData>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建信号并指定超时时间,到期后自动触发(异步方法)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="signal">枚举信号标识符</param>
|
||||||
|
/// <param name="outTime">超时时间</param>
|
||||||
|
/// <returns>等待任务</returns>
|
||||||
|
public async Task<TriggerData> CreateChannelWithTimeoutAsync<TResult>(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 触发信号
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="signal">枚举信号标识符</param>
|
||||||
|
/// <returns>是否成功触发</returns>
|
||||||
|
public bool TriggerSignal<TResult>(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 取消所有任务
|
||||||
|
/// </summary>
|
||||||
|
public void CancelAllTasks()
|
||||||
|
{
|
||||||
|
foreach (var channel in _channels.Values)
|
||||||
|
{
|
||||||
|
channel.Writer.Complete();
|
||||||
|
}
|
||||||
|
_channels.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或创建指定信号的 Channel
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="signal">枚举信号标识符</param>
|
||||||
|
/// <returns>对应的 Channel</returns>
|
||||||
|
private Channel<TriggerData> GetOrCreateChannel(TSignal signal)
|
||||||
|
{
|
||||||
|
return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded<TriggerData>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<TSignal> where TSignal : struct, Enum
|
|
||||||
{
|
|
||||||
public ConcurrentDictionary<TSignal, TaskCompletionSource<object>> TcsEvent { get; } = new ConcurrentDictionary<TSignal, TaskCompletionSource<object>>();
|
|
||||||
|
|
||||||
public ConcurrentDictionary<TSignal, object> TcsLock { get; } = new ConcurrentDictionary<TSignal, object>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 触发信号
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="signal">信号</param>
|
|
||||||
/// <param name="value">传递的参数</param>
|
|
||||||
/// <returns>是否成功触发</returns>
|
|
||||||
public bool TriggerSignal<T>(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<object> CreateTcs(TSignal signal)
|
|
||||||
{
|
|
||||||
var tcsLock = TcsLock.GetOrAdd(signal, new object());
|
|
||||||
lock (tcsLock)
|
|
||||||
{
|
|
||||||
var tcs = TcsEvent.GetOrAdd(signal, new TaskCompletionSource<object>());
|
|
||||||
return tcs;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CancelTask()
|
|
||||||
{
|
|
||||||
foreach (var tcs in TcsEvent.Values)
|
|
||||||
{
|
|
||||||
tcs.SetException(new FlipflopException("任务取消"));
|
|
||||||
}
|
|
||||||
TcsEvent.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,12 +22,14 @@ namespace Serein.NodeFlow.Base
|
|||||||
PreviousNodes[ctType] = [];
|
PreviousNodes[ctType] = [];
|
||||||
SuccessorNodes[ctType] = [];
|
SuccessorNodes[ctType] = [];
|
||||||
}
|
}
|
||||||
|
DebugSetting = new NodeDebugSetting();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否中断(调试中断功能)
|
/// 调试功能
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsInterrupt { get; set; }
|
public NodeDebugSetting DebugSetting { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 节点对应的控件类型
|
/// 节点对应的控件类型
|
||||||
@@ -84,13 +86,7 @@ namespace Serein.NodeFlow.Base
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class DebugInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 是否中断
|
|
||||||
/// </summary>
|
|
||||||
public bool IsInterrupt { get;set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using System.Linq;
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using static Serein.Library.Utils.ChannelFlowInterrupt;
|
||||||
|
|
||||||
namespace Serein.NodeFlow.Base
|
namespace Serein.NodeFlow.Base
|
||||||
{
|
{
|
||||||
@@ -20,6 +21,32 @@ namespace Serein.NodeFlow.Base
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class NodeModelBase : IDynamicFlowNode
|
public abstract partial class NodeModelBase : IDynamicFlowNode
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
#region 调试中断
|
||||||
|
|
||||||
|
public Action? CancelInterruptCallback;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 中断节点
|
||||||
|
/// </summary>
|
||||||
|
public void Interrupt()
|
||||||
|
{
|
||||||
|
this.DebugSetting.InterruptClass = InterruptClass.Branch;
|
||||||
|
this.DebugSetting.IsInterrupt = true;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 不再中断
|
||||||
|
/// </summary>
|
||||||
|
public void CancelInterrupt()
|
||||||
|
{
|
||||||
|
this.DebugSetting.InterruptClass = InterruptClass.None;
|
||||||
|
this.DebugSetting.IsInterrupt = false;
|
||||||
|
CancelInterruptCallback?.Invoke();
|
||||||
|
CancelInterruptCallback = null;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region 导出/导入项目文件节点信息
|
#region 导出/导入项目文件节点信息
|
||||||
|
|
||||||
internal abstract Parameterdata[] GetParameterdatas();
|
internal abstract Parameterdata[] GetParameterdatas();
|
||||||
@@ -84,19 +111,19 @@ namespace Serein.NodeFlow.Base
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 节点方法的执行
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开始执行
|
/// 开始执行
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task StartExecution(IDynamicContext context)
|
public async Task StartExecute(IDynamicContext context)
|
||||||
{
|
{
|
||||||
var cts = context.SereinIoc.GetOrRegisterInstantiate<CancellationTokenSource>();
|
var cts = context.SereinIoc.GetOrRegisterInstantiate<CancellationTokenSource>();
|
||||||
|
|
||||||
Stack<NodeModelBase> stack = new Stack<NodeModelBase>();
|
Stack<NodeModelBase> stack = new Stack<NodeModelBase>();
|
||||||
stack.Push(this);
|
stack.Push(this);
|
||||||
|
|
||||||
while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环
|
while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环
|
||||||
{
|
{
|
||||||
// 从栈中弹出一个节点作为当前节点进行处理
|
// 从栈中弹出一个节点作为当前节点进行处理
|
||||||
@@ -108,51 +135,112 @@ namespace Serein.NodeFlow.Base
|
|||||||
currentNode.MethodDetails.ActingInstance ??= context.SereinIoc.GetOrRegisterInstantiate(currentNode.MethodDetails.ActingInstanceType);
|
currentNode.MethodDetails.ActingInstance ??= context.SereinIoc.GetOrRegisterInstantiate(currentNode.MethodDetails.ActingInstanceType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//if (TryCreateInterruptTask(context, currentNode, out Task<CancelType>? task))
|
||||||
|
//{
|
||||||
|
// var cancelType = await task!;
|
||||||
|
// await Console.Out.WriteLineAsync($"[{currentNode.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支");
|
||||||
|
//}
|
||||||
|
|
||||||
|
#region 执行相关
|
||||||
// 首先执行上游分支
|
// 首先执行上游分支
|
||||||
var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream];
|
var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream];
|
||||||
for (int i = upstreamNodes.Count - 1; i >= 0; i--)
|
for (int i = upstreamNodes.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
upstreamNodes[i].PreviousNode = currentNode;
|
if (upstreamNodes[i].DebugSetting.IsEnable) // 排除未启用的上游节点
|
||||||
await upstreamNodes[i].StartExecution(context); // 执行上游分支
|
{
|
||||||
|
upstreamNodes[i].PreviousNode = currentNode;
|
||||||
|
await upstreamNodes[i].StartExecute(context); // 执行流程节点的上游分支
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentNode.FlowData = currentNode.ExecutingAsync(context); // 流程中正常执行
|
||||||
|
|
||||||
// 判断是否为触发器节点,如果是,则开始等待。
|
// 判断是否为触发器节点,如果是,则开始等待。
|
||||||
if (currentNode.MethodDetails != null && currentNode.MethodDetails.MethodDynamicType == NodeType.Flipflop)
|
//if (currentNode.MethodDetails != null && currentNode.MethodDetails.MethodDynamicType == NodeType.Flipflop)
|
||||||
{
|
//{
|
||||||
|
|
||||||
currentNode.FlowData = await currentNode.ExecuteAsync(context); // 流程中遇到了触发器
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentNode.FlowData = currentNode.Execute(context); // 流程中正常执行
|
|
||||||
}
|
|
||||||
|
|
||||||
if(currentNode.NextOrientation == ConnectionType.None)
|
// currentNode.FlowData = await currentNode.ExecutingFlipflopAsync(context); // 流程中遇到了触发器
|
||||||
{
|
//}
|
||||||
// 不再执行
|
//else
|
||||||
break;
|
//{
|
||||||
}
|
|
||||||
|
//}
|
||||||
|
#endregion
|
||||||
|
|
||||||
// 获取下一分支
|
|
||||||
|
|
||||||
|
|
||||||
|
#region 执行完成
|
||||||
|
if (currentNode.NextOrientation == ConnectionType.None) break; // 不再执行
|
||||||
|
|
||||||
|
|
||||||
|
// 选择后继分支
|
||||||
var nextNodes = currentNode.SuccessorNodes[currentNode.NextOrientation];
|
var nextNodes = currentNode.SuccessorNodes[currentNode.NextOrientation];
|
||||||
|
|
||||||
// 将下一个节点集合中的所有节点逆序推入栈中
|
// 将下一个节点集合中的所有节点逆序推入栈中
|
||||||
for (int i = nextNodes.Count - 1; i >= 0; i--)
|
for (int i = nextNodes.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
nextNodes[i].PreviousNode = currentNode;
|
// 排除未启用的节点
|
||||||
stack.Push(nextNodes[i]);
|
if (nextNodes[i].DebugSetting.IsEnable)
|
||||||
}
|
{
|
||||||
|
nextNodes[i].PreviousNode = currentNode;
|
||||||
|
stack.Push(nextNodes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TryCreateInterruptTask(IDynamicContext context, NodeModelBase currentNode, out Task<CancelType>? task)
|
||||||
|
{
|
||||||
|
if (!currentNode.DebugSetting.IsInterrupt)
|
||||||
|
{
|
||||||
|
task = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task<CancelType>? result = null;
|
||||||
|
bool haveTask = false;
|
||||||
|
Console.WriteLine($"[{currentNode.MethodDetails.MethodName}]在当前分支中断");
|
||||||
|
if (currentNode.DebugSetting.InterruptClass == InterruptClass.None)
|
||||||
|
{
|
||||||
|
haveTask = false;
|
||||||
|
task = null;
|
||||||
|
currentNode.DebugSetting.IsInterrupt = false; // 纠正设置
|
||||||
|
}
|
||||||
|
if (currentNode.DebugSetting.InterruptClass == InterruptClass.Branch) // 中断当前分支
|
||||||
|
{
|
||||||
|
currentNode.DebugSetting.IsInterrupt = true;
|
||||||
|
haveTask = true;
|
||||||
|
task = context.FlowEnvironment.ChannelFlowInterrupt.CreateChannelWithTimeoutAsync(currentNode.Guid, TimeSpan.FromSeconds(1));
|
||||||
|
currentNode.CancelInterruptCallback ??= () => context.FlowEnvironment.ChannelFlowInterrupt.TriggerSignal(currentNode.Guid);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
haveTask = false;
|
||||||
|
task = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return haveTask;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 执行节点对应的方法
|
/// 执行节点对应的方法
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">流程上下文</param>
|
/// <param name="context">流程上下文</param>
|
||||||
/// <returns>节点传回数据对象</returns>
|
/// <returns>节点传回数据对象</returns>
|
||||||
public virtual object? Execute(IDynamicContext context)
|
public virtual async Task<object?> ExecutingAsync(IDynamicContext context)
|
||||||
{
|
{
|
||||||
|
#region 调试中断
|
||||||
|
if (TryCreateInterruptTask(context, this, out Task<CancelType>? task))
|
||||||
|
{
|
||||||
|
var cancelType = await task!;
|
||||||
|
await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
MethodDetails md = MethodDetails;
|
MethodDetails md = MethodDetails;
|
||||||
var del = md.MethodDelegate;
|
var del = md.MethodDelegate;
|
||||||
object instance = md.ActingInstance;
|
object instance = md.ActingInstance;
|
||||||
@@ -186,38 +274,7 @@ namespace Serein.NodeFlow.Base
|
|||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
/// <returns>节点传回数据对象</returns>
|
/// <returns>节点传回数据对象</returns>
|
||||||
/// <exception cref="RuningException"></exception>
|
/// <exception cref="RuningException"></exception>
|
||||||
public virtual async Task<object?> ExecuteAsync(IDynamicContext context)
|
|
||||||
{
|
|
||||||
MethodDetails md = MethodDetails;
|
|
||||||
Delegate del = md.MethodDelegate;
|
|
||||||
object instance = md.ActingInstance;
|
|
||||||
var haveParameter = md.ExplicitDatas.Length >= 0;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 调用委托并获取结果
|
|
||||||
Task<IFlipflopContext> flipflopTask = haveParameter switch
|
|
||||||
{
|
|
||||||
true => ((Func<object, object?[]?, Task<IFlipflopContext>>)del).Invoke(instance, GetParameters(context, md)), // 执行流程中的触发器方法时获取入参参数
|
|
||||||
false => ((Func<object, Task<IFlipflopContext>>)del).Invoke(instance),
|
|
||||||
};
|
|
||||||
|
|
||||||
IFlipflopContext flipflopContext = (await flipflopTask) ?? throw new FlipflopException("没有返回上下文");
|
|
||||||
NextOrientation = flipflopContext.State.ToContentType();
|
|
||||||
return flipflopContext.Data;
|
|
||||||
}
|
|
||||||
//catch(FlipflopException ex)
|
|
||||||
//{
|
|
||||||
// NextOrientation = ConnectionType.IsError;
|
|
||||||
// RuningException = ex;
|
|
||||||
// return null;
|
|
||||||
//}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
NextOrientation = ConnectionType.IsError;
|
|
||||||
RuningException = ex;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#region 节点转换的委托类型
|
#region 节点转换的委托类型
|
||||||
@@ -248,45 +305,34 @@ namespace Serein.NodeFlow.Base
|
|||||||
public object?[]? GetParameters(IDynamicContext context, MethodDetails md)
|
public object?[]? GetParameters(IDynamicContext context, MethodDetails md)
|
||||||
{
|
{
|
||||||
// 用正确的大小初始化参数数组
|
// 用正确的大小初始化参数数组
|
||||||
var types = md.ExplicitDatas.Select(it => it.DataType).ToArray();
|
if (md.ExplicitDatas.Length == 0)
|
||||||
if (types.Length == 0)
|
|
||||||
{
|
{
|
||||||
return [md.ActingInstance];
|
return [];// md.ActingInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
object?[]? parameters = new object[types.Length];
|
object?[]? parameters = new object[md.ExplicitDatas.Length];
|
||||||
var flowData = PreviousNode?.FlowData; // 当前传递的数据
|
var flowData = PreviousNode?.FlowData; // 当前传递的数据
|
||||||
var previousDataType = flowData?.GetType();
|
var previousDataType = flowData?.GetType();
|
||||||
|
|
||||||
for (int i = 0; i < types.Length; i++)
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
{
|
{
|
||||||
//if (flowData is null)
|
|
||||||
//{
|
object? inputParameter; // 存放解析的临时参数
|
||||||
// parameters[i] = md.ExplicitDatas[i].DataType switch
|
|
||||||
// {
|
|
||||||
// Type t when t == typeof(IDynamicContext) => context, // 上下文
|
|
||||||
// Type t when t == typeof(MethodDetails) => md, // 节点方法描述
|
|
||||||
// Type t when t == typeof(NodeModelBase) => this, // 节点实体类
|
|
||||||
// _ => null,
|
|
||||||
// };
|
|
||||||
// continue; // 上一节点数据为空,提前跳过
|
|
||||||
//}
|
|
||||||
object? inputParameter; //
|
|
||||||
var ed = md.ExplicitDatas[i]; // 方法入参描述
|
var ed = md.ExplicitDatas[i]; // 方法入参描述
|
||||||
|
|
||||||
|
|
||||||
if (ed.IsExplicitData)
|
if (ed.IsExplicitData)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (ed.DataValue.StartsWith("@get", StringComparison.OrdinalIgnoreCase))
|
if (ed.DataValue.StartsWith("@get", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// 执行表达式从上一节点获取对象
|
// 执行表达式从上一节点获取对象
|
||||||
inputParameter = SerinExpressionEvaluator.Evaluate(ed.DataValue, flowData, out _);
|
inputParameter = SerinExpressionEvaluator.Evaluate(ed.DataValue, flowData, out _);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 使用输入的固定值
|
// 使用输入的固定值
|
||||||
inputParameter = ed.DataValue;
|
inputParameter = ed.DataValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -303,16 +349,26 @@ namespace Serein.NodeFlow.Base
|
|||||||
Type t when t == typeof(MethodDetails) => md, // 节点方法描述
|
Type t when t == typeof(MethodDetails) => md, // 节点方法描述
|
||||||
Type t when t == typeof(NodeModelBase) => this, // 节点实体类
|
Type t when t == typeof(NodeModelBase) => this, // 节点实体类
|
||||||
Type t when t == typeof(Guid) => new Guid(inputParameter?.ToString()),
|
Type t when t == typeof(Guid) => new Guid(inputParameter?.ToString()),
|
||||||
Type t when t == typeof(decimal) => decimal.Parse(inputParameter?.ToString()),
|
Type t when t == typeof(DateTime) => DateTime.Parse(inputParameter?.ToString()),
|
||||||
Type t when t == typeof(string) => inputParameter?.ToString(),
|
Type t when t == typeof(string) => inputParameter?.ToString(),
|
||||||
Type t when t == typeof(char) => char.Parse(inputParameter?.ToString()),
|
Type t when t == typeof(char) => char.Parse(inputParameter?.ToString()),
|
||||||
Type t when t == typeof(bool) => bool.Parse(inputParameter?.ToString()),
|
Type t when t == typeof(bool) => inputParameter is null ? false : bool.Parse(inputParameter?.ToString()),
|
||||||
Type t when t == typeof(byte) => byte.Parse(inputParameter?.ToString()),
|
Type t when t == typeof(float) => inputParameter is null ? 0F : float.Parse(inputParameter?.ToString()),
|
||||||
Type t when t == typeof(int) => int.Parse(inputParameter?.ToString()),
|
Type t when t == typeof(decimal) => inputParameter is null ? 0 : decimal.Parse(inputParameter?.ToString()),
|
||||||
Type t when t == typeof(long) => long.Parse(inputParameter?.ToString()),
|
Type t when t == typeof(double) => inputParameter is null ? 0 : double.Parse(inputParameter?.ToString()),
|
||||||
Type t when t == typeof(DateTime) => DateTime.Parse(inputParameter?.ToString()),
|
Type t when t == typeof(sbyte) => inputParameter is null ? 0 : sbyte.Parse(inputParameter?.ToString()),
|
||||||
Type t when t == typeof(float) => float.Parse(inputParameter?.ToString()),
|
Type t when t == typeof(byte) => inputParameter is null ? 0 : byte.Parse(inputParameter?.ToString()),
|
||||||
Type t when t == typeof(double) => double.Parse(inputParameter?.ToString()),
|
Type t when t == typeof(short) => inputParameter is null ? 0 : short.Parse(inputParameter?.ToString()),
|
||||||
|
Type t when t == typeof(ushort) => inputParameter is null ? 0U : ushort.Parse(inputParameter?.ToString()),
|
||||||
|
Type t when t == typeof(int) => inputParameter is null ? 0 : int.Parse(inputParameter?.ToString()),
|
||||||
|
Type t when t == typeof(uint) => inputParameter is null ? 0U : uint.Parse(inputParameter?.ToString()),
|
||||||
|
Type t when t == typeof(long) => inputParameter is null ? 0L : long.Parse(inputParameter?.ToString()),
|
||||||
|
Type t when t == typeof(ulong) => inputParameter is null ? 0UL : ulong.Parse(inputParameter?.ToString()),
|
||||||
|
Type t when t == typeof(nint) => inputParameter is null ? 0 : nint.Parse(inputParameter?.ToString()),
|
||||||
|
Type t when t == typeof(nuint) => inputParameter is null ? 0 : nuint.Parse(inputParameter?.ToString()),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Type t when t.IsEnum => Enum.Parse(ed.DataType, ed.DataValue),// 需要枚举
|
Type t when t.IsEnum => Enum.Parse(ed.DataType, ed.DataValue),// 需要枚举
|
||||||
Type t when t.IsArray => (inputParameter as Array)?.Cast<object>().ToList(),
|
Type t when t.IsArray => (inputParameter as Array)?.Cast<object>().ToList(),
|
||||||
Type t when t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>) => inputParameter,
|
Type t when t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>) => inputParameter,
|
||||||
@@ -329,6 +385,10 @@ namespace Serein.NodeFlow.Base
|
|||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// json文本反序列化为对象
|
/// json文本反序列化为对象
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -7,14 +7,8 @@ using Serein.Library.Utils;
|
|||||||
using Serein.NodeFlow.Base;
|
using Serein.NodeFlow.Base;
|
||||||
using Serein.NodeFlow.Model;
|
using Serein.NodeFlow.Model;
|
||||||
using Serein.NodeFlow.Tool;
|
using Serein.NodeFlow.Tool;
|
||||||
using System.Diagnostics;
|
using System.Collections.Concurrent;
|
||||||
using System.Net.Mime;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Reflection.Emit;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
using static Serein.NodeFlow.FlowStarter;
|
using static Serein.NodeFlow.FlowStarter;
|
||||||
|
|
||||||
namespace Serein.NodeFlow
|
namespace Serein.NodeFlow
|
||||||
@@ -37,9 +31,8 @@ namespace Serein.NodeFlow
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 运行环境
|
|
||||||
/// </summary>
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -47,74 +40,99 @@ namespace Serein.NodeFlow
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class FlowEnvironment : IFlowEnvironment
|
public class FlowEnvironment : IFlowEnvironment
|
||||||
{
|
{
|
||||||
|
public FlowEnvironment()
|
||||||
|
{
|
||||||
|
ChannelFlowInterrupt = new ChannelFlowInterrupt();
|
||||||
|
LoadedAssemblyPaths = new List<string>();
|
||||||
|
LoadedAssemblies = new List<Assembly>();
|
||||||
|
MethodDetailss = new List<MethodDetails>();
|
||||||
|
Nodes = new Dictionary<string, NodeModelBase>();
|
||||||
|
FlipflopNodes = new List<SingleFlipflopNode>();
|
||||||
|
IsGlobalInterrupt = false;
|
||||||
|
flowStarter = null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 节点的命名空间
|
/// 节点的命名空间
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}";
|
public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}";
|
||||||
|
|
||||||
|
#region 环境接口事件
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 加载Dll
|
/// 加载Dll
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event LoadDLLHandler OnDllLoad;
|
public event LoadDLLHandler OnDllLoad;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 项目加载完成
|
/// 项目加载完成
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event ProjectLoadedHandler OnProjectLoaded;
|
public event ProjectLoadedHandler OnProjectLoaded;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 节点连接属性改变事件
|
/// 节点连接属性改变事件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event NodeConnectChangeHandler OnNodeConnectChange;
|
public event NodeConnectChangeHandler OnNodeConnectChange;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 节点创建事件
|
/// 节点创建事件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event NodeCreateHandler OnNodeCreate;
|
public event NodeCreateHandler OnNodeCreate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 移除节点事件
|
/// 移除节点事件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event NodeRemoteHandler OnNodeRemote;
|
public event NodeRemoteHandler OnNodeRemote;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 起始节点变化事件
|
/// 起始节点变化事件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event StartNodeChangeHandler OnStartNodeChange;
|
public event StartNodeChangeHandler OnStartNodeChange;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 流程运行完成时间
|
/// 流程运行完成时间
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event FlowRunCompleteHandler OnFlowRunComplete;
|
public event FlowRunCompleteHandler OnFlowRunComplete;
|
||||||
|
|
||||||
private FlowStarter? flowStarter = null;
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 一种轻量的IOC容器
|
/// 流程中断器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// public SereinIoc SereinIoc { get; } = new SereinIoc();
|
public ChannelFlowInterrupt ChannelFlowInterrupt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否全局中断
|
||||||
|
/// </summary>
|
||||||
|
public bool IsGlobalInterrupt { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 存储加载的程序集路径
|
/// 存储加载的程序集路径
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> LoadedAssemblyPaths { get; } = [];
|
public List<string> LoadedAssemblyPaths { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 存储加载的程序集
|
/// 存储加载的程序集
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Assembly> LoadedAssemblies { get; } = [];
|
public List<Assembly> LoadedAssemblies { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 存储所有方法信息
|
/// 存储所有方法信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<MethodDetails> MethodDetailss { get; } = [];
|
public List<MethodDetails> MethodDetailss { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
public Dictionary<string, NodeModelBase> Nodes { get; } = [];
|
/// 环境加载的节点集合
|
||||||
|
/// </summary>
|
||||||
public List<NodeModelBase> Regions { get; } = [];
|
public Dictionary<string, NodeModelBase> Nodes { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 存放触发器节点(运行时全部调用)
|
/// 存放触发器节点(运行时全部调用)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<SingleFlipflopNode> FlipflopNodes { get; } = [];
|
public List<SingleFlipflopNode> FlipflopNodes { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 私有属性
|
/// 起始节点私有属性
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private NodeModelBase _startNode;
|
private NodeModelBase _startNode;
|
||||||
|
|
||||||
@@ -138,12 +156,19 @@ namespace Serein.NodeFlow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 流程启动器(每次运行时都会重新new一个)
|
||||||
|
/// </summary>
|
||||||
|
private FlowStarter? flowStarter;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步运行
|
/// 异步运行
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task StartAsync()
|
public async Task StartAsync()
|
||||||
{
|
{
|
||||||
|
ChannelFlowInterrupt?.CancelAllTasks();
|
||||||
flowStarter = new FlowStarter();
|
flowStarter = new FlowStarter();
|
||||||
List<SingleFlipflopNode> flipflopNodes = Nodes.Values.Where(it => it.MethodDetails?.MethodDynamicType == NodeType.Flipflop && it.IsStart == false)
|
List<SingleFlipflopNode> flipflopNodes = Nodes.Values.Where(it => it.MethodDetails?.MethodDynamicType == NodeType.Flipflop && it.IsStart == false)
|
||||||
.Select(it => (SingleFlipflopNode)it)
|
.Select(it => (SingleFlipflopNode)it)
|
||||||
@@ -171,6 +196,7 @@ namespace Serein.NodeFlow
|
|||||||
}
|
}
|
||||||
public void Exit()
|
public void Exit()
|
||||||
{
|
{
|
||||||
|
ChannelFlowInterrupt?.CancelAllTasks();
|
||||||
flowStarter?.Exit();
|
flowStarter?.Exit();
|
||||||
OnFlowRunComplete?.Invoke(new FlowEventArgs());
|
OnFlowRunComplete?.Invoke(new FlowEventArgs());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Serein.Library.Web;
|
|||||||
using Serein.NodeFlow.Base;
|
using Serein.NodeFlow.Base;
|
||||||
using Serein.NodeFlow.Model;
|
using Serein.NodeFlow.Model;
|
||||||
using System.ComponentModel.Design;
|
using System.ComponentModel.Design;
|
||||||
|
using static Serein.Library.Utils.ChannelFlowInterrupt;
|
||||||
|
|
||||||
namespace Serein.NodeFlow
|
namespace Serein.NodeFlow
|
||||||
{
|
{
|
||||||
@@ -22,6 +23,7 @@ namespace Serein.NodeFlow
|
|||||||
{
|
{
|
||||||
SereinIOC = new SereinIOC();
|
SereinIOC = new SereinIOC();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 流程运行状态
|
/// 流程运行状态
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -60,13 +62,12 @@ namespace Serein.NodeFlow
|
|||||||
/// 结束运行时需要执行的方法
|
/// 结束运行时需要执行的方法
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Action ExitAction { get; set; } = null;
|
private Action ExitAction { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 运行的上下文
|
/// 运行的上下文
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IDynamicContext Context { get; set; } = null;
|
private IDynamicContext Context { get; set; } = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开始运行
|
/// 开始运行
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -106,7 +107,7 @@ namespace Serein.NodeFlow
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 初始化运行环境的Ioc容器
|
#region 初始化运行环境的Ioc容器
|
||||||
// 清除节点使用的对象
|
// 清除节点使用的对象,筛选出需要初始化的方法描述
|
||||||
var thisRuningMds = new List<MethodDetails>();
|
var thisRuningMds = new List<MethodDetails>();
|
||||||
thisRuningMds.AddRange(runNodeMd.Where(md => md is not null));
|
thisRuningMds.AddRange(runNodeMd.Where(md => md is not null));
|
||||||
thisRuningMds.AddRange(initMethods.Where(md => md is not null));
|
thisRuningMds.AddRange(initMethods.Where(md => md is not null));
|
||||||
@@ -213,11 +214,11 @@ namespace Serein.NodeFlow
|
|||||||
// 使用 TaskCompletionSource 创建未启动的触发器任务
|
// 使用 TaskCompletionSource 创建未启动的触发器任务
|
||||||
var tasks = flipflopNodes.Select(async node =>
|
var tasks = flipflopNodes.Select(async node =>
|
||||||
{
|
{
|
||||||
await FlipflopExecute(node, env);
|
await FlipflopExecute(env,node);
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
_ = Task.WhenAll(tasks);
|
_ = Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
await startNode.StartExecution(Context); // 从起始节点开始运行
|
await startNode.StartExecute(Context); // 开始运行时从起始节点开始运行
|
||||||
// 等待结束
|
// 等待结束
|
||||||
if (FlipFlopCts != null)
|
if (FlipFlopCts != null)
|
||||||
{
|
{
|
||||||
@@ -240,15 +241,17 @@ namespace Serein.NodeFlow
|
|||||||
{
|
{
|
||||||
// 设置对象
|
// 设置对象
|
||||||
singleFlipFlopNode.MethodDetails.ActingInstance = SereinIOC.GetOrRegisterInstantiate(singleFlipFlopNode.MethodDetails.ActingInstanceType);
|
singleFlipFlopNode.MethodDetails.ActingInstance = SereinIOC.GetOrRegisterInstantiate(singleFlipFlopNode.MethodDetails.ActingInstanceType);
|
||||||
await FlipflopExecute(singleFlipFlopNode, flowEnvironment); // 启动触发器
|
await FlipflopExecute(flowEnvironment,singleFlipFlopNode); // 启动触发器
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 启动全局触发器
|
/// 启动全局触发器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task FlipflopExecute(SingleFlipflopNode singleFlipFlopNode, IFlowEnvironment flowEnvironment)
|
/// <param name="flowEnvironment">流程运行全局环境</param>
|
||||||
|
/// <param name="singleFlipFlopNode">需要全局监听信号的触发器</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task FlipflopExecute(IFlowEnvironment flowEnvironment,SingleFlipflopNode singleFlipFlopNode)
|
||||||
{
|
{
|
||||||
var context = new DynamicContext(SereinIOC, flowEnvironment);
|
var context = new DynamicContext(SereinIOC, flowEnvironment);
|
||||||
MethodDetails md = singleFlipFlopNode.MethodDetails;
|
MethodDetails md = singleFlipFlopNode.MethodDetails;
|
||||||
@@ -259,6 +262,7 @@ namespace Serein.NodeFlow
|
|||||||
{
|
{
|
||||||
md.ActingInstance ??= context.SereinIoc.GetOrRegisterInstantiate(md.ActingInstanceType);
|
md.ActingInstance ??= context.SereinIoc.GetOrRegisterInstantiate(md.ActingInstanceType);
|
||||||
}
|
}
|
||||||
|
object?[]? parameters = singleFlipFlopNode.GetParameters(context, singleFlipFlopNode.MethodDetails); // 启动全局触发器时获取入参参数
|
||||||
// 设置委托对象
|
// 设置委托对象
|
||||||
var func = md.ExplicitDatas.Length == 0 ?
|
var func = md.ExplicitDatas.Length == 0 ?
|
||||||
(Func<object, object, Task<IFlipflopContext>>)del :
|
(Func<object, object, Task<IFlipflopContext>>)del :
|
||||||
@@ -267,49 +271,13 @@ namespace Serein.NodeFlow
|
|||||||
{
|
{
|
||||||
while (!FlipFlopCts.IsCancellationRequested)
|
while (!FlipFlopCts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
object?[]? parameters = singleFlipFlopNode.GetParameters(context, singleFlipFlopNode.MethodDetails); // 启动全局触发器时获取入参参数
|
IFlipflopContext flipflopContext = await func.Invoke(md.ActingInstance, parameters);// 开始等待全局触发器的触发
|
||||||
IFlipflopContext flipflopContext = await func.Invoke(md.ActingInstance, parameters);// 首先开始等待触发器
|
var connectionType = flipflopContext.State.ToContentType();
|
||||||
_ = GlobalFlipflopExecute(singleFlipFlopNode, context);
|
if (connectionType != ConnectionType.None)
|
||||||
|
{
|
||||||
|
await GlobalFlipflopExecute(context, singleFlipFlopNode, connectionType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//while (!FlipFlopCts.IsCancellationRequested)
|
|
||||||
//{
|
|
||||||
// if (singleFlipFlopNode.NotExitPreviousNode() == false)
|
|
||||||
// {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// object?[]? parameters = singleFlipFlopNode.GetParameters(context, md);
|
|
||||||
// if (md.ActingInstance == null)
|
|
||||||
// {
|
|
||||||
// md.ActingInstance = context.SereinIoc.GetOrRegisterInstantiate(md.ActingInstanceType);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// IFlipflopContext flipflopContext = await func.Invoke(md.ActingInstance, parameters);
|
|
||||||
// ConnectionType connection = flipflopContext.State.ToContentType();
|
|
||||||
|
|
||||||
// if (connection != ConnectionType.None)
|
|
||||||
// {
|
|
||||||
// singleFlipFlopNode.NextOrientation = connection;
|
|
||||||
// singleFlipFlopNode.FlowData = flipflopContext.Data;
|
|
||||||
|
|
||||||
// var tasks = singleFlipFlopNode.SuccessorNodes.Values
|
|
||||||
// .SelectMany(nodeList => nodeList)
|
|
||||||
// .Select(nextNode =>
|
|
||||||
// {
|
|
||||||
// var nextContext = new DynamicContext(SereinIOC, flowEnvironment);
|
|
||||||
// nextNode.PreviousNode = singleFlipFlopNode;
|
|
||||||
// return nextNode.StartExecution(nextContext); // 全局触发器收到信号,开始执行
|
|
||||||
// }).ToArray();
|
|
||||||
|
|
||||||
// await Task.WhenAll(tasks);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -317,18 +285,25 @@ namespace Serein.NodeFlow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task GlobalFlipflopExecute(SingleFlipflopNode singleFlipFlopNode, IDynamicContext context)
|
/// <summary>
|
||||||
|
/// 全局触发器开始执行相关分支
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">上下文</param>
|
||||||
|
/// <param name="singleFlipFlopNode">被触发的全局触发器</param>
|
||||||
|
/// <param name="connectionType">分支类型</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task GlobalFlipflopExecute(IDynamicContext context, SingleFlipflopNode singleFlipFlopNode, ConnectionType connectionType)
|
||||||
{
|
{
|
||||||
if (FlipFlopCts.IsCancellationRequested)
|
if (FlipFlopCts.IsCancellationRequested )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skip = true;
|
bool skip = true;
|
||||||
var cts = context.SereinIoc.GetOrRegisterInstantiate<CancellationTokenSource>();
|
var cts = context.SereinIoc.GetOrRegisterInstantiate<CancellationTokenSource>();
|
||||||
Stack<NodeModelBase> stack = new Stack<NodeModelBase>();
|
Stack<NodeModelBase> stack = new Stack<NodeModelBase>();
|
||||||
stack.Push(singleFlipFlopNode);
|
stack.Push(singleFlipFlopNode);
|
||||||
|
|
||||||
ConnectionType connectionType = ConnectionType.IsSucceed;
|
|
||||||
|
|
||||||
while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环
|
while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环
|
||||||
{
|
{
|
||||||
@@ -346,7 +321,7 @@ namespace Serein.NodeFlow
|
|||||||
for (int i = upstreamNodes.Count - 1; i >= 0; i--)
|
for (int i = upstreamNodes.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
upstreamNodes[i].PreviousNode = currentNode;
|
upstreamNodes[i].PreviousNode = currentNode;
|
||||||
await upstreamNodes[i].StartExecution(context); // 执行上游分支
|
await upstreamNodes[i].StartExecute(context); // 执行全局触发器的上游分支
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当前节点是已经触发了的全局触发器,所以跳过,难道每次都要判断一次?
|
// 当前节点是已经触发了的全局触发器,所以跳过,难道每次都要判断一次?
|
||||||
@@ -356,15 +331,8 @@ namespace Serein.NodeFlow
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 判断是否为触发器节点,如果是,则开始等待。
|
currentNode.FlowData = await currentNode.ExecutingAsync(context);
|
||||||
if (currentNode.MethodDetails != null && currentNode.MethodDetails.MethodDynamicType == NodeType.Flipflop)
|
|
||||||
{
|
|
||||||
currentNode.FlowData = await currentNode.ExecuteAsync(context); // 流程中遇到了触发器
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentNode.FlowData = currentNode.Execute(context); // 流程中正常执行
|
|
||||||
}
|
|
||||||
if (currentNode.NextOrientation == ConnectionType.None)
|
if (currentNode.NextOrientation == ConnectionType.None)
|
||||||
{
|
{
|
||||||
break; // 不再执行
|
break; // 不再执行
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ namespace Serein.NodeFlow.Model
|
|||||||
ActionNodes = actionNodes;
|
ActionNodes = actionNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override object? Execute(IDynamicContext context)
|
//public override async Task<object?> Executing(IDynamicContext context)
|
||||||
|
public override Task<object?> ExecutingAsync(IDynamicContext context)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("动作区域暂未实现");
|
throw new NotImplementedException("动作区域暂未实现");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ namespace Serein.NodeFlow.Model
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public override object? Execute(IDynamicContext context)
|
//public override object? Executing(IDynamicContext context)
|
||||||
|
public override Task<object?> ExecutingAsync(IDynamicContext context)
|
||||||
{
|
{
|
||||||
// 条件区域中遍历每个条件节点
|
// 条件区域中遍历每个条件节点
|
||||||
foreach (SingleConditionNode? node in ConditionNodes)
|
foreach (SingleConditionNode? node in ConditionNodes)
|
||||||
@@ -37,7 +38,7 @@ namespace Serein.NodeFlow.Model
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return PreviousNode?.FlowData;
|
return Task.FromResult( PreviousNode?.FlowData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -45,7 +46,7 @@ namespace Serein.NodeFlow.Model
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
node.Execute(context);
|
node.ExecutingAsync(context);
|
||||||
return node.NextOrientation;
|
return node.NextOrientation;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ namespace Serein.NodeFlow.Model
|
|||||||
public string Expression { get; set; }
|
public string Expression { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public override object? Execute(IDynamicContext context)
|
//public override object? Executing(IDynamicContext context)
|
||||||
|
public override Task<object?> ExecutingAsync(IDynamicContext context)
|
||||||
{
|
{
|
||||||
// 接收上一节点参数or自定义参数内容
|
// 接收上一节点参数or自定义参数内容
|
||||||
object? result;
|
object? result;
|
||||||
@@ -52,7 +53,7 @@ namespace Serein.NodeFlow.Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine($"{result} {Expression} -> " + NextOrientation);
|
Console.WriteLine($"{result} {Expression} -> " + NextOrientation);
|
||||||
return result;
|
return Task.FromResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override Parameterdata[] GetParameterdatas()
|
internal override Parameterdata[] GetParameterdatas()
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ namespace Serein.NodeFlow.Model
|
|||||||
public string Expression { get; set; }
|
public string Expression { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public override object? Execute(IDynamicContext context)
|
//public override async Task<object?> Executing(IDynamicContext context)
|
||||||
|
public override Task<object?> ExecutingAsync(IDynamicContext context)
|
||||||
{
|
{
|
||||||
var data = PreviousNode?.FlowData;
|
var data = PreviousNode?.FlowData;
|
||||||
|
|
||||||
@@ -37,13 +38,13 @@ namespace Serein.NodeFlow.Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
NextOrientation = ConnectionType.IsSucceed;
|
NextOrientation = ConnectionType.IsSucceed;
|
||||||
return result;
|
return Task.FromResult(result);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
NextOrientation = ConnectionType.IsError;
|
NextOrientation = ConnectionType.IsError;
|
||||||
RuningException = ex;
|
RuningException = ex;
|
||||||
return PreviousNode?.FlowData;
|
return Task.FromResult(PreviousNode?.FlowData);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,67 @@
|
|||||||
using Serein.Library.Api;
|
using Serein.Library.Api;
|
||||||
using Serein.Library.Entity;
|
using Serein.Library.Entity;
|
||||||
|
using Serein.Library.Enums;
|
||||||
using Serein.Library.Ex;
|
using Serein.Library.Ex;
|
||||||
using Serein.NodeFlow.Base;
|
using Serein.NodeFlow.Base;
|
||||||
|
using static Serein.Library.Utils.ChannelFlowInterrupt;
|
||||||
|
|
||||||
namespace Serein.NodeFlow.Model
|
namespace Serein.NodeFlow.Model
|
||||||
{
|
{
|
||||||
|
|
||||||
public class SingleFlipflopNode : NodeModelBase
|
public class SingleFlipflopNode : NodeModelBase
|
||||||
{
|
{
|
||||||
public override object? Execute(IDynamicContext context)
|
//public override async Task<object?> Executing(IDynamicContext context)
|
||||||
|
//public override Task<object?> ExecutingAsync(IDynamicContext context)
|
||||||
|
//{
|
||||||
|
// NextOrientation = Library.Enums.ConnectionType.IsError;
|
||||||
|
// RuningException = new FlipflopException ("无法以非await/async的形式调用触发器");
|
||||||
|
// return null;
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
public override async Task<object?> ExecutingAsync(IDynamicContext context)
|
||||||
{
|
{
|
||||||
NextOrientation = Library.Enums.ConnectionType.IsError;
|
#region 执行前中断
|
||||||
RuningException = new FlipflopException ("无法以非await/async的形式调用触发器");
|
if (TryCreateInterruptTask(context, this, out Task<CancelType>? task))
|
||||||
return null;
|
{
|
||||||
|
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<IFlipflopContext> flipflopTask = haveParameter switch
|
||||||
|
{
|
||||||
|
true => ((Func<object, object?[]?, Task<IFlipflopContext>>)del).Invoke(instance, GetParameters(context, md)), // 执行流程中的触发器方法时获取入参参数
|
||||||
|
false => ((Func<object, Task<IFlipflopContext>>)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()
|
internal override Parameterdata[] GetParameterdatas()
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace Serein.WorkBench
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AppendText(string text)
|
public void AppendText(string text)
|
||||||
{
|
{
|
||||||
Dispatcher.BeginInvoke(() =>
|
Dispatcher.BeginInvoke(() =>
|
||||||
@@ -19,6 +20,13 @@ namespace Serein.WorkBench
|
|||||||
LogTextBox.ScrollToEnd();
|
LogTextBox.ScrollToEnd();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Dispatcher.BeginInvoke(() =>
|
||||||
|
{
|
||||||
|
LogTextBox.Clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
private void ClearLog_Click(object sender, RoutedEventArgs e)
|
private void ClearLog_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
LogTextBox.Clear();
|
LogTextBox.Clear();
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver"
|
AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver"
|
||||||
Loaded="Window_Loaded"
|
Loaded="Window_Loaded"
|
||||||
ContentRendered="Window_ContentRendered"
|
ContentRendered="Window_ContentRendered"
|
||||||
|
PreviewKeyDown="Window_PreviewKeyDown"
|
||||||
|
PreviewTextInput="Window_PreviewTextInput"
|
||||||
Closing="Window_Closing">
|
Closing="Window_Closing">
|
||||||
|
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
@@ -108,7 +110,7 @@
|
|||||||
Stroke="Blue"
|
Stroke="Blue"
|
||||||
StrokeThickness="2"
|
StrokeThickness="2"
|
||||||
Fill="LightBlue"
|
Fill="LightBlue"
|
||||||
Opacity="0.5"
|
Opacity="0.2"
|
||||||
Panel.ZIndex="999999"
|
Panel.ZIndex="999999"
|
||||||
Visibility="Collapsed"/>
|
Visibility="Collapsed"/>
|
||||||
|
|
||||||
|
|||||||
@@ -92,9 +92,17 @@ namespace Serein.WorkBench
|
|||||||
private readonly List<NodeControlBase> selectNodeControls = [];
|
private readonly List<NodeControlBase> selectNodeControls = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 记录拖动开始时的鼠标位置
|
/// 记录开始拖动节点控件时的鼠标位置
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Point startPoint;
|
private Point startControlDragPoint;
|
||||||
|
/// <summary>
|
||||||
|
/// 记录移动画布开始时的鼠标位置
|
||||||
|
/// </summary>
|
||||||
|
private Point startCanvasDragPoint;
|
||||||
|
/// <summary>
|
||||||
|
/// 记录开始选取节点控件时的鼠标位置
|
||||||
|
/// </summary>
|
||||||
|
private Point startSelectControolPoint;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 记录开始连接的文本块
|
/// 记录开始连接的文本块
|
||||||
@@ -134,8 +142,10 @@ namespace Serein.WorkBench
|
|||||||
logWindow = new LogWindow();
|
logWindow = new LogWindow();
|
||||||
logWindow.Show();
|
logWindow.Show();
|
||||||
// 重定向 Console 输出
|
// 重定向 Console 输出
|
||||||
var logTextWriter = new LogTextWriter(WriteLog);
|
var logTextWriter = new LogTextWriter(WriteLog,() => logWindow.Clear());;
|
||||||
Console.SetOut(logTextWriter);
|
Console.SetOut(logTextWriter);
|
||||||
|
|
||||||
|
|
||||||
InitUI();
|
InitUI();
|
||||||
|
|
||||||
var project = App.FData;
|
var project = App.FData;
|
||||||
@@ -202,7 +212,7 @@ namespace Serein.WorkBench
|
|||||||
//{
|
//{
|
||||||
// connection.Refresh();
|
// connection.Refresh();
|
||||||
//}
|
//}
|
||||||
Console.WriteLine((FlowChartStackPanel.ActualWidth, FlowChartStackPanel.ActualHeight));
|
//Console.WriteLine((FlowChartStackPanel.ActualWidth, FlowChartStackPanel.ActualHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -517,62 +527,6 @@ namespace Serein.WorkBench
|
|||||||
|
|
||||||
#region 节点控件的创建
|
#region 节点控件的创建
|
||||||
|
|
||||||
private static TControl CreateNodeControl<TControl, TViewModel>(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<TNode, TControl,TViewModel>(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("无法创建节点控件");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建了节点,添加到画布。配置默认事件
|
/// 创建了节点,添加到画布。配置默认事件
|
||||||
@@ -593,50 +547,6 @@ namespace Serein.WorkBench
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 配置节点右键菜单
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="nodeControl"></param>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 创建菜单子项
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="header"></param>
|
|
||||||
/// <param name="handler"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static MenuItem CreateMenuItem(string header, RoutedEventHandler handler)
|
|
||||||
{
|
|
||||||
var menuItem = new MenuItem { Header = header };
|
|
||||||
menuItem.Click += handler;
|
|
||||||
return menuItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 配置节点事件
|
/// 配置节点事件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -682,6 +592,55 @@ namespace Serein.WorkBench
|
|||||||
|
|
||||||
#region 右键菜单事件
|
#region 右键菜单事件
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置节点右键菜单
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeControl"></param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 配置连接曲线的右键菜单
|
/// 配置连接曲线的右键菜单
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -693,6 +652,7 @@ namespace Serein.WorkBench
|
|||||||
connection.ArrowPath.ContextMenu = contextMenu;
|
connection.ArrowPath.ContextMenu = contextMenu;
|
||||||
connection.BezierPath.ContextMenu = contextMenu;
|
connection.BezierPath.ContextMenu = contextMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 删除该连线
|
/// 删除该连线
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -776,24 +736,7 @@ namespace Serein.WorkBench
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e)
|
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) // 正在连接节点
|
if (IsConnecting) // 正在连接节点
|
||||||
{
|
{
|
||||||
Point position = e.GetPosition(FlowChartCanvas);
|
Point position = e.GetPosition(FlowChartCanvas);
|
||||||
@@ -809,22 +752,41 @@ namespace Serein.WorkBench
|
|||||||
if (IsCanvasDragging) // 正在移动画布
|
if (IsCanvasDragging) // 正在移动画布
|
||||||
{
|
{
|
||||||
Point currentMousePosition = e.GetPosition(this);
|
Point currentMousePosition = e.GetPosition(this);
|
||||||
double deltaX = currentMousePosition.X - startPoint.X;
|
double deltaX = currentMousePosition.X - startCanvasDragPoint.X;
|
||||||
double deltaY = currentMousePosition.Y - startPoint.Y;
|
double deltaY = currentMousePosition.Y - startCanvasDragPoint.Y;
|
||||||
|
|
||||||
translateTransform.X += deltaX;
|
translateTransform.X += deltaX;
|
||||||
translateTransform.Y += deltaY;
|
translateTransform.Y += deltaY;
|
||||||
|
|
||||||
startPoint = currentMousePosition;
|
startCanvasDragPoint = currentMousePosition;
|
||||||
|
|
||||||
foreach (var line in Connections)
|
foreach (var line in Connections)
|
||||||
{
|
{
|
||||||
line.Refresh();
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -910,25 +872,6 @@ namespace Serein.WorkBench
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 穿透元素获取区域容器
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="element"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static T GetParentOfType<T>(DependencyObject element) where T : DependencyObject
|
|
||||||
{
|
|
||||||
while (element != null)
|
|
||||||
{
|
|
||||||
if (element is T)
|
|
||||||
{
|
|
||||||
return element as T;
|
|
||||||
}
|
|
||||||
element = VisualTreeHelper.GetParent(element);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 将节点放在目标区域中
|
/// 将节点放在目标区域中
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -972,12 +915,10 @@ namespace Serein.WorkBench
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void Block_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
private void Block_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
if (IsConnecting)
|
|
||||||
return;
|
|
||||||
|
|
||||||
IsControlDragging = true;
|
IsControlDragging = true;
|
||||||
startPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置
|
startControlDragPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置
|
||||||
((UIElement)sender).CaptureMouse(); // 捕获鼠标
|
((UIElement)sender).CaptureMouse(); // 捕获鼠标
|
||||||
|
e.Handled = true; // 防止事件传播影响其他控件
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -985,7 +926,15 @@ namespace Serein.WorkBench
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void Block_MouseMove(object sender, MouseEventArgs e)
|
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); // 获取当前鼠标位置
|
Point currentPosition = e.GetPosition(FlowChartCanvas); // 获取当前鼠标位置
|
||||||
// 获取引发事件的控件
|
// 获取引发事件的控件
|
||||||
@@ -994,8 +943,8 @@ namespace Serein.WorkBench
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
double deltaX = currentPosition.X - startPoint.X; // 计算X轴方向的偏移量
|
double deltaX = currentPosition.X - startControlDragPoint.X; // 计算X轴方向的偏移量
|
||||||
double deltaY = currentPosition.Y - startPoint.Y; // 计算Y轴方向的偏移量
|
double deltaY = currentPosition.Y - startControlDragPoint.Y; // 计算Y轴方向的偏移量
|
||||||
|
|
||||||
double newLeft = Canvas.GetLeft(block) + deltaX; // 新的左边距
|
double newLeft = Canvas.GetLeft(block) + deltaX; // 新的左边距
|
||||||
double newTop = Canvas.GetTop(block) + deltaY; // 新的上边距
|
double newTop = Canvas.GetTop(block) + deltaY; // 新的上边距
|
||||||
@@ -1012,7 +961,7 @@ namespace Serein.WorkBench
|
|||||||
|
|
||||||
UpdateConnections(block);
|
UpdateConnections(block);
|
||||||
|
|
||||||
startPoint = currentPosition; // 更新起始点位置
|
startControlDragPoint = currentPosition; // 更新起始点位置
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1159,13 +1108,14 @@ namespace Serein.WorkBench
|
|||||||
#region 拖动画布实现缩放平移效果
|
#region 拖动画布实现缩放平移效果
|
||||||
private void FlowChartCanvas_MouseDown(object sender, MouseButtonEventArgs e)
|
private void FlowChartCanvas_MouseDown(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.MiddleButton == MouseButtonState.Pressed)
|
IsCanvasDragging = true;
|
||||||
{
|
startCanvasDragPoint = e.GetPosition(this);
|
||||||
IsCanvasDragging = true;
|
FlowChartCanvas.CaptureMouse();
|
||||||
startPoint = e.GetPosition(this);
|
e.Handled = true; // 防止事件传播影响其他控件
|
||||||
FlowChartCanvas.CaptureMouse();
|
//if (e.MiddleButton == MouseButtonState.Pressed)
|
||||||
e.Handled = true; // 防止事件传播影响其他控件
|
//{
|
||||||
}
|
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FlowChartCanvas_MouseUp(object sender, MouseButtonEventArgs e)
|
private void FlowChartCanvas_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
@@ -1180,7 +1130,7 @@ namespace Serein.WorkBench
|
|||||||
// 单纯缩放画布,不改变画布大小
|
// 单纯缩放画布,不改变画布大小
|
||||||
private void FlowChartCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
|
private void FlowChartCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
|
||||||
{
|
{
|
||||||
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
// if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
||||||
{
|
{
|
||||||
if (e.Delta < 0 && scaleTransform.ScaleX < 0.2) return;
|
if (e.Delta < 0 && scaleTransform.ScaleX < 0.2) return;
|
||||||
if (e.Delta > 0 && scaleTransform.ScaleY > 1.5) return;
|
if (e.Delta > 0 && scaleTransform.ScaleY > 1.5) return;
|
||||||
@@ -1355,26 +1305,29 @@ namespace Serein.WorkBench
|
|||||||
/// <param name="e"></param>
|
/// <param name="e"></param>
|
||||||
private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
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.SetLeft(SelectionRectangle, startSelectControolPoint.X);
|
||||||
Canvas.SetTop(SelectionRectangle, startPoint.Y);
|
Canvas.SetTop(SelectionRectangle, startSelectControolPoint.Y);
|
||||||
SelectionRectangle.Width = 0;
|
SelectionRectangle.Width = 0;
|
||||||
SelectionRectangle.Height = 0;
|
SelectionRectangle.Height = 0;
|
||||||
|
|
||||||
// 显示选取矩形
|
// 显示选取矩形
|
||||||
SelectionRectangle.Visibility = Visibility.Visible;
|
SelectionRectangle.Visibility = Visibility.Visible;
|
||||||
SelectionRectangle.ContextMenu ??= ConfiguerSelectionRectangle();
|
SelectionRectangle.ContextMenu ??= ConfiguerSelectionRectangle();
|
||||||
|
|
||||||
// 捕获鼠标,以便在鼠标移动到Canvas外部时仍能处理事件
|
// 捕获鼠标,以便在鼠标移动到Canvas外部时仍能处理事件
|
||||||
FlowChartCanvas.CaptureMouse();
|
FlowChartCanvas.CaptureMouse();
|
||||||
}
|
|
||||||
|
//if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
||||||
|
//{
|
||||||
|
|
||||||
|
//}
|
||||||
|
e.Handled = true; // 防止事件传播影响其他控件
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContextMenu ConfiguerSelectionRectangle()
|
private ContextMenu ConfiguerSelectionRectangle()
|
||||||
@@ -1448,6 +1401,7 @@ namespace Serein.WorkBench
|
|||||||
if(selectNodeControls.Count == 0)
|
if(selectNodeControls.Count == 0)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"没有选择控件");
|
Console.WriteLine($"没有选择控件");
|
||||||
|
SelectionRectangle.Visibility = Visibility.Collapsed;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Console.WriteLine($"一共选取了{selectNodeControls.Count}个控件");
|
Console.WriteLine($"一共选取了{selectNodeControls.Count}个控件");
|
||||||
@@ -1455,6 +1409,8 @@ namespace Serein.WorkBench
|
|||||||
{
|
{
|
||||||
node.ViewModel.Selected();
|
node.ViewModel.Selected();
|
||||||
node.ViewModel.CancelSelect();
|
node.ViewModel.CancelSelect();
|
||||||
|
node.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFC700"));
|
||||||
|
node.BorderThickness = new Thickness(4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void CancelSelectNode()
|
private void CancelSelectNode()
|
||||||
@@ -1462,13 +1418,109 @@ namespace Serein.WorkBench
|
|||||||
foreach (var node in selectNodeControls)
|
foreach (var node in selectNodeControls)
|
||||||
{
|
{
|
||||||
node.ViewModel.CancelSelect();
|
node.ViewModel.CancelSelect();
|
||||||
|
node.BorderBrush = Brushes.Black;
|
||||||
|
node.BorderThickness = new Thickness(0);
|
||||||
}
|
}
|
||||||
selectNodeControls.Clear();
|
selectNodeControls.Clear();
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 窗体静态方法
|
||||||
|
|
||||||
|
|
||||||
|
private static TControl CreateNodeControl<TControl, TViewModel>(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<TNode, TControl, TViewModel>(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("无法创建节点控件");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建菜单子项
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="header"></param>
|
||||||
|
/// <param name="handler"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static MenuItem CreateMenuItem(string header, RoutedEventHandler handler)
|
||||||
|
{
|
||||||
|
var menuItem = new MenuItem { Header = header };
|
||||||
|
menuItem.Click += handler;
|
||||||
|
return menuItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 穿透元素获取区域容器
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="element"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static T GetParentOfType<T>(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)
|
private async void ButtonDebugRun_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
logWindow?.Show();
|
logWindow?.Show();
|
||||||
await FlowEnvironment.StartAsync();
|
|
||||||
|
await FlowEnvironment.StartAsync(); // 快
|
||||||
|
|
||||||
|
//await Task.Run( FlowEnvironment.StartAsync); // 上下文多次切换的场景中吗慢了1/5
|
||||||
|
//await Task.Factory.StartNew(FlowEnvironment.StartAsync); // 慢了1/5
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1614,6 +1670,20 @@ namespace Serein.WorkBench
|
|||||||
Uri relativeUri = baseUri.MakeRelativeUri(fullUri);
|
Uri relativeUri = baseUri.MakeRelativeUri(fullUri);
|
||||||
return Uri.UnescapeDataString(relativeUri.ToString().Replace('/', System.IO.Path.DirectorySeparatorChar));
|
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层面上显示为 带箭头指向的贝塞尔曲线
|
#region 创建两个控件之间的连接关系,在UI层面上显示为 带箭头指向的贝塞尔曲线
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ namespace Serein.WorkBench.Node.ViewModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal NodeModelBase Node { get; }
|
internal NodeModelBase Node { get; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private bool isSelect;
|
private bool isSelect;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 表示节点控件是否被选中
|
/// 表示节点控件是否被选中
|
||||||
@@ -37,29 +40,68 @@ namespace Serein.WorkBench.Node.ViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MethodDetails methodDetails;
|
|
||||||
|
|
||||||
|
public NodeDebugSetting DebugSetting
|
||||||
public MethodDetails MethodDetails
|
|
||||||
{
|
{
|
||||||
get => methodDetails;
|
get => Node.DebugSetting;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
methodDetails = value;
|
if (value != null)
|
||||||
OnPropertyChanged();
|
{
|
||||||
|
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;
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||||
|
|
||||||
{
|
{
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public void Selected()
|
public void Selected()
|
||||||
{
|
{
|
||||||
IsSelect = true;
|
IsSelect = true;
|
||||||
@@ -69,5 +111,7 @@ namespace Serein.WorkBench.Node.ViewModel
|
|||||||
{
|
{
|
||||||
IsSelect = false;
|
IsSelect = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,43 +5,73 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
|
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
|
||||||
xmlns:vm="clr-namespace:Serein.WorkBench.Node.ViewModel"
|
xmlns:vm="clr-namespace:Serein.WorkBench.Node.ViewModel"
|
||||||
|
xmlns:Converters="clr-namespace:Serein.WorkBench.Tool.Converters"
|
||||||
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
|
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
|
||||||
MaxWidth="300">
|
MaxWidth="300">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<!--<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />-->
|
||||||
|
<Converters:InvertableBooleanToVisibilityConverter x:Key="InvertedBoolConverter"/>
|
||||||
|
</UserControl.Resources>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ToolTip>
|
<Grid.ToolTip>
|
||||||
<ToolTip Background="LightYellow" Foreground="Black" Content="{Binding MethodDetails.MethodName, UpdateSourceTrigger=PropertyChanged}" />
|
<ToolTip Background="LightYellow" Foreground="Black" Content="{Binding MethodDetails.MethodName, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
</Grid.ToolTip>
|
</Grid.ToolTip>
|
||||||
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Background="#FFCFDF">
|
<Border>
|
||||||
<CheckBox VerticalContentAlignment="Center">
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<!-- 默认无边框 -->
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<!-- 当 DebugSetting.IsInterrupt 为 True 时,显示红色边框 -->
|
||||||
|
<DataTrigger Binding="{Binding IsInterrupt}" Value="True">
|
||||||
|
<Setter Property="BorderBrush" Value="Red" />
|
||||||
|
<Setter Property="BorderThickness" Value="2" />
|
||||||
|
<Setter Property="Background" Value="#80000000" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
|
||||||
</CheckBox>
|
|
||||||
<TextBlock Text="{Binding MethodDetails.MethodTips}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<themes:MethodDetailsControl Grid.Row="1" MethodDetails="{Binding MethodDetails}" />
|
|
||||||
|
|
||||||
<Grid Grid.Row="2" >
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="50"/>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Border Grid.Column="0" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
|
|
||||||
<TextBlock Text="result" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
|
||||||
</Border>
|
|
||||||
<Border Grid.Column="1" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
|
|
||||||
<TextBlock Text="{Binding MethodDetails.ReturnType}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
|
||||||
</Border>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="0" Orientation="Horizontal" Background="#FFCFDF">
|
||||||
|
<CheckBox IsChecked="{Binding DebugSetting.IsEnable, Mode=TwoWay}" VerticalContentAlignment="Center"/>
|
||||||
|
<CheckBox IsChecked="{Binding MethodDetails.IsProtectionParameter, Mode=TwoWay}" VerticalContentAlignment="Center"/>
|
||||||
|
<TextBlock Text="{Binding MethodDetails.MethodTips}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
<themes:MethodDetailsControl Grid.Row="1" MethodDetails="{Binding MethodDetails}"/>
|
||||||
|
<!-- ParameterProtectionMask 参数保护 -->
|
||||||
|
<!--取反 Visibility="{Binding DebugSetting.IsEnable, Converter={StaticResource InvertedBoolConverter}, ConverterParameter=Inverted}"-->
|
||||||
|
<Border Grid.Row="1" x:Name="ParameterProtectionMask" Background="LightBlue" Opacity="0.5" BorderBrush="Black" BorderThickness="0"
|
||||||
|
Visibility="{Binding MethodDetails.IsProtectionParameter, Converter={StaticResource InvertedBoolConverter},ConverterParameter=Normal}" />
|
||||||
|
<Grid Grid.Row="2" >
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="50"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Border Grid.Column="0" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
|
||||||
|
<TextBlock Text="result" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
<Border Grid.Column="1" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
|
||||||
|
<TextBlock Text="{Binding MethodDetails.ReturnType}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!--Visibility="{Binding IsEnable, Converter={StaticResource BoolToVisConverter}, ConverterParameter=False}"-->
|
||||||
|
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</local:NodeControlBase>
|
</local:NodeControlBase>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
|
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
|
||||||
MaxWidth="300">
|
MaxWidth="300">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Border BorderBrush="Black" BorderThickness="1" Padding="10">
|
<!--<Border BorderBrush="Black" BorderThickness="1" Padding="10">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<Grid Margin="2,2,2,5">
|
<Grid Margin="2,2,2,5">
|
||||||
<TextBlock Text="动作区域" FontWeight="Bold" HorizontalAlignment="Left" FontSize="14" Margin="0,1,0,0"/>
|
<TextBlock Text="动作区域" FontWeight="Bold" HorizontalAlignment="Left" FontSize="14" Margin="0,1,0,0"/>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
<ListBox x:Name="ActionsListBox" AllowDrop="True" Drop="ActionsListBox_Drop" />
|
<ListBox x:Name="ActionsListBox" AllowDrop="True" Drop="ActionsListBox_Drop" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>-->
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,7 @@
|
|||||||
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
|
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
|
||||||
MaxWidth="300">
|
MaxWidth="300">
|
||||||
|
|
||||||
<!--d:DataContext="{d:DesignInstance Type=vm:ConditionNodeControlViewModel}"-->
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<!--<vm:TypeToStringConverter x:Key="TypeToStringConverter"/>-->
|
|
||||||
<BooleanToVisibilityConverter x:Key="BoolToVis" />
|
<BooleanToVisibilityConverter x:Key="BoolToVis" />
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ namespace Serein.WorkBench.Themes
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class TypeViewerWindow : Window
|
public partial class TypeViewerWindow : Window
|
||||||
{
|
{
|
||||||
|
|
||||||
public TypeViewerWindow()
|
public TypeViewerWindow()
|
||||||
|
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
@@ -34,55 +32,244 @@ namespace Serein.WorkBench.Themes
|
|||||||
if (Type == null)
|
if (Type == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var rootNode = new TreeViewItem { Header = Type.Name };
|
TypeNodeDetails typeNodeDetails = new TypeNodeDetails
|
||||||
AddMembersToTreeNode(rootNode, Type);
|
{
|
||||||
|
Name = Type.Name,
|
||||||
|
DataType = Type,
|
||||||
|
};
|
||||||
|
var rootNode = new TreeViewItem { Header = Type.Name, Tag = typeNodeDetails };
|
||||||
|
AddPlaceholderNode(rootNode); // 添加占位符节点
|
||||||
TypeTreeView.Items.Clear();
|
TypeTreeView.Items.Clear();
|
||||||
TypeTreeView.Items.Add(rootNode);
|
TypeTreeView.Items.Add(rootNode);
|
||||||
|
|
||||||
|
rootNode.Expanded += TreeViewItem_Expanded; // 监听节点展开事件
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加占位符节点
|
||||||
|
/// </summary>
|
||||||
|
private void AddPlaceholderNode(TreeViewItem node)
|
||||||
|
{
|
||||||
|
node.Items.Add(new TreeViewItem { Header = "Loading..." });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点展开事件,延迟加载子节点
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 添加属性节点
|
/// 添加属性节点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="node"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
private void AddMembersToTreeNode(TreeViewItem node, Type type)
|
private void AddMembersToTreeNode(TreeViewItem node, Type type)
|
||||||
{
|
{
|
||||||
var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
||||||
foreach (var member in members)
|
foreach (var member in members)
|
||||||
{
|
{
|
||||||
TreeViewItem memberNode;
|
TreeViewItem memberNode = ConfigureTreeViewItem(member); // 生成类型节点的子项
|
||||||
try
|
if (ConfigureTreeItemMenu(memberNode,member, out ContextMenu? contextMenu))
|
||||||
{
|
{
|
||||||
memberNode = new TreeViewItem { Header = member.Name };
|
memberNode.ContextMenu = contextMenu; // 设置子项节点的事件
|
||||||
}
|
|
||||||
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}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Items.Add(memberNode);
|
node.Items.Add(memberNode); // 添加到父节点中
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成类型节点的子项
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="member"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置子项节点的事件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="member"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前节点的完整路径,例如 "node1.node2.node3.node4"
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">目标节点</param>
|
||||||
|
/// <returns>节点路径</returns>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定节点的父级节点
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">目标节点</param>
|
||||||
|
/// <returns>父节点</returns>
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 属性名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 属性类型
|
||||||
|
/// </summary>
|
||||||
|
public TreeItemType ItemType { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据类型
|
||||||
|
/// </summary>
|
||||||
|
public Type DataType { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 数据(调试用?)
|
||||||
|
/// </summary>
|
||||||
|
public object DataValue { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 数据路径
|
||||||
|
/// </summary>
|
||||||
|
public string DataPath { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TreeItemType
|
||||||
|
{
|
||||||
|
Property,
|
||||||
|
Method,
|
||||||
|
Field
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.IO;
|
using System.Collections.Concurrent;
|
||||||
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Serein.WorkBench.tool
|
namespace Serein.WorkBench.tool
|
||||||
@@ -6,10 +7,24 @@ namespace Serein.WorkBench.tool
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 可以捕获类库输出的打印输出
|
/// 可以捕获类库输出的打印输出
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LogTextWriter(Action<string> logAction) : TextWriter
|
public class LogTextWriter : TextWriter
|
||||||
{
|
{
|
||||||
private readonly Action<string> logAction = logAction;
|
private readonly Action<string> logAction;
|
||||||
private readonly StringWriter stringWriter = new();
|
private readonly StringWriter stringWriter = new();
|
||||||
|
private readonly BlockingCollection<string> logQueue = new();
|
||||||
|
private readonly Task logTask;
|
||||||
|
|
||||||
|
// 用于计数的字段
|
||||||
|
private int writeCount = 0;
|
||||||
|
private const int maxWrites = 500;
|
||||||
|
private readonly Action clearTextBoxAction;
|
||||||
|
|
||||||
|
public LogTextWriter(Action<string> logAction, Action clearTextBoxAction)
|
||||||
|
{
|
||||||
|
this.logAction = logAction;
|
||||||
|
this.clearTextBoxAction = clearTextBoxAction;
|
||||||
|
logTask = Task.Run(ProcessLogQueue); // 异步处理日志
|
||||||
|
}
|
||||||
|
|
||||||
public override Encoding Encoding => Encoding.UTF8;
|
public override Encoding Encoding => Encoding.UTF8;
|
||||||
|
|
||||||
@@ -18,28 +33,60 @@ namespace Serein.WorkBench.tool
|
|||||||
stringWriter.Write(value);
|
stringWriter.Write(value);
|
||||||
if (value == '\n')
|
if (value == '\n')
|
||||||
{
|
{
|
||||||
logAction(stringWriter.ToString());
|
EnqueueLog();
|
||||||
stringWriter.GetStringBuilder().Clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Write(string? value)
|
public override void Write(string? value)
|
||||||
{
|
{
|
||||||
if(string.IsNullOrWhiteSpace(value)) { return; }
|
if (string.IsNullOrWhiteSpace(value)) return;
|
||||||
stringWriter.Write(value);
|
stringWriter.Write(value);
|
||||||
if (value.Contains('\n'))
|
if (value.Contains('\n'))
|
||||||
{
|
{
|
||||||
logAction(stringWriter.ToString());
|
EnqueueLog();
|
||||||
stringWriter.GetStringBuilder().Clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void WriteLine(string? value)
|
public override void WriteLine(string? value)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(value)) { return; }
|
if (string.IsNullOrWhiteSpace(value)) return;
|
||||||
stringWriter.WriteLine(value);
|
stringWriter.WriteLine(value);
|
||||||
logAction(stringWriter.ToString());
|
EnqueueLog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnqueueLog()
|
||||||
|
{
|
||||||
|
logQueue.Add(stringWriter.ToString());
|
||||||
stringWriter.GetStringBuilder().Clear();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user