mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-04-28 18:43:23 +08:00
优化了中断功能,增加了节点变量的查看。
This commit is contained in:
@@ -45,7 +45,7 @@ namespace Serein.Library.Api
|
|||||||
/// 环境中流程起始节点发生了改变
|
/// 环境中流程起始节点发生了改变
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="eventArgs"></param>
|
/// <param name="eventArgs"></param>
|
||||||
public delegate void StartNodeChangeHandler(StartNodeChangeEventArgs eventArgs);
|
public delegate void StartNodeChangeHandler(StartNodeChangeEventArgs eventArgs);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 环境事件签名
|
#region 环境事件签名
|
||||||
@@ -211,19 +211,134 @@ namespace Serein.Library.Api
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 被监视的对象改变事件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventArgs"></param>
|
||||||
|
public delegate void MonitorObjectChangeHandler(MonitorObjectEventArgs eventArgs);
|
||||||
|
/// <summary>
|
||||||
|
/// 节点中断状态改变事件(开启了中断/取消了中断)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventArgs"></param>
|
||||||
|
public delegate void NodeInterruptStateChangeHandler(NodeInterruptStateChangeEventArgs eventArgs);
|
||||||
|
/// <summary>
|
||||||
|
/// 节点触发中断事件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventArgs"></param>
|
||||||
|
public delegate void NodeInterruptTriggerHandler(NodeInterruptTriggerEventArgs eventArgs);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 监视的节点数据发生变化
|
||||||
|
/// </summary>
|
||||||
|
public class MonitorObjectEventArgs : FlowEventArgs
|
||||||
|
{
|
||||||
|
public MonitorObjectEventArgs(string nodeGuid,object newData)
|
||||||
|
{
|
||||||
|
NodeGuid = nodeGuid;
|
||||||
|
NewData = newData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 中断的节点Guid
|
||||||
|
/// </summary>
|
||||||
|
public string NodeGuid { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 新的数据
|
||||||
|
/// </summary>
|
||||||
|
public object NewData { get; protected set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点中断状态改变事件参数
|
||||||
|
/// </summary>
|
||||||
|
public class NodeInterruptStateChangeEventArgs : FlowEventArgs
|
||||||
|
{
|
||||||
|
public NodeInterruptStateChangeEventArgs(string nodeGuid,InterruptClass @class)
|
||||||
|
{
|
||||||
|
NodeGuid = nodeGuid;
|
||||||
|
Class = @class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 中断的节点Guid
|
||||||
|
/// </summary>
|
||||||
|
public string NodeGuid { get; protected set; }
|
||||||
|
public InterruptClass Class { get; protected set; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 节点触发了中断事件参数
|
||||||
|
/// </summary>
|
||||||
|
public class NodeInterruptTriggerEventArgs : FlowEventArgs
|
||||||
|
{
|
||||||
|
public NodeInterruptTriggerEventArgs(string nodeGuid)
|
||||||
|
{
|
||||||
|
NodeGuid = nodeGuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 中断的节点Guid
|
||||||
|
/// </summary>
|
||||||
|
public string NodeGuid { get; protected set; }
|
||||||
|
}
|
||||||
|
|
||||||
public interface IFlowEnvironment
|
public interface IFlowEnvironment
|
||||||
{
|
{
|
||||||
ChannelFlowInterrupt ChannelFlowInterrupt { get; set; }
|
ChannelFlowInterrupt ChannelFlowInterrupt { get; set; }
|
||||||
|
|
||||||
|
|
||||||
event FlowRunCompleteHandler OnFlowRunComplete;
|
/// <summary>
|
||||||
event ProjectLoadedHandler OnProjectLoaded;
|
/// 加载Dll
|
||||||
|
/// </summary>
|
||||||
event LoadDLLHandler OnDllLoad;
|
event LoadDLLHandler OnDllLoad;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 项目加载完成
|
||||||
|
/// </summary>
|
||||||
|
event ProjectLoadedHandler OnProjectLoaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点连接属性改变事件
|
||||||
|
/// </summary>
|
||||||
event NodeConnectChangeHandler OnNodeConnectChange;
|
event NodeConnectChangeHandler OnNodeConnectChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点创建事件
|
||||||
|
/// </summary>
|
||||||
event NodeCreateHandler OnNodeCreate;
|
event NodeCreateHandler OnNodeCreate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 移除节点事件
|
||||||
|
/// </summary>
|
||||||
event NodeRemoteHandler OnNodeRemote;
|
event NodeRemoteHandler OnNodeRemote;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 起始节点变化事件
|
||||||
|
/// </summary>
|
||||||
event StartNodeChangeHandler OnStartNodeChange;
|
event StartNodeChangeHandler OnStartNodeChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 流程运行完成事件
|
||||||
|
/// </summary>
|
||||||
|
event FlowRunCompleteHandler OnFlowRunComplete;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 被监视的对象改变事件
|
||||||
|
/// </summary>
|
||||||
|
event MonitorObjectChangeHandler OnMonitorObjectChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点中断状态变化事件
|
||||||
|
/// </summary>
|
||||||
|
event NodeInterruptStateChangeHandler OnNodeInterruptStateChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点触发中断
|
||||||
|
/// </summary>
|
||||||
|
event NodeInterruptTriggerHandler OnNodeInterruptTrigger;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 保存当前项目
|
/// 保存当前项目
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -252,6 +367,7 @@ namespace Serein.Library.Api
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
bool TryGetMethodDetails(string methodName,out MethodDetails md);
|
bool TryGetMethodDetails(string methodName,out MethodDetails md);
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开始运行
|
/// 开始运行
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -261,6 +377,8 @@ namespace Serein.Library.Api
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void Exit();
|
void Exit();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置流程起点节点
|
/// 设置流程起点节点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -293,6 +411,27 @@ namespace Serein.Library.Api
|
|||||||
void RemoteNode(string nodeGuid);
|
void RemoteNode(string nodeGuid);
|
||||||
|
|
||||||
|
|
||||||
}
|
/// <summary>
|
||||||
|
/// 设置节点中断级别
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeGuid">被中断的节点Guid</param>
|
||||||
|
/// <param name="interruptClass">新的中断级别</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool NodeInterruptChange(string nodeGuid,InterruptClass interruptClass);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// /// <summary>
|
||||||
|
/// 设置节点数据监视状态
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeGuid">需要监视的节点Guid</param>
|
||||||
|
/// <param name="isMonitor">是否监视</param>
|
||||||
|
void SetNodeFLowDataMonitorState(string nodeGuid, bool isMonitor);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点数据更新通知
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeGuid"></param>
|
||||||
|
void FlowDataUpdateNotification(string nodeGuid, object flowData);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ namespace Serein.Library.Api
|
|||||||
object Get(string name);
|
object Get(string name);
|
||||||
void CustomRegisterInstance(string name, object instance, bool needInjectProperty = true);
|
void CustomRegisterInstance(string name, object instance, bool needInjectProperty = true);
|
||||||
|
|
||||||
|
ISereinIOC Run<T>(string name, Action<T> action);
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建目标类型的对象, 并注入依赖项
|
/// 创建目标类型的对象, 并注入依赖项
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -12,14 +12,17 @@ namespace Serein.Library.Entity
|
|||||||
public bool IsEnable { get; set; } = true;
|
public bool IsEnable { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否中断(调试中断功能)
|
/// 是否监视数据改变
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsInterrupt { get; set; } = false;
|
public bool IsMonitorFlowData { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 中断级别,暂时停止继续执行后继分支。
|
/// 中断级别,暂时停止继续执行后继分支。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public InterruptClass InterruptClass { get; set; } = InterruptClass.None;
|
public InterruptClass InterruptClass { get; set; } = InterruptClass.None;
|
||||||
|
|
||||||
|
|
||||||
|
public List<string> InterruptExpression { get; } = new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -32,15 +35,15 @@ namespace Serein.Library.Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
None,
|
None,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分支中断,当前节点。
|
/// 分支中断,中断进入当前节点的分支。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Branch,
|
Branch,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分组中断,相同中断分组的节点。
|
/// 分组中断,中断进入指定节点分组的分支。(暂未实现相关)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Group,
|
Group,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 全局中断,其它所有节点。
|
/// 全局中断,中断全局所有节点的运行。(暂未实现相关)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Global,
|
Global,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -366,6 +366,27 @@ namespace Serein.Library.Utils
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region run()
|
#region run()
|
||||||
|
public ISereinIOC Run<T>(string name, Action<T> action)
|
||||||
|
{
|
||||||
|
var obj = Get(name);
|
||||||
|
if (obj != null)
|
||||||
|
{
|
||||||
|
if(obj is T service)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action(service);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public ISereinIOC Run<T>(Action<T> action)
|
public ISereinIOC Run<T>(Action<T> action)
|
||||||
{
|
{
|
||||||
var service = GetOrRegisterInstantiate<T>();
|
var service = GetOrRegisterInstantiate<T>();
|
||||||
|
|||||||
@@ -123,8 +123,16 @@ namespace Serein.Library.Web
|
|||||||
// 停止服务器
|
// 停止服务器
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
listener?.Stop(); // 停止监听
|
|
||||||
listener?.Close(); // 关闭监听器
|
try
|
||||||
|
{
|
||||||
|
listener?.Stop(); // 停止监听
|
||||||
|
listener?.Close(); // 关闭监听器
|
||||||
|
}
|
||||||
|
catch (Exception EX)
|
||||||
|
{
|
||||||
|
Console.WriteLine(EX);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ namespace Serein.NodeFlow.Base
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前传递数据(执行了节点对应的方法,才会存在值)
|
/// 当前传递数据(执行了节点对应的方法,才会存在值)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public object? FlowData { get; set; } = null;
|
protected object? FlowData { get; set; } = null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Serein.NodeFlow.Tool.SereinExpression;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -34,7 +35,6 @@ namespace Serein.NodeFlow.Base
|
|||||||
public void Interrupt()
|
public void Interrupt()
|
||||||
{
|
{
|
||||||
this.DebugSetting.InterruptClass = InterruptClass.Branch;
|
this.DebugSetting.InterruptClass = InterruptClass.Branch;
|
||||||
this.DebugSetting.IsInterrupt = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -43,7 +43,6 @@ namespace Serein.NodeFlow.Base
|
|||||||
public void CancelInterrupt()
|
public void CancelInterrupt()
|
||||||
{
|
{
|
||||||
this.DebugSetting.InterruptClass = InterruptClass.None;
|
this.DebugSetting.InterruptClass = InterruptClass.None;
|
||||||
this.DebugSetting.IsInterrupt = false;
|
|
||||||
CancelInterruptCallback?.Invoke();
|
CancelInterruptCallback?.Invoke();
|
||||||
CancelInterruptCallback = null;
|
CancelInterruptCallback = null;
|
||||||
}
|
}
|
||||||
@@ -106,93 +105,64 @@ namespace Serein.NodeFlow.Base
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task StartExecute(IDynamicContext context)
|
public async Task StartExecute(IDynamicContext context)
|
||||||
{
|
{
|
||||||
CancellationTokenSource cts = null;
|
|
||||||
|
|
||||||
try
|
Stack<NodeModelBase> stack = new Stack<NodeModelBase>();
|
||||||
|
stack.Push(this);
|
||||||
|
var cts = context.SereinIoc.Get<CancellationTokenSource>(FlowStarter.FlipFlopCtsName);
|
||||||
|
while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环
|
||||||
{
|
{
|
||||||
cts = context.SereinIoc.Get<CancellationTokenSource>(FlowStarter.FlipFlopCtsName);
|
// 从栈中弹出一个节点作为当前节点进行处理
|
||||||
|
var currentNode = stack.Pop();
|
||||||
|
|
||||||
|
// 设置方法执行的对象
|
||||||
Stack<NodeModelBase> stack = new Stack<NodeModelBase>();
|
if (currentNode.MethodDetails?.ActingInstance == null && currentNode.MethodDetails?.ActingInstanceType is not null)
|
||||||
stack.Push(this);
|
|
||||||
while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环
|
|
||||||
{
|
{
|
||||||
// 从栈中弹出一个节点作为当前节点进行处理
|
currentNode.MethodDetails.ActingInstance ??= context.SereinIoc.GetOrRegisterInstantiate(currentNode.MethodDetails.ActingInstanceType);
|
||||||
var currentNode = stack.Pop();
|
|
||||||
|
|
||||||
// 设置方法执行的对象
|
|
||||||
if (currentNode.MethodDetails?.ActingInstance == null && currentNode.MethodDetails?.ActingInstanceType is not null)
|
|
||||||
{
|
|
||||||
currentNode.MethodDetails.ActingInstance ??= context.SereinIoc.GetOrRegisterInstantiate(currentNode.MethodDetails.ActingInstanceType);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region 执行相关
|
|
||||||
// 首先执行上游分支
|
|
||||||
#if false
|
|
||||||
var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream];
|
|
||||||
for (int i = upstreamNodes.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
if (upstreamNodes[i].DebugSetting.IsEnable) // 排除未启用的上游节点
|
|
||||||
{
|
|
||||||
upstreamNodes[i].PreviousNode = currentNode;
|
|
||||||
await upstreamNodes[i].StartExecute(context); // 执行流程节点的上游分支
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
currentNode.FlowData = await currentNode.ExecutingAsync(context); // 流程中正常执行
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 执行完成
|
|
||||||
if (currentNode.NextOrientation == ConnectionType.None) break; // 不再执行
|
|
||||||
|
|
||||||
|
|
||||||
// 选择后继分支
|
|
||||||
var nextNodes = currentNode.SuccessorNodes[currentNode.NextOrientation];
|
|
||||||
|
|
||||||
// 将下一个节点集合中的所有节点逆序推入栈中
|
|
||||||
for (int i = nextNodes.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
// 排除未启用的节点
|
|
||||||
if (nextNodes[i].DebugSetting.IsEnable)
|
|
||||||
{
|
|
||||||
nextNodes[i].PreviousNode = currentNode;
|
|
||||||
stack.Push(nextNodes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
cts?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryCreateInterruptTask(IDynamicContext context, NodeModelBase currentNode, out Task<CancelType>? task)
|
#region 执行相关
|
||||||
{
|
|
||||||
bool haveTask;
|
|
||||||
Console.WriteLine($"[{currentNode.MethodDetails.MethodName}]在当前分支中断");
|
|
||||||
|
|
||||||
if (currentNode.DebugSetting.InterruptClass == InterruptClass.None)
|
// 首先执行上游分支
|
||||||
{
|
var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream];
|
||||||
haveTask = false;
|
for (int i = upstreamNodes.Count - 1; i >= 0; i--)
|
||||||
task = null;
|
{
|
||||||
currentNode.DebugSetting.IsInterrupt = false; // 纠正设置
|
if (upstreamNodes[i].DebugSetting.IsEnable) // 排除未启用的上游节点
|
||||||
}
|
{
|
||||||
else if (currentNode.DebugSetting.InterruptClass == InterruptClass.Branch) // 中断当前分支
|
upstreamNodes[i].PreviousNode = currentNode;
|
||||||
{
|
var upNewFlowData = await upstreamNodes[i].ExecutingAsync(context); // 执行流程节点的上游分支
|
||||||
currentNode.DebugSetting.IsInterrupt = true;
|
await FlowRefreshDataOrInterrupt(context, upstreamNodes[i], upNewFlowData); // 执行上游分支后刷新上游节点数据
|
||||||
haveTask = true;
|
}
|
||||||
task = context.FlowEnvironment.ChannelFlowInterrupt.CreateChannelWithTimeoutAsync(currentNode.Guid, TimeSpan.FromSeconds(60 * 30)); // 中断30分钟
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
haveTask = false;
|
|
||||||
task = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return haveTask;
|
// 执行当前节点
|
||||||
|
var newFlowData = await currentNode.ExecutingAsync(context);
|
||||||
|
await FlowRefreshDataOrInterrupt(context, currentNode, newFlowData); // 执行当前节点后刷新数据
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region 执行完成
|
||||||
|
if (cts == null || cts.IsCancellationRequested || currentNode.NextOrientation == ConnectionType.None)
|
||||||
|
{
|
||||||
|
// 不再执行
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 选择后继分支
|
||||||
|
var nextNodes = currentNode.SuccessorNodes[currentNode.NextOrientation];
|
||||||
|
|
||||||
|
// 将下一个节点集合中的所有节点逆序推入栈中
|
||||||
|
for (int i = nextNodes.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
// 排除未启用的节点
|
||||||
|
if (nextNodes[i].DebugSetting.IsEnable)
|
||||||
|
{
|
||||||
|
nextNodes[i].PreviousNode = currentNode;
|
||||||
|
stack.Push(nextNodes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -203,12 +173,11 @@ namespace Serein.NodeFlow.Base
|
|||||||
public virtual async Task<object?> ExecutingAsync(IDynamicContext context)
|
public virtual async Task<object?> ExecutingAsync(IDynamicContext context)
|
||||||
{
|
{
|
||||||
#region 调试中断
|
#region 调试中断
|
||||||
if (DebugSetting.IsInterrupt && TryCreateInterruptTask(context, this, out Task<CancelType>? task)) // 执行节点前检查中断
|
if (DebugSetting.InterruptClass != InterruptClass.None && TryCreateInterruptTask(context, this, out Task<CancelType>? task)) // 执行节点前检查中断
|
||||||
{
|
{
|
||||||
string guid = this.Guid.ToString();
|
string guid = this.Guid.ToString();
|
||||||
this.CancelInterruptCallback ??= () => context.FlowEnvironment.ChannelFlowInterrupt.TriggerSignal(guid);
|
this.CancelInterruptCallback ??= () => context.FlowEnvironment.ChannelFlowInterrupt.TriggerSignal(guid);
|
||||||
var cancelType = await task!;
|
var cancelType = await task!;
|
||||||
task?.ToString();
|
|
||||||
await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支");
|
await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +192,7 @@ namespace Serein.NodeFlow.Base
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Action/Func([方法作用的实例],[可能的参数值],[可能的返回值])
|
// Action/Func([方法作用的实例],[可能的参数值],[可能的返回值])
|
||||||
object?[]? parameters = GetParameters(context, md);
|
object?[]? parameters = GetParameters(context,this, md);
|
||||||
object? result = (haveParameter, haveResult) switch
|
object? result = (haveParameter, haveResult) switch
|
||||||
{
|
{
|
||||||
(false, false) => Execution((Action<object>)del, instance), // 调用节点方法,返回null
|
(false, false) => Execution((Action<object>)del, instance), // 调用节点方法,返回null
|
||||||
@@ -232,33 +201,6 @@ namespace Serein.NodeFlow.Base
|
|||||||
(true, true) => Execution((Func<object, object?[]?, object?>)del, instance, parameters), // 调用节点方法,获取入参参数,返回方法忏悔类型
|
(true, true) => Execution((Func<object, object?[]?, object?>)del, instance, parameters), // 调用节点方法,获取入参参数,返回方法忏悔类型
|
||||||
};
|
};
|
||||||
|
|
||||||
//object?[]? parameters;
|
|
||||||
//object? result = null;
|
|
||||||
//if ( haveParameter )
|
|
||||||
//{
|
|
||||||
// var data = GetParameters(context, md);
|
|
||||||
|
|
||||||
// if (data[0] is Int32 count && count > 1)
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
// parameters = [instance, data];
|
|
||||||
//}
|
|
||||||
//else
|
|
||||||
//{
|
|
||||||
// parameters = [instance];
|
|
||||||
//}
|
|
||||||
|
|
||||||
//if (haveResult)
|
|
||||||
//{
|
|
||||||
// result = del.DynamicInvoke(parameters);
|
|
||||||
//}
|
|
||||||
//else
|
|
||||||
//{
|
|
||||||
// del.DynamicInvoke(parameters);
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
NextOrientation = ConnectionType.IsSucceed;
|
NextOrientation = ConnectionType.IsSucceed;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -270,13 +212,6 @@ namespace Serein.NodeFlow.Base
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 执行等待触发器的方法
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
/// <returns>节点传回数据对象</returns>
|
|
||||||
/// <exception cref="RuningException"></exception>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region 节点转换的委托类型
|
#region 节点转换的委托类型
|
||||||
@@ -304,7 +239,7 @@ namespace Serein.NodeFlow.Base
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取对应的参数数组
|
/// 获取对应的参数数组
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public object?[]? GetParameters(IDynamicContext context, MethodDetails md)
|
public static object?[]? GetParameters(IDynamicContext context, NodeModelBase nodeModel, MethodDetails md)
|
||||||
{
|
{
|
||||||
// 用正确的大小初始化参数数组
|
// 用正确的大小初始化参数数组
|
||||||
if (md.ExplicitDatas.Length == 0)
|
if (md.ExplicitDatas.Length == 0)
|
||||||
@@ -313,7 +248,7 @@ namespace Serein.NodeFlow.Base
|
|||||||
}
|
}
|
||||||
|
|
||||||
object?[]? parameters = new object[md.ExplicitDatas.Length];
|
object?[]? parameters = new object[md.ExplicitDatas.Length];
|
||||||
var flowData = PreviousNode?.FlowData; // 当前传递的数据
|
var flowData = nodeModel.PreviousNode?.FlowData; // 当前传递的数据
|
||||||
var previousDataType = flowData?.GetType();
|
var previousDataType = flowData?.GetType();
|
||||||
|
|
||||||
for (int i = 0; i < parameters.Length; i++)
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
@@ -349,7 +284,7 @@ namespace Serein.NodeFlow.Base
|
|||||||
//Type t when t == previousDataType => inputParameter, // 上下文
|
//Type t when t == previousDataType => inputParameter, // 上下文
|
||||||
Type t when t == typeof(IDynamicContext) => context, // 上下文
|
Type t when t == typeof(IDynamicContext) => context, // 上下文
|
||||||
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) => nodeModel, // 节点实体类
|
||||||
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(DateTime) => DateTime.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(),
|
||||||
@@ -387,6 +322,88 @@ namespace Serein.NodeFlow.Base
|
|||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新节点数据,并检查监视表达式
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newData"></param>
|
||||||
|
public static async Task FlowRefreshDataOrInterrupt(IDynamicContext context , NodeModelBase nodeModel, object? newData = null)
|
||||||
|
{
|
||||||
|
string guid = nodeModel.Guid;
|
||||||
|
if (newData is not null)
|
||||||
|
{
|
||||||
|
// 判断是否存在表达式
|
||||||
|
bool isInterrupt = false;
|
||||||
|
// 判断监视表达式
|
||||||
|
for (int i = 0; i < nodeModel.DebugSetting.InterruptExpression.Count && !isInterrupt; i++)
|
||||||
|
{
|
||||||
|
string? exp = nodeModel.DebugSetting.InterruptExpression[i];
|
||||||
|
isInterrupt = SereinConditionParser.To(newData, exp);
|
||||||
|
}
|
||||||
|
if (isInterrupt) // 触发中断
|
||||||
|
{
|
||||||
|
nodeModel.Interrupt();
|
||||||
|
if(TryCreateInterruptTask(context, nodeModel, out Task<CancelType>? task))
|
||||||
|
{
|
||||||
|
|
||||||
|
nodeModel.CancelInterruptCallback ??= () => context.FlowEnvironment.ChannelFlowInterrupt.TriggerSignal(guid);
|
||||||
|
var cancelType = await task!;
|
||||||
|
await Console.Out.WriteLineAsync($"[{nodeModel.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodeModel.FlowData = newData;
|
||||||
|
// 节点是否监视了数据,如果是,调用环境接口触发其相关事件。
|
||||||
|
if (nodeModel.DebugSetting.IsMonitorFlowData)
|
||||||
|
{
|
||||||
|
context.FlowEnvironment.FlowDataUpdateNotification(guid, newData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryCreateInterruptTask(IDynamicContext context, NodeModelBase currentNode, out Task<CancelType>? task)
|
||||||
|
{
|
||||||
|
bool haveTask;
|
||||||
|
Console.WriteLine($"[{currentNode.MethodDetails.MethodName}]在当前分支中断");
|
||||||
|
|
||||||
|
if (currentNode.DebugSetting.InterruptClass == InterruptClass.None)
|
||||||
|
{
|
||||||
|
haveTask = false;
|
||||||
|
task = null;
|
||||||
|
}
|
||||||
|
else if (currentNode.DebugSetting.InterruptClass == InterruptClass.Branch) // 中断当前分支
|
||||||
|
{
|
||||||
|
haveTask = true;
|
||||||
|
task = context.FlowEnvironment.ChannelFlowInterrupt.CreateChannelWithTimeoutAsync(currentNode.Guid, TimeSpan.FromSeconds(60 * 30)); // 中断30分钟
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
haveTask = false;
|
||||||
|
task = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return haveTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 释放对象
|
||||||
|
/// </summary>
|
||||||
|
public void ReleaseFlowData()
|
||||||
|
{
|
||||||
|
if (typeof(IDisposable).IsAssignableFrom(FlowData?.GetType()) && FlowData is IDisposable disposable)
|
||||||
|
{
|
||||||
|
disposable?.Dispose();
|
||||||
|
}
|
||||||
|
this.FlowData = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取节点数据
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object? GetFlowData()
|
||||||
|
{
|
||||||
|
return this.FlowData ;
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using Serein.Library.Api;
|
using Serein.Library.Api;
|
||||||
using Serein.Library.Attributes;
|
using Serein.Library.Attributes;
|
||||||
using Serein.Library.Entity;
|
using Serein.Library.Entity;
|
||||||
@@ -90,10 +91,25 @@ namespace Serein.NodeFlow
|
|||||||
public event StartNodeChangeHandler OnStartNodeChange;
|
public event StartNodeChangeHandler OnStartNodeChange;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 流程运行完成时间
|
/// 流程运行完成事件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event FlowRunCompleteHandler OnFlowRunComplete;
|
public event FlowRunCompleteHandler OnFlowRunComplete;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 被监视的对象改变事件
|
||||||
|
/// </summary>
|
||||||
|
public event MonitorObjectChangeHandler OnMonitorObjectChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点中断状态改变事件
|
||||||
|
/// </summary>
|
||||||
|
public event NodeInterruptStateChangeHandler OnNodeInterruptStateChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点触发了中断
|
||||||
|
/// </summary>
|
||||||
|
public event NodeInterruptTriggerHandler OnNodeInterruptTrigger;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
@@ -171,23 +187,25 @@ namespace Serein.NodeFlow
|
|||||||
{
|
{
|
||||||
ChannelFlowInterrupt?.CancelAllTasks();
|
ChannelFlowInterrupt?.CancelAllTasks();
|
||||||
flowStarter = new FlowStarter();
|
flowStarter = new FlowStarter();
|
||||||
List<SingleFlipflopNode> flipflopNodes = Nodes.Values.Where(it => it.MethodDetails?.MethodDynamicType == NodeType.Flipflop && it.IsStart == false)
|
var nodes = Nodes.Values.ToList();
|
||||||
.Select(it => (SingleFlipflopNode)it)
|
|
||||||
.Where(node => node is SingleFlipflopNode flipflopNode && flipflopNode.NotExitPreviousNode())
|
List<MethodDetails> initMethods;
|
||||||
.ToList();// 获取需要再运行开始之前启动的触发器节点
|
List<MethodDetails> loadingMethods;
|
||||||
var runMethodDetailess = Nodes.Values.Select(item => item.MethodDetails).ToList(); // 获取环境中所有节点的方法信息
|
List<MethodDetails> exitMethods;
|
||||||
var initMethods = MethodDetailss.Where(it => it.MethodDynamicType == NodeType.Init).ToList();
|
initMethods = MethodDetailss.Where(it => it.MethodDynamicType == NodeType.Init).ToList();
|
||||||
var loadingMethods = MethodDetailss.Where(it => it.MethodDynamicType == NodeType.Loading).ToList();
|
loadingMethods = MethodDetailss.Where(it => it.MethodDynamicType == NodeType.Loading).ToList();
|
||||||
var exitMethods = MethodDetailss.Where(it => it.MethodDynamicType == NodeType.Exit).ToList();
|
exitMethods = MethodDetailss.Where(it => it.MethodDynamicType == NodeType.Exit).ToList();
|
||||||
|
|
||||||
|
await flowStarter.RunAsync(this, nodes, initMethods, loadingMethods, exitMethods);
|
||||||
|
|
||||||
|
|
||||||
await flowStarter.RunAsync(StartNode,
|
//await flowStarter.RunAsync(StartNode,
|
||||||
this,
|
// this,
|
||||||
runMethodDetailess,
|
// runMethodDetailess,
|
||||||
initMethods,
|
// initMethods,
|
||||||
loadingMethods,
|
// loadingMethods,
|
||||||
exitMethods,
|
// exitMethods,
|
||||||
flipflopNodes);
|
// flipflopNodes);
|
||||||
|
|
||||||
if(flowStarter?.FlipFlopState == RunState.NoStart)
|
if(flowStarter?.FlipFlopState == RunState.NoStart)
|
||||||
{
|
{
|
||||||
@@ -195,19 +213,24 @@ namespace Serein.NodeFlow
|
|||||||
}
|
}
|
||||||
flowStarter = null;
|
flowStarter = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 退出
|
||||||
|
/// </summary>
|
||||||
public void Exit()
|
public void Exit()
|
||||||
{
|
{
|
||||||
foreach (var node in Nodes.Values)
|
|
||||||
{
|
|
||||||
if (typeof(IDisposable).IsAssignableFrom(node?.FlowData?.GetType()) && node.FlowData is IDisposable disposable)
|
|
||||||
{
|
|
||||||
disposable?.Dispose();
|
|
||||||
}
|
|
||||||
node!.FlowData = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChannelFlowInterrupt?.CancelAllTasks();
|
ChannelFlowInterrupt?.CancelAllTasks();
|
||||||
flowStarter?.Exit();
|
flowStarter?.Exit();
|
||||||
|
|
||||||
|
foreach (var node in Nodes.Values)
|
||||||
|
{
|
||||||
|
if(node is not null)
|
||||||
|
{
|
||||||
|
node.ReleaseFlowData(); // 退出时释放对象计数
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
OnFlowRunComplete?.Invoke(new FlowEventArgs());
|
OnFlowRunComplete?.Invoke(new FlowEventArgs());
|
||||||
|
|
||||||
GC.Collect();
|
GC.Collect();
|
||||||
@@ -443,20 +466,12 @@ namespace Serein.NodeFlow
|
|||||||
/// <exception cref="NotImplementedException"></exception>
|
/// <exception cref="NotImplementedException"></exception>
|
||||||
public void RemoteNode(string nodeGuid)
|
public void RemoteNode(string nodeGuid)
|
||||||
{
|
{
|
||||||
if (!Nodes.TryGetValue(nodeGuid, out NodeModelBase? remoteNode))
|
NodeModelBase remoteNode = GuidToModel(nodeGuid);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (remoteNode is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (remoteNode.IsStart)
|
if (remoteNode.IsStart)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 遍历所有父节点,从那些父节点中的子节点集合移除该节点
|
// 遍历所有父节点,从那些父节点中的子节点集合移除该节点
|
||||||
foreach (var pnc in remoteNode.PreviousNodes)
|
foreach (var pnc in remoteNode.PreviousNodes)
|
||||||
{
|
{
|
||||||
@@ -507,14 +522,8 @@ namespace Serein.NodeFlow
|
|||||||
public void ConnectNode(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
|
public void ConnectNode(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
|
||||||
{
|
{
|
||||||
// 获取起始节点与目标节点
|
// 获取起始节点与目标节点
|
||||||
if (!Nodes.TryGetValue(fromNodeGuid, out NodeModelBase? fromNode) || !Nodes.TryGetValue(toNodeGuid, out NodeModelBase? toNode))
|
NodeModelBase fromNode = GuidToModel(fromNodeGuid);
|
||||||
{
|
NodeModelBase toNode = GuidToModel(toNodeGuid);
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (fromNode is null || toNode is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 开始连接
|
// 开始连接
|
||||||
ConnectNode(fromNode, toNode, connectionType); // 外部调用连接方法
|
ConnectNode(fromNode, toNode, connectionType); // 外部调用连接方法
|
||||||
|
|
||||||
@@ -530,14 +539,8 @@ namespace Serein.NodeFlow
|
|||||||
public void RemoteConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
|
public void RemoteConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
|
||||||
{
|
{
|
||||||
// 获取起始节点与目标节点
|
// 获取起始节点与目标节点
|
||||||
if (!Nodes.TryGetValue(fromNodeGuid, out NodeModelBase? fromNode) || !Nodes.TryGetValue(toNodeGuid, out NodeModelBase? toNode))
|
NodeModelBase fromNode = GuidToModel(fromNodeGuid);
|
||||||
{
|
NodeModelBase toNode = GuidToModel(toNodeGuid);
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (fromNode is null || toNode is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
RemoteConnect(fromNode, toNode, connectionType);
|
RemoteConnect(fromNode, toNode, connectionType);
|
||||||
|
|
||||||
//fromNode.SuccessorNodes[connectionType].Remove(toNode);
|
//fromNode.SuccessorNodes[connectionType].Remove(toNode);
|
||||||
@@ -603,28 +606,83 @@ namespace Serein.NodeFlow
|
|||||||
/// <param name="newNodeGuid"></param>
|
/// <param name="newNodeGuid"></param>
|
||||||
public void SetStartNode(string newNodeGuid)
|
public void SetStartNode(string newNodeGuid)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(newNodeGuid))
|
NodeModelBase newStartNodeModel = GuidToModel(newNodeGuid);
|
||||||
{
|
SetStartNode(newStartNodeModel);
|
||||||
return;
|
|
||||||
}
|
//if (string.IsNullOrEmpty(newNodeGuid))
|
||||||
if (Nodes.TryGetValue(newNodeGuid, out NodeModelBase? newStartNodeModel))
|
//{
|
||||||
{
|
// return;
|
||||||
if (newStartNodeModel != null)
|
//}
|
||||||
{
|
//if (Nodes.TryGetValue(newNodeGuid, out NodeModelBase? newStartNodeModel))
|
||||||
SetStartNode(newStartNodeModel);
|
//{
|
||||||
//var oldNodeGuid = "";
|
// if (newStartNodeModel != null)
|
||||||
//if(StartNode != null)
|
// {
|
||||||
//{
|
// SetStartNode(newStartNodeModel);
|
||||||
// oldNodeGuid = StartNode.Guid;
|
// //var oldNodeGuid = "";
|
||||||
// StartNode.IsStart = false;
|
// //if(StartNode != null)
|
||||||
//}
|
// //{
|
||||||
//newStartNodeModel.IsStart = true;
|
// // oldNodeGuid = StartNode.Guid;
|
||||||
//StartNode = newStartNodeModel;
|
// // StartNode.IsStart = false;
|
||||||
//OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(oldNodeGuid, newNodeGuid));
|
// //}
|
||||||
}
|
// //newStartNodeModel.IsStart = true;
|
||||||
}
|
// //StartNode = newStartNodeModel;
|
||||||
|
// //OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(oldNodeGuid, newNodeGuid));
|
||||||
|
// }
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 中断指定节点,并指定中断等级。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeGuid">被中断的目标节点Guid</param>
|
||||||
|
/// <param name="interruptClass">中断级别</param>
|
||||||
|
/// <returns>操作是否成功</returns>
|
||||||
|
public bool NodeInterruptChange(string nodeGuid, InterruptClass interruptClass)
|
||||||
|
{
|
||||||
|
NodeModelBase nodeModel = GuidToModel(nodeGuid);
|
||||||
|
nodeModel.DebugSetting.InterruptClass = interruptClass;
|
||||||
|
OnNodeInterruptStateChange.Invoke(new NodeInterruptStateChangeEventArgs(nodeGuid, interruptClass));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 监视节点的数据
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeGuid">需要监视的节点Guid</param>
|
||||||
|
public void SetNodeFLowDataMonitorState(string nodeGuid, bool isMonitor)
|
||||||
|
{
|
||||||
|
NodeModelBase nodeModel = GuidToModel(nodeGuid);
|
||||||
|
nodeModel.DebugSetting.IsMonitorFlowData = isMonitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点数据更新通知
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeGuid"></param>
|
||||||
|
public void FlowDataUpdateNotification(string nodeGuid, object flowData)
|
||||||
|
{
|
||||||
|
OnMonitorObjectChange?.Invoke(new MonitorObjectEventArgs(nodeGuid, flowData));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Guid 转 NodeModel
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeGuid">节点Guid</param>
|
||||||
|
/// <returns>节点Model</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">无法获取节点、Guid/节点为null时报错</exception>
|
||||||
|
private NodeModelBase GuidToModel(string nodeGuid)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(nodeGuid))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("not contains - Guid没有对应节点:" + (nodeGuid));
|
||||||
|
}
|
||||||
|
if (!Nodes.TryGetValue(nodeGuid, out NodeModelBase? nodeModel) || nodeModel is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("null - Guid存在对应节点,但节点为null:" + (nodeGuid));
|
||||||
|
}
|
||||||
|
return nodeModel;
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 私有方法
|
#region 私有方法
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Serein.NodeFlow.Base;
|
|||||||
using Serein.NodeFlow.Model;
|
using Serein.NodeFlow.Model;
|
||||||
using System.ComponentModel.Design;
|
using System.ComponentModel.Design;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Xml.Linq;
|
||||||
using static Serein.Library.Utils.ChannelFlowInterrupt;
|
using static Serein.Library.Utils.ChannelFlowInterrupt;
|
||||||
|
|
||||||
namespace Serein.NodeFlow
|
namespace Serein.NodeFlow
|
||||||
@@ -46,7 +47,7 @@ namespace Serein.NodeFlow
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 控制触发器
|
/// 控制触发器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
private CancellationTokenSource _flipFlopCts = null;
|
||||||
public const string FlipFlopCtsName = "<>.FlowFlipFlopCts";
|
public const string FlipFlopCtsName = "<>.FlowFlipFlopCts";
|
||||||
|
|
||||||
public bool IsStopStart = false;
|
public bool IsStopStart = false;
|
||||||
@@ -55,22 +56,18 @@ namespace Serein.NodeFlow
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public RunState FlowState { get; private set; } = RunState.NoStart;
|
public RunState FlowState { get; private set; } = RunState.NoStart;
|
||||||
public RunState FlipFlopState { get; private set; } = RunState.NoStart;
|
public RunState FlipFlopState { get; private set; } = RunState.NoStart;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 运行时的IOC容器
|
/// 运行时的IOC容器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private ISereinIOC SereinIOC { get; } = null;
|
private ISereinIOC SereinIOC { get; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 结束运行时需要执行的方法
|
/// 结束运行时需要执行的方法
|
||||||
/// </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;
|
||||||
|
|
||||||
private void CheckStartState()
|
private void CheckStartState()
|
||||||
{
|
{
|
||||||
if (IsStopStart)
|
if (IsStopStart)
|
||||||
@@ -81,30 +78,52 @@ namespace Serein.NodeFlow
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// <summary>
|
||||||
|
// 开始运行
|
||||||
|
// </summary>
|
||||||
|
// <param name="startNode">起始节点</param>
|
||||||
|
// <param name="env">运行环境</param>
|
||||||
|
// <param name="runNodeMd">环境中已加载的所有节点方法</param>
|
||||||
|
// <param name="flipflopNodes">触发器节点</param>
|
||||||
|
// <returns></returns>
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开始运行
|
/// 开始运行
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="startNode">起始节点</param>
|
|
||||||
/// <param name="env">运行环境</param>
|
/// <param name="env">运行环境</param>
|
||||||
/// <param name="runNodeMd">环境中已加载的所有节点方法</param>
|
/// <param name="nodes">环境中已加载的所有节点</param>
|
||||||
/// <param name="flipflopNodes">触发器节点</param>
|
/// <param name="initMethods">初始化方法</param>
|
||||||
|
/// <param name="loadingMethods">加载时方法</param>
|
||||||
|
/// <param name="exitMethods">结束时方法</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task RunAsync(NodeModelBase startNode,
|
public async Task RunAsync(IFlowEnvironment env,
|
||||||
IFlowEnvironment env,
|
List<NodeModelBase> nodes,
|
||||||
List<MethodDetails> runNodeMd,
|
|
||||||
List<MethodDetails> initMethods,
|
List<MethodDetails> initMethods,
|
||||||
List<MethodDetails> loadingMethods,
|
List<MethodDetails> loadingMethods,
|
||||||
List<MethodDetails> exitMethods,
|
List<MethodDetails> exitMethods)
|
||||||
List<SingleFlipflopNode> flipflopNodes)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
FlowState = RunState.Running; // 开始运行
|
FlowState = RunState.Running; // 开始运行
|
||||||
|
NodeModelBase? startNode = nodes.FirstOrDefault(node => node.IsStart);
|
||||||
if (startNode == null) {
|
if (startNode is null) {
|
||||||
FlowState = RunState.Completion; // 不存在起点,退出流程
|
FlowState = RunState.Completion; // 不存在起点,退出流程
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region 获取所有触发器,以及已加载节点的方法信息
|
||||||
|
List<MethodDetails> runNodeMd;
|
||||||
|
List<SingleFlipflopNode> flipflopNodes;
|
||||||
|
|
||||||
|
flipflopNodes = nodes.Where(it => it.MethodDetails?.MethodDynamicType == NodeType.Flipflop && it.IsStart == false)
|
||||||
|
.Select(it => (SingleFlipflopNode)it)
|
||||||
|
.Where(node => node is SingleFlipflopNode flipflopNode && flipflopNode.NotExitPreviousNode())
|
||||||
|
.ToList();// 获取需要再运行开始之前启动的触发器节点
|
||||||
|
runNodeMd = nodes.Select(item => item.MethodDetails).ToList(); // 获取环境中所有节点的方法信息
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region 选择运行环境的上下文
|
#region 选择运行环境的上下文
|
||||||
|
|
||||||
// 判断使用哪一种流程上下文
|
// 判断使用哪一种流程上下文
|
||||||
@@ -146,7 +165,7 @@ namespace Serein.NodeFlow
|
|||||||
IsStopStart = true;
|
IsStopStart = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CheckStartState();
|
CheckStartState(); // 初始化IOC后检查状态
|
||||||
|
|
||||||
|
|
||||||
SereinIOC.Build(); // 流程启动前的初始化
|
SereinIOC.Build(); // 流程启动前的初始化
|
||||||
@@ -161,12 +180,8 @@ namespace Serein.NodeFlow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckStartState();
|
CheckStartState();// 调用节点初始化后检查状态
|
||||||
|
|
||||||
//foreach (var md in flipflopNodes.Select(it => it.MethodDetails).ToArray())
|
|
||||||
//{
|
|
||||||
// md.ActingInstance = SereinIoc.GetOrCreateServiceInstance(md.ActingInstanceType);
|
|
||||||
//}
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 检查并修正初始化、加载时、退出时方法作用的对象,保证后续不会报错(已注释)
|
#region 检查并修正初始化、加载时、退出时方法作用的对象,保证后续不会报错(已注释)
|
||||||
@@ -204,7 +219,6 @@ namespace Serein.NodeFlow
|
|||||||
#region 设置流程退出时的回调函数
|
#region 设置流程退出时的回调函数
|
||||||
ExitAction = () =>
|
ExitAction = () =>
|
||||||
{
|
{
|
||||||
|
|
||||||
SereinIOC.Run<WebServer>(web => {
|
SereinIOC.Run<WebServer>(web => {
|
||||||
web?.Stop();
|
web?.Stop();
|
||||||
});
|
});
|
||||||
@@ -214,23 +228,18 @@ namespace Serein.NodeFlow
|
|||||||
((Action<object, object?[]?>)md.MethodDelegate).Invoke(md.ActingInstance, [Context]);
|
((Action<object, object?[]?>)md.MethodDelegate).Invoke(md.ActingInstance, [Context]);
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (Context != null && Context.NodeRunCts != null && !Context.NodeRunCts.IsCancellationRequested)
|
if (_flipFlopCts != null && !_flipFlopCts.IsCancellationRequested)
|
||||||
//{
|
{
|
||||||
// Context.NodeRunCts.Cancel();
|
_flipFlopCts?.Cancel();
|
||||||
//}
|
_flipFlopCts?.Dispose();
|
||||||
|
}
|
||||||
//if (FlipFlopCts != null && !FlipFlopCts.IsCancellationRequested)
|
|
||||||
//{
|
|
||||||
// FlipFlopCts?.Cancel();
|
|
||||||
// FlipFlopCts?.Dispose();
|
|
||||||
//}
|
|
||||||
FlowState = RunState.Completion;
|
FlowState = RunState.Completion;
|
||||||
FlipFlopState = RunState.Completion;
|
FlipFlopState = RunState.Completion;
|
||||||
};
|
};
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 开始启动流程
|
#region 开始启动流程
|
||||||
CancellationTokenSource FlipFlopCts = null;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -238,8 +247,8 @@ namespace Serein.NodeFlow
|
|||||||
{
|
{
|
||||||
FlipFlopState = RunState.Running;
|
FlipFlopState = RunState.Running;
|
||||||
// 如果存在需要启动的触发器,则开始启动
|
// 如果存在需要启动的触发器,则开始启动
|
||||||
FlipFlopCts = new CancellationTokenSource();
|
_flipFlopCts = new CancellationTokenSource();
|
||||||
SereinIOC.CustomRegisterInstance(FlipFlopCtsName, FlipFlopCts,false);
|
SereinIOC.CustomRegisterInstance(FlipFlopCtsName, _flipFlopCts,false);
|
||||||
|
|
||||||
// 使用 TaskCompletionSource 创建未启动的触发器任务
|
// 使用 TaskCompletionSource 创建未启动的触发器任务
|
||||||
var tasks = flipflopNodes.Select(async node =>
|
var tasks = flipflopNodes.Select(async node =>
|
||||||
@@ -250,15 +259,13 @@ namespace Serein.NodeFlow
|
|||||||
}
|
}
|
||||||
await startNode.StartExecute(Context); // 开始运行时从起始节点开始运行
|
await startNode.StartExecute(Context); // 开始运行时从起始节点开始运行
|
||||||
// 等待结束
|
// 等待结束
|
||||||
if(FlipFlopState == RunState.Running && FlipFlopCts is not null)
|
if(FlipFlopState == RunState.Running && _flipFlopCts is not null)
|
||||||
{
|
{
|
||||||
while (!FlipFlopCts.IsCancellationRequested)
|
while (!_flipFlopCts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//FlipFlopCts?.Dispose();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -266,7 +273,6 @@ namespace Serein.NodeFlow
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
FlipFlopCts?.Dispose();
|
|
||||||
FlowState = RunState.Completion;
|
FlowState = RunState.Completion;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@@ -290,72 +296,72 @@ namespace Serein.NodeFlow
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private async Task FlipflopExecute(IFlowEnvironment flowEnvironment,SingleFlipflopNode singleFlipFlopNode)
|
private async Task FlipflopExecute(IFlowEnvironment flowEnvironment,SingleFlipflopNode singleFlipFlopNode)
|
||||||
{
|
{
|
||||||
CancellationTokenSource cts = null;
|
|
||||||
|
|
||||||
var context = new DynamicContext(SereinIOC, flowEnvironment); // 启动全局触发器时新建上下文
|
var context = new DynamicContext(SereinIOC, flowEnvironment); // 启动全局触发器时新建上下文
|
||||||
|
|
||||||
MethodDetails md = singleFlipFlopNode.MethodDetails;
|
|
||||||
var del = md.MethodDelegate;
|
|
||||||
object?[]? parameters = singleFlipFlopNode.GetParameters(context, singleFlipFlopNode.MethodDetails); // 启动全局触发器时获取入参参数
|
|
||||||
// 设置委托对象
|
|
||||||
var func = md.ExplicitDatas.Length == 0 ?
|
|
||||||
(Func<object, object, Task<IFlipflopContext>>)del :
|
|
||||||
(Func<object, object[], Task<IFlipflopContext>>)del;
|
|
||||||
|
|
||||||
bool t = md.ExplicitDatas.Length == 0;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
|
while (!_flipFlopCts.IsCancellationRequested)
|
||||||
cts = Context.SereinIoc.Get<CancellationTokenSource>(FlipFlopCtsName);
|
|
||||||
|
|
||||||
while (!cts.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
|
var newFlowData = await singleFlipFlopNode.ExecutingAsync(context);
|
||||||
singleFlipFlopNode.FlowData = await singleFlipFlopNode.ExecutingAsync(context);
|
await NodeModelBase.FlowRefreshDataOrInterrupt(context, singleFlipFlopNode, newFlowData); // 全局触发器触发后刷新该触发器的节点数据
|
||||||
if (singleFlipFlopNode.NextOrientation != ConnectionType.None)
|
if (singleFlipFlopNode.NextOrientation != ConnectionType.None)
|
||||||
{
|
{
|
||||||
var nextNodes = singleFlipFlopNode.SuccessorNodes[singleFlipFlopNode.NextOrientation];
|
var nextNodes = singleFlipFlopNode.SuccessorNodes[singleFlipFlopNode.NextOrientation];
|
||||||
for (int i = nextNodes.Count - 1; i >= 0; i--)
|
for (int i = nextNodes.Count - 1; i >= 0 && !_flipFlopCts.IsCancellationRequested; i--)
|
||||||
{
|
{
|
||||||
if (nextNodes[i].DebugSetting.IsEnable) // 排除未启用的后继节点
|
if (nextNodes[i].DebugSetting.IsEnable) // 排除未启用的后继节点
|
||||||
{
|
{
|
||||||
nextNodes[i].PreviousNode = singleFlipFlopNode;
|
nextNodes[i].PreviousNode = singleFlipFlopNode;
|
||||||
await nextNodes[i].StartExecute(context); // 执行流程节点的后继分支
|
await nextNodes[i].StartExecute(context); // 启动执行触发器后继分支的节点
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//if(t)
|
|
||||||
//{
|
|
||||||
// IFlipflopContext flipflopContext = await ((Func<object, Task<IFlipflopContext>>)del.Clone()).Invoke(md.ActingInstance);// 开始等待全局触发器的触发
|
|
||||||
// var connectionType = flipflopContext.State.ToContentType();
|
|
||||||
// if (connectionType != ConnectionType.None)
|
|
||||||
// {
|
|
||||||
// await GlobalFlipflopExecute(context, singleFlipFlopNode, connectionType, cts);
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//else
|
|
||||||
//{
|
|
||||||
// IFlipflopContext flipflopContext = await ((Func<object, object[], Task<IFlipflopContext>>)del.Clone()).Invoke(md.ActingInstance, parameters);// 开始等待全局触发器的触发
|
|
||||||
// var connectionType = flipflopContext.State.ToContentType();
|
|
||||||
// if (connectionType != ConnectionType.None)
|
|
||||||
// {
|
|
||||||
// await GlobalFlipflopExecute(context, singleFlipFlopNode, connectionType, cts);
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await Console.Out.WriteLineAsync(ex.ToString());
|
await Console.Out.WriteLineAsync(ex.ToString());
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
cts?.Cancel();
|
//MethodDetails md = singleFlipFlopNode.MethodDetails;
|
||||||
}
|
//var del = md.MethodDelegate;
|
||||||
|
//object?[]? parameters = singleFlipFlopNode.GetParameters(context, singleFlipFlopNode.MethodDetails); // 启动全局触发器时获取入参参数
|
||||||
|
//// 设置委托对象
|
||||||
|
//var func = md.ExplicitDatas.Length == 0 ?
|
||||||
|
// (Func<object, object, Task<IFlipflopContext>>)del :
|
||||||
|
// (Func<object, object[], Task<IFlipflopContext>>)del;
|
||||||
|
|
||||||
|
//if(t)
|
||||||
|
//{
|
||||||
|
// IFlipflopContext flipflopContext = await ((Func<object, Task<IFlipflopContext>>)del.Clone()).Invoke(md.ActingInstance);// 开始等待全局触发器的触发
|
||||||
|
// var connectionType = flipflopContext.State.ToContentType();
|
||||||
|
// if (connectionType != ConnectionType.None)
|
||||||
|
// {
|
||||||
|
// await GlobalFlipflopExecute(context, singleFlipFlopNode, connectionType, cts);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//else
|
||||||
|
//{
|
||||||
|
// IFlipflopContext flipflopContext = await ((Func<object, object[], Task<IFlipflopContext>>)del.Clone()).Invoke(md.ActingInstance, parameters);// 开始等待全局触发器的触发
|
||||||
|
// var connectionType = flipflopContext.State.ToContentType();
|
||||||
|
// if (connectionType != ConnectionType.None)
|
||||||
|
// {
|
||||||
|
// await GlobalFlipflopExecute(context, singleFlipFlopNode, connectionType, cts);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Exit()
|
||||||
|
{
|
||||||
|
ExitAction?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if false
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 全局触发器开始执行相关分支
|
/// 全局触发器开始执行相关分支
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -364,9 +370,10 @@ namespace Serein.NodeFlow
|
|||||||
/// <param name="connectionType">分支类型</param>
|
/// <param name="connectionType">分支类型</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task GlobalFlipflopExecute(IDynamicContext context, SingleFlipflopNode singleFlipFlopNode,
|
public async Task GlobalFlipflopExecute(IDynamicContext context, SingleFlipflopNode singleFlipFlopNode,
|
||||||
ConnectionType connectionType, CancellationTokenSource cts)
|
|
||||||
|
ConnectionType connectionType, CancellationTokenSource cts)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
bool skip = true;
|
bool skip = true;
|
||||||
Stack<NodeModelBase> stack = new Stack<NodeModelBase>();
|
Stack<NodeModelBase> stack = new Stack<NodeModelBase>();
|
||||||
@@ -400,9 +407,9 @@ namespace Serein.NodeFlow
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
currentNode.FlowData = await currentNode.ExecutingAsync(context);
|
currentNode.FlowData = await currentNode.ExecutingAsync(context);
|
||||||
|
|
||||||
|
|
||||||
if (currentNode.NextOrientation == ConnectionType.None)
|
|
||||||
|
if (currentNode.NextOrientation == ConnectionType.None)
|
||||||
{
|
{
|
||||||
break; // 不再执行
|
break; // 不再执行
|
||||||
}
|
}
|
||||||
@@ -418,13 +425,13 @@ namespace Serein.NodeFlow
|
|||||||
nextNodes[i].PreviousNode = currentNode;
|
nextNodes[i].PreviousNode = currentNode;
|
||||||
stack.Push(nextNodes[i]);
|
stack.Push(nextNodes[i]);
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
}
|
#endif
|
||||||
|
|
||||||
|
|
||||||
public void Exit()
|
|
||||||
{
|
|
||||||
ExitAction?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace Serein.NodeFlow.Model
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Task.FromResult( PreviousNode?.FlowData);
|
return Task.FromResult( PreviousNode?.GetFlowData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Serein.NodeFlow.Model
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = PreviousNode?.FlowData;
|
result = PreviousNode?.GetFlowData();
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Serein.NodeFlow.Model
|
|||||||
//public override async Task<object?> Executing(IDynamicContext context)
|
//public override async Task<object?> Executing(IDynamicContext context)
|
||||||
public override Task<object?> ExecutingAsync(IDynamicContext context)
|
public override Task<object?> ExecutingAsync(IDynamicContext context)
|
||||||
{
|
{
|
||||||
var data = PreviousNode?.FlowData;
|
var data = PreviousNode?.GetFlowData();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -34,7 +34,7 @@ namespace Serein.NodeFlow.Model
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = PreviousNode?.FlowData;
|
result = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
NextOrientation = ConnectionType.IsSucceed;
|
NextOrientation = ConnectionType.IsSucceed;
|
||||||
@@ -44,7 +44,7 @@ namespace Serein.NodeFlow.Model
|
|||||||
{
|
{
|
||||||
NextOrientation = ConnectionType.IsError;
|
NextOrientation = ConnectionType.IsError;
|
||||||
RuningException = ex;
|
RuningException = ex;
|
||||||
return Task.FromResult(PreviousNode?.FlowData);
|
return Task.FromResult(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,12 +22,11 @@ namespace Serein.NodeFlow.Model
|
|||||||
public override async Task<object?> ExecutingAsync(IDynamicContext context)
|
public override async Task<object?> ExecutingAsync(IDynamicContext context)
|
||||||
{
|
{
|
||||||
#region 执行前中断
|
#region 执行前中断
|
||||||
if (DebugSetting.IsInterrupt && TryCreateInterruptTask(context, this, out Task<CancelType>? task)) // 执行触发前
|
if (DebugSetting.InterruptClass != InterruptClass.None && TryCreateInterruptTask(context, this, out Task<CancelType>? task)) // 执行触发前
|
||||||
{
|
{
|
||||||
string guid = this.Guid.ToString();
|
string guid = this.Guid.ToString();
|
||||||
this.CancelInterruptCallback ??= () => context.FlowEnvironment.ChannelFlowInterrupt.TriggerSignal(guid);
|
this.CancelInterruptCallback ??= () => context.FlowEnvironment.ChannelFlowInterrupt.TriggerSignal(guid);
|
||||||
var cancelType = await task!;
|
var cancelType = await task!;
|
||||||
task?.ToString();
|
|
||||||
await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支");
|
await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支");
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@@ -42,24 +41,9 @@ namespace Serein.NodeFlow.Model
|
|||||||
Task<IFlipflopContext> flipflopTask = md.ExplicitDatas.Length switch
|
Task<IFlipflopContext> flipflopTask = md.ExplicitDatas.Length switch
|
||||||
{
|
{
|
||||||
0 => ((Func<object, Task<IFlipflopContext>>)del).Invoke(md.ActingInstance),
|
0 => ((Func<object, Task<IFlipflopContext>>)del).Invoke(md.ActingInstance),
|
||||||
_ => ((Func<object, object?[]?, Task<IFlipflopContext>>)del).Invoke(md.ActingInstance, GetParameters(context, md)), // 执行流程中的触发器方法时获取入参参数
|
_ => ((Func<object, object?[]?, Task<IFlipflopContext>>)del).Invoke(md.ActingInstance, GetParameters(context, this, md)), // 执行流程中的触发器方法时获取入参参数
|
||||||
};
|
};
|
||||||
//object?[]? parameters;
|
|
||||||
//object? result = null;
|
|
||||||
//if (haveParameter)
|
|
||||||
//{
|
|
||||||
// var data = GetParameters(context, md);
|
|
||||||
// parameters = [instance, data];
|
|
||||||
//}
|
|
||||||
//else
|
|
||||||
//{
|
|
||||||
// parameters = [instance];
|
|
||||||
//}
|
|
||||||
//flipflopTask = del.DynamicInvoke(parameters) as Task<IFlipflopContext>;
|
|
||||||
//if (flipflopTask == null)
|
|
||||||
//{
|
|
||||||
// throw new FlipflopException(base.MethodDetails.MethodName + "触发器返回值非 Task<IFlipflopContext> 类型");
|
|
||||||
//}
|
|
||||||
IFlipflopContext flipflopContext = (await flipflopTask) ?? throw new FlipflopException("没有返回上下文");
|
IFlipflopContext flipflopContext = (await flipflopTask) ?? throw new FlipflopException("没有返回上下文");
|
||||||
NextOrientation = flipflopContext.State.ToContentType();
|
NextOrientation = flipflopContext.State.ToContentType();
|
||||||
if(flipflopContext.TriggerData is null || flipflopContext.TriggerData.Type == Library.NodeFlow.Tool.TriggerType.Overtime)
|
if(flipflopContext.TriggerData is null || flipflopContext.TriggerData.Type == Library.NodeFlow.Tool.TriggerType.Overtime)
|
||||||
@@ -72,7 +56,7 @@ namespace Serein.NodeFlow.Model
|
|||||||
{
|
{
|
||||||
NextOrientation = ConnectionType.None;
|
NextOrientation = ConnectionType.None;
|
||||||
RuningException = ex;
|
RuningException = ex;
|
||||||
throw;
|
return null;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="clr-namespace:Serein.WorkBench"
|
xmlns:local="clr-namespace:Serein.WorkBench"
|
||||||
xmlns:custom="clr-namespace:Serein.WorkBench.Node.View"
|
xmlns:custom="clr-namespace:Serein.WorkBench.Node.View"
|
||||||
|
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
|
||||||
Title="Dynamic Node Flow" Height="700" Width="1200"
|
Title="Dynamic Node Flow" Height="700" Width="1200"
|
||||||
AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver"
|
AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver"
|
||||||
Loaded="Window_Loaded"
|
Loaded="Window_Loaded"
|
||||||
@@ -38,37 +39,26 @@
|
|||||||
<RowDefinition Height="2*"></RowDefinition>
|
<RowDefinition Height="2*"></RowDefinition>
|
||||||
<RowDefinition Height="*"></RowDefinition>
|
<RowDefinition Height="*"></RowDefinition>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<!--<Button Grid.Row="0" Content="Button 1"></Button>
|
|
||||||
<Button Grid.Row="1" Content="Button 2"></Button>
|
|
||||||
<Button Grid.Row="2" Content="Button 3"></Button>
|
|
||||||
<Button Grid.Row="3" Content="Button 4"></Button>-->
|
|
||||||
<!--DockPanel.Dock="Top"-->
|
|
||||||
|
|
||||||
<Grid Margin="2,2,2,5">
|
<Grid Margin="2,2,2,5" Grid.Row="0" >
|
||||||
<Button Grid.Row="0" Content="保存项目" Click="ButtonSaveFile_Click" HorizontalAlignment="Left" Margin="5,5,5,5"/>
|
<Button Grid.Row="0" Content="保存项目" Click="ButtonSaveFile_Click" HorizontalAlignment="Left" Margin="5,5,5,5"/>
|
||||||
<!--<Button Grid.Row="0" Content="卸载清空" Click="UnloadAllButton_Click" HorizontalAlignment="Right" Margin="5,5,5,5"/>-->
|
<!--<Button Grid.Row="0" Content="卸载清空" Click="UnloadAllButton_Click" HorizontalAlignment="Right" Margin="5,5,5,5"/>-->
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto">
|
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto">
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<!--<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
</Grid.ColumnDefinitions>-->
|
|
||||||
|
|
||||||
<custom:ExpOpNodeControl x:Name="ExpOpNodeControl" Margin="10" AllowDrop="True" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
|
<custom:ExpOpNodeControl x:Name="ExpOpNodeControl" Margin="10" AllowDrop="True" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
|
||||||
<custom:ConditionNodeControl x:Name="ConditionNodeControl" Margin="10" AllowDrop="True" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
|
<custom:ConditionNodeControl x:Name="ConditionNodeControl" Margin="10" AllowDrop="True" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
|
||||||
<custom:ConditionRegionControl x:Name="ConditionRegionControl" Margin="10" AllowDrop="True" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
|
<custom:ConditionRegionControl x:Name="ConditionRegionControl" Margin="10" AllowDrop="True" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
|
||||||
<!--<custom:ActionRegionControl x:Name="ActionRegionControl" Grid.Column="1" Margin="10" AllowDrop="True" Drop="ActionRegionControl_Drop" PreviewMouseMove="RegionControl_PreviewMouseMove"/>-->
|
|
||||||
<!--<TextBlock Text="触发器" Grid.Column="2"/>-->
|
|
||||||
<!--<custom:StateRegionControl x:Name="StateRegionControl" Grid.Column="2" Margin="10" AllowDrop="True" Drop="StateRegionControl_Drop" PreviewMouseMove="RegionControl_PreviewMouseMove"/>-->
|
|
||||||
<!--</Grid>-->
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto">
|
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto" MaxHeight="400">
|
||||||
<StackPanel x:Name="DllStackPanel" Margin="5"/>
|
<StackPanel x:Name="DllStackPanel" Margin="5"/>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
<Grid Grid.Row="3" >
|
||||||
|
|
||||||
|
<themes:ObjectViewerControl x:Name="ObjectViewer"/>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ using System.Windows.Media.Animation;
|
|||||||
using System.Windows.Media.Media3D;
|
using System.Windows.Media.Media3D;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
using System.Xml.Linq;
|
||||||
using DataObject = System.Windows.DataObject;
|
using DataObject = System.Windows.DataObject;
|
||||||
|
|
||||||
namespace Serein.WorkBench
|
namespace Serein.WorkBench
|
||||||
@@ -62,10 +63,15 @@ namespace Serein.WorkBench
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 存储所有与节点有关的控件
|
/// 存储所有与节点有关的控件
|
||||||
|
/// 任何情景下都尽量避免直接操作 ViewModel 中的 NodeModel 节点,
|
||||||
|
/// 而是应该调用 FlowEnvironment 提供接口进行操作,
|
||||||
|
/// 因为 Workbench 应该更加关注UI视觉效果,而非直接干扰流程环境运行的逻辑。
|
||||||
|
/// 之所以暴露 NodeModel 属性,因为有些场景下不可避免的需要直接获取节点的属性。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Dictionary<string, NodeControlBase> NodeControls { get; } = [];
|
private Dictionary<string, NodeControlBase> NodeControls { get; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 存储所有的连接
|
/// 存储所有的连接。考虑集成在运行环境中。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<Connection> Connections { get; } = [];
|
private List<Connection> Connections { get; } = [];
|
||||||
|
|
||||||
@@ -174,9 +180,14 @@ namespace Serein.WorkBench
|
|||||||
FlowEnvironment.OnNodeRemote += FlowEnvironment_NodeRemoteEvent;
|
FlowEnvironment.OnNodeRemote += FlowEnvironment_NodeRemoteEvent;
|
||||||
FlowEnvironment.OnFlowRunComplete += FlowEnvironment_OnFlowRunComplete;
|
FlowEnvironment.OnFlowRunComplete += FlowEnvironment_OnFlowRunComplete;
|
||||||
|
|
||||||
|
|
||||||
|
FlowEnvironment.OnMonitorObjectChange += FlowEnvironment_OnMonitorObjectChange;
|
||||||
|
FlowEnvironment.OnNodeInterruptStateChange += FlowEnvironment_OnNodeInterruptStateChange;
|
||||||
|
FlowEnvironment.OnNodeInterruptTrigger += FlowEnvironment_OnNodeInterruptTrigger;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void InitUI()
|
private void InitUI()
|
||||||
{
|
{
|
||||||
@@ -191,13 +202,6 @@ namespace Serein.WorkBench
|
|||||||
//FlowChartCanvas.RenderTransformOrigin = new Point(0.5, 0.5);
|
//FlowChartCanvas.RenderTransformOrigin = new Point(0.5, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
//private void ButtonReflushCanvasConfig_Click(object sender, RoutedEventArgs e)
|
|
||||||
//{
|
|
||||||
// scaleTransform.ScaleX = 1;
|
|
||||||
// scaleTransform.ScaleY = 1;
|
|
||||||
// translateTransform.X = 0;
|
|
||||||
// translateTransform.Y = 0;
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
#region 窗体加载方法
|
#region 窗体加载方法
|
||||||
@@ -295,41 +299,6 @@ namespace Serein.WorkBench
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 运行环境成功加载了节点,需要在画布上创建节点控件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="nodeInfo"></param>
|
|
||||||
/// <param name="methodDetailss"></param>
|
|
||||||
//private void FlowEnvironment_NodeLoadEvent(LoadNodeEventArgs eventArgs)
|
|
||||||
//{
|
|
||||||
// if (!eventArgs.IsSucceed)
|
|
||||||
// {
|
|
||||||
// MessageBox.Show(eventArgs.ErrorTips);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// NodeInfo nodeInfo = eventArgs.NodeInfo;
|
|
||||||
// MethodDetails methodDetailss = eventArgs.MethodDetailss;
|
|
||||||
|
|
||||||
// // 创建对应的实例(包含NodeModel,NodeControl,NodeControlViewModel)
|
|
||||||
// NodeControlBase? nodeControl = CreateNodeControlOfNodeInfo(nodeInfo, methodDetailss);
|
|
||||||
// if (nodeControl == null)
|
|
||||||
// {
|
|
||||||
// WriteLog($"无法为节点类型创建节点控件: {nodeInfo.MethodName}\r\n");
|
|
||||||
// return;
|
|
||||||
// // ConfigureNodeControl(nodeInfo, nodeControl, nodeControls, regionControls);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 判断是否属于区域控件,如果是,则加载区域子项
|
|
||||||
// // if (nodeControl is ActionRegionControl || nodeControl is ConditionRegionControl)
|
|
||||||
// // {
|
|
||||||
// // AddNodeControlInRegeionControl(nodeControl, nodeInfo.ChildNodes);
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// NodeControls.TryAdd(nodeInfo.Guid, nodeControl); // 存放对应的控件
|
|
||||||
// PlaceNodeOnCanvas(nodeControl, nodeInfo.Position.X, nodeInfo.Position.Y); // 配置节点,并放置在画布上
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 节点连接关系变更
|
/// 节点连接关系变更
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -342,10 +311,8 @@ namespace Serein.WorkBench
|
|||||||
{
|
{
|
||||||
string fromNodeGuid = eventArgs.FromNodeGuid;
|
string fromNodeGuid = eventArgs.FromNodeGuid;
|
||||||
string toNodeGuid = eventArgs.ToNodeGuid;
|
string toNodeGuid = eventArgs.ToNodeGuid;
|
||||||
if (!NodeControls.TryGetValue(fromNodeGuid, out var fromNode) || !NodeControls.TryGetValue(toNodeGuid, out var toNode))
|
NodeControlBase fromNode = GuidToControl(fromNodeGuid);
|
||||||
{
|
NodeControlBase toNode = GuidToControl(toNodeGuid);
|
||||||
return;
|
|
||||||
}
|
|
||||||
ConnectionType connectionType = eventArgs.ConnectionType;
|
ConnectionType connectionType = eventArgs.ConnectionType;
|
||||||
if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create)
|
if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create)
|
||||||
{
|
{
|
||||||
@@ -375,7 +342,7 @@ namespace Serein.WorkBench
|
|||||||
.ToList();
|
.ToList();
|
||||||
foreach (var connection in removeConnections)
|
foreach (var connection in removeConnections)
|
||||||
{
|
{
|
||||||
connection.RemoveFromCanvas(FlowChartCanvas);
|
connection.RemoveFromCanvas();
|
||||||
Connections.Remove(connection);
|
Connections.Remove(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -389,14 +356,7 @@ namespace Serein.WorkBench
|
|||||||
private void FlowEnvironment_NodeRemoteEvent(NodeRemoteEventArgs eventArgs)
|
private void FlowEnvironment_NodeRemoteEvent(NodeRemoteEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
var nodeGuid = eventArgs.NodeGuid;
|
var nodeGuid = eventArgs.NodeGuid;
|
||||||
if (!NodeControls.TryGetValue(nodeGuid, out NodeControlBase nodeControl))
|
NodeControlBase nodeControl = GuidToControl(nodeGuid);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(nodeControl is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (selectNodeControls.Count > 0)
|
if (selectNodeControls.Count > 0)
|
||||||
{
|
{
|
||||||
if (selectNodeControls.Contains(nodeControl))
|
if (selectNodeControls.Contains(nodeControl))
|
||||||
@@ -479,23 +439,13 @@ namespace Serein.WorkBench
|
|||||||
{
|
{
|
||||||
string oldNodeGuid = eventArgs.OldNodeGuid;
|
string oldNodeGuid = eventArgs.OldNodeGuid;
|
||||||
string newNodeGuid = eventArgs.NewNodeGuid;
|
string newNodeGuid = eventArgs.NewNodeGuid;
|
||||||
if (!NodeControls.TryGetValue(newNodeGuid, out var newStartNodeControl))
|
NodeControlBase newStartNodeControl = GuidToControl(newNodeGuid);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (newStartNodeControl == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrEmpty(oldNodeGuid))
|
if (!string.IsNullOrEmpty(oldNodeGuid))
|
||||||
{
|
{
|
||||||
NodeControls.TryGetValue(oldNodeGuid, out var oldStartNodeControl);
|
NodeControlBase oldStartNodeControl = GuidToControl(oldNodeGuid);
|
||||||
if (oldStartNodeControl != null)
|
oldStartNodeControl.BorderBrush = Brushes.Black;
|
||||||
{
|
oldStartNodeControl.BorderThickness = new Thickness(0);
|
||||||
oldStartNodeControl.BorderBrush = Brushes.Black;
|
|
||||||
oldStartNodeControl.BorderThickness = new Thickness(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newStartNodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"));
|
newStartNodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"));
|
||||||
@@ -504,10 +454,89 @@ namespace Serein.WorkBench
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 被监视的对象发生改变(节点执行了一次)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventArgs"></param>
|
||||||
|
private void FlowEnvironment_OnMonitorObjectChange(MonitorObjectEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
string nodeGuid = eventArgs.NodeGuid;
|
||||||
|
if (string.IsNullOrEmpty(ObjectViewer.NodeGuid)) // 如果没有加载过
|
||||||
|
{
|
||||||
|
ObjectViewer.NodeGuid = nodeGuid;
|
||||||
|
ObjectViewer.LoadObjectInformation(eventArgs.NewData); // 加载节点
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 加载过,如果显示的对象来源并非同一个节点,则停止监听之前的节点
|
||||||
|
if (!ObjectViewer.NodeGuid.Equals(nodeGuid))
|
||||||
|
{
|
||||||
|
FlowEnvironment.SetNodeFLowDataMonitorState(ObjectViewer.NodeGuid, false);
|
||||||
|
}
|
||||||
|
ObjectViewer.RefreshObjectTree(eventArgs.NewData);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点中断状态改变。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventArgs"></param>
|
||||||
|
private void FlowEnvironment_OnNodeInterruptStateChange(NodeInterruptStateChangeEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
string nodeGuid = eventArgs.NodeGuid;
|
||||||
|
NodeControlBase nodeControl = GuidToControl(nodeGuid);
|
||||||
|
if (eventArgs.Class == InterruptClass.None)
|
||||||
|
{
|
||||||
|
nodeControl.ViewModel.IsInterrupt = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nodeControl.ViewModel.IsInterrupt = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点触发了中断
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventArgs"></param>
|
||||||
|
/// <exception cref="NotImplementedException"></exception>
|
||||||
|
private void FlowEnvironment_OnNodeInterruptTrigger(NodeInterruptTriggerEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
string nodeGuid = eventArgs.NodeGuid;
|
||||||
|
NodeControlBase nodeControl = GuidToControl(nodeGuid);
|
||||||
|
|
||||||
|
Console.WriteLine("节点触发了中断");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Guid 转 NodeControl
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeGuid">节点Guid</param>
|
||||||
|
/// <returns>节点Model</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">无法获取节点、Guid/节点为null时报错</exception>
|
||||||
|
private NodeControlBase GuidToControl(string nodeGuid)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(nodeGuid))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("not contains - Guid没有对应节点:" + (nodeGuid));
|
||||||
|
}
|
||||||
|
if (!NodeControls.TryGetValue(nodeGuid, out NodeControlBase? nodeControl) || nodeControl is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("null - Guid存在对应节点,但节点为null:" + (nodeGuid));
|
||||||
|
}
|
||||||
|
return nodeControl;
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region 加载项目文件后触发事件相关方法
|
#region 加载项目文件后触发事件相关方法
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 运行环节加载了项目文件,需要创建节点控件
|
/// 运行环节加载了项目文件,需要创建节点控件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -634,7 +663,7 @@ namespace Serein.WorkBench
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 配置节点右键菜单
|
/// 配置节点右键菜单
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="nodeControl"></param>
|
/// <param name="nodeControl"><para> 任何情景下都尽量避免直接操作 ViewModel 中的 NodeModel 节点,而是应该调用 FlowEnvironment 提供接口进行操作。</para> 因为 Workbench 应该更加关注UI视觉效果,而非直接干扰流程环境运行的逻辑。<para> 之所以暴露 NodeModel 属性,因为有些场景下不可避免的需要直接获取节点的属性。</para> </param>
|
||||||
private void ConfigureContextMenu(NodeControlBase nodeControl)
|
private void ConfigureContextMenu(NodeControlBase nodeControl)
|
||||||
{
|
{
|
||||||
var contextMenu = new ContextMenu();
|
var contextMenu = new ContextMenu();
|
||||||
@@ -650,29 +679,42 @@ namespace Serein.WorkBench
|
|||||||
}
|
}
|
||||||
var nodeGuid = nodeControl?.ViewModel?.Node?.Guid;
|
var nodeGuid = nodeControl?.ViewModel?.Node?.Guid;
|
||||||
|
|
||||||
MenuItem? debugMenu = null;
|
#region 右键菜单功能 - 中断
|
||||||
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) =>
|
||||||
|
{
|
||||||
|
if ((s is MenuItem menuItem) && menuItem is not null)
|
||||||
|
{
|
||||||
|
if (nodeControl?.ViewModel?.Node?.DebugSetting?.InterruptClass == InterruptClass.None)
|
||||||
|
{
|
||||||
|
FlowEnvironment.NodeInterruptChange(nodeGuid, InterruptClass.Branch);
|
||||||
|
|
||||||
|
menuItem.Header = "取消中断";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FlowEnvironment.NodeInterruptChange(nodeGuid, InterruptClass.None);
|
||||||
|
menuItem.Header = "在此中断";
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
contextMenu.Items.Add(CreateMenuItem("查看数据", (s, e) =>
|
||||||
|
{
|
||||||
|
var node = nodeControl?.ViewModel?.Node;
|
||||||
|
if(node is not null)
|
||||||
|
{
|
||||||
|
FlowEnvironment.SetNodeFLowDataMonitorState(node.Guid, true); // 通知环境,该节点的数据更新后需要传到UI
|
||||||
|
}
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid)));
|
contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid)));
|
||||||
contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoteNode(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.IsSucceed)));
|
||||||
contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsFail)));
|
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.IsError)));
|
||||||
@@ -680,20 +722,40 @@ namespace Serein.WorkBench
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region 右键菜单功能 - 控件对齐
|
||||||
|
|
||||||
var AvoidMenu = new MenuItem();
|
var AvoidMenu = new MenuItem();
|
||||||
//AvoidMenu.Items.Add(CreateMenuItem("水平对齐", (s, e) => AlignHorizontallyAvoidOverlap(selectNodeControls)));
|
AvoidMenu.Items.Add(CreateMenuItem("群组对齐", (s, e) =>
|
||||||
//AvoidMenu.Items.Add(CreateMenuItem("垂直对齐", (s, e) => VerticalAlignAvoidOverlap(selectNodeControls)));
|
{
|
||||||
AvoidMenu.Items.Add(CreateMenuItem("群组对齐", (s, e) => {
|
AlignControlsWithGrouping(selectNodeControls, AlignMode.Grouping);
|
||||||
AlignControlsWithGrouping(selectNodeControls);
|
|
||||||
UpdateConnectedLines();
|
|
||||||
}));
|
}));
|
||||||
AvoidMenu.Items.Add(CreateMenuItem("规划对齐", (s, e) =>
|
AvoidMenu.Items.Add(CreateMenuItem("规划对齐", (s, e) =>
|
||||||
{
|
{
|
||||||
AlignControlsWithDynamicProgramming(selectNodeControls);
|
AlignControlsWithGrouping(selectNodeControls, AlignMode.Planning);
|
||||||
UpdateConnectedLines();
|
|
||||||
}));
|
}));
|
||||||
|
AvoidMenu.Items.Add(CreateMenuItem("水平中心对齐", (s, e) =>
|
||||||
|
{
|
||||||
|
AlignControlsWithGrouping(selectNodeControls, AlignMode.HorizontalCenter);
|
||||||
|
}));
|
||||||
|
AvoidMenu.Items.Add(CreateMenuItem("垂直中心对齐 ", (s, e) =>
|
||||||
|
{
|
||||||
|
AlignControlsWithGrouping(selectNodeControls, AlignMode.VerticalCenter);
|
||||||
|
}));
|
||||||
|
|
||||||
|
AvoidMenu.Items.Add(CreateMenuItem("垂直对齐时水平斜分布", (s, e) =>
|
||||||
|
{
|
||||||
|
AlignControlsWithGrouping(selectNodeControls, AlignMode.Vertical);
|
||||||
|
}));
|
||||||
|
AvoidMenu.Items.Add(CreateMenuItem("水平对齐时垂直斜分布", (s, e) =>
|
||||||
|
{
|
||||||
|
AlignControlsWithGrouping(selectNodeControls, AlignMode.Horizontal);
|
||||||
|
}));
|
||||||
|
|
||||||
AvoidMenu.Header = "对齐";
|
AvoidMenu.Header = "对齐";
|
||||||
contextMenu.Items.Add(AvoidMenu);
|
contextMenu.Items.Add(AvoidMenu);
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
nodeControl.ContextMenu = contextMenu;
|
nodeControl.ContextMenu = contextMenu;
|
||||||
}
|
}
|
||||||
@@ -747,6 +809,21 @@ namespace Serein.WorkBench
|
|||||||
Console.WriteLine(ex);
|
Console.WriteLine(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//private void DisplayFlowDataTreeViewer(object @object)
|
||||||
|
//{
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// var typeViewerWindow = new ObjectViewerWindow();
|
||||||
|
// typeViewerWindow.LoadObjectInformation(@object);
|
||||||
|
// typeViewerWindow.Show();
|
||||||
|
// }
|
||||||
|
// catch (Exception ex)
|
||||||
|
// {
|
||||||
|
// Console.WriteLine(ex);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 拖拽DLL文件到左侧功能区,加载相关节点清单
|
#region 拖拽DLL文件到左侧功能区,加载相关节点清单
|
||||||
@@ -1205,7 +1282,9 @@ namespace Serein.WorkBench
|
|||||||
{
|
{
|
||||||
if (connection.Start == block || connection.End == block)
|
if (connection.Start == block || connection.End == block)
|
||||||
{
|
{
|
||||||
BezierLineDrawer.UpdateBezierLine(FlowChartCanvas, connection.Start, connection.End, connection.BezierPath, connection.ArrowPath);
|
connection.Refresh();
|
||||||
|
//connection.RemoveFromCanvas();
|
||||||
|
//BezierLineDrawer.UpdateBezierLine(FlowChartCanvas, connection.Start, connection.End, connection.BezierPath, connection.ArrowPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1620,10 +1699,14 @@ namespace Serein.WorkBench
|
|||||||
//{
|
//{
|
||||||
// UpdateConnections(nodeControl);
|
// UpdateConnections(nodeControl);
|
||||||
//}
|
//}
|
||||||
foreach (var line in Connections)
|
this.Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
line.Refresh();
|
foreach (var line in Connections)
|
||||||
}
|
{
|
||||||
|
line.Refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1787,6 +1870,119 @@ namespace Serein.WorkBench
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
public enum AlignMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 水平对齐
|
||||||
|
/// </summary>
|
||||||
|
Horizontal,
|
||||||
|
/// <summary>
|
||||||
|
/// 垂直对齐
|
||||||
|
/// </summary>
|
||||||
|
Vertical,
|
||||||
|
/// <summary>
|
||||||
|
/// 水平中心对齐
|
||||||
|
/// </summary>
|
||||||
|
HorizontalCenter,
|
||||||
|
/// <summary>
|
||||||
|
/// 垂直中心对齐
|
||||||
|
/// </summary>
|
||||||
|
VerticalCenter,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 规划对齐
|
||||||
|
/// </summary>
|
||||||
|
Planning,
|
||||||
|
/// <summary>
|
||||||
|
/// 群组对齐
|
||||||
|
/// </summary>
|
||||||
|
Grouping,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void AlignControlsWithGrouping(List<NodeControlBase> selectNodeControls, AlignMode alignMode, double proximityThreshold = 50, double spacing = 10)
|
||||||
|
{
|
||||||
|
if (selectNodeControls == null || selectNodeControls.Count < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (alignMode)
|
||||||
|
{
|
||||||
|
case AlignMode.Horizontal:
|
||||||
|
AlignHorizontally(selectNodeControls, spacing);// AlignToCenter
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AlignMode.Vertical:
|
||||||
|
|
||||||
|
AlignVertically(selectNodeControls, spacing);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AlignMode.HorizontalCenter:
|
||||||
|
AlignToCenter(selectNodeControls, isHorizontal: false, spacing);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AlignMode.VerticalCenter:
|
||||||
|
AlignToCenter(selectNodeControls, isHorizontal: true, spacing);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AlignMode.Planning:
|
||||||
|
AlignControlsWithDynamicProgramming(selectNodeControls, spacing);
|
||||||
|
break;
|
||||||
|
case AlignMode.Grouping:
|
||||||
|
AlignControlsWithGrouping(selectNodeControls, proximityThreshold, spacing);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 垂直对齐并避免重叠
|
||||||
|
private void AlignHorizontally(List<NodeControlBase> controls, double spacing)
|
||||||
|
{
|
||||||
|
double avgY = controls.Average(c => Canvas.GetTop(c)); // 计算Y坐标平均值
|
||||||
|
double currentY = avgY;
|
||||||
|
|
||||||
|
foreach (var control in controls.OrderBy(c => Canvas.GetTop(c))) // 按Y坐标排序对齐
|
||||||
|
{
|
||||||
|
Canvas.SetTop(control, currentY);
|
||||||
|
currentY += control.ActualHeight + spacing; // 保证控件之间有足够的垂直间距
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 水平对齐并避免重叠
|
||||||
|
private void AlignVertically(List<NodeControlBase> controls, double spacing)
|
||||||
|
{
|
||||||
|
double avgX = controls.Average(c => Canvas.GetLeft(c)); // 计算X坐标平均值
|
||||||
|
double currentX = avgX;
|
||||||
|
|
||||||
|
foreach (var control in controls.OrderBy(c => Canvas.GetLeft(c))) // 按X坐标排序对齐
|
||||||
|
{
|
||||||
|
Canvas.SetLeft(control, currentX);
|
||||||
|
currentX += control.ActualWidth + spacing; // 保证控件之间有足够的水平间距
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按中心点对齐
|
||||||
|
private void AlignToCenter(List<NodeControlBase> controls, bool isHorizontal, double spacing)
|
||||||
|
{
|
||||||
|
double avgCenter = isHorizontal
|
||||||
|
? controls.Average(c => Canvas.GetLeft(c) + c.ActualWidth / 2) // 水平中心点
|
||||||
|
: controls.Average(c => Canvas.GetTop(c) + c.ActualHeight / 2); // 垂直中心点
|
||||||
|
|
||||||
|
foreach (var control in controls)
|
||||||
|
{
|
||||||
|
if (isHorizontal)
|
||||||
|
{
|
||||||
|
double left = avgCenter - control.ActualWidth / 2;
|
||||||
|
Canvas.SetLeft(control, left);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double top = avgCenter - control.ActualHeight / 2;
|
||||||
|
Canvas.SetTop(control, top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 窗体静态方法
|
#region 窗体静态方法
|
||||||
@@ -1936,6 +2132,7 @@ namespace Serein.WorkBench
|
|||||||
private void ButtonDebugFlipflopNode_Click(object sender, RoutedEventArgs e)
|
private void ButtonDebugFlipflopNode_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
FlowEnvironment?.Exit(); // 在运行平台上点击了退出
|
FlowEnvironment?.Exit(); // 在运行平台上点击了退出
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -2079,19 +2276,19 @@ namespace Serein.WorkBench
|
|||||||
public static Connection Draw(Canvas canvas, Connection connection)
|
public static Connection Draw(Canvas canvas, Connection connection)
|
||||||
{
|
{
|
||||||
connection.Canvas = canvas;
|
connection.Canvas = canvas;
|
||||||
UpdateBezierLine(canvas, connection);
|
UpdateBezierLineInDragging(canvas, connection);
|
||||||
//MakeDraggable(canvas, connection, connection.Start);
|
//MakeDraggable(canvas, connection, connection.Start);
|
||||||
//MakeDraggable(canvas, connection, connection.End);
|
//MakeDraggable(canvas, connection, connection.End);
|
||||||
|
|
||||||
if (connection.BezierPath == null)
|
if (connection.BezierPath == null)
|
||||||
{
|
{
|
||||||
connection.BezierPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 };
|
connection.BezierPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetLineColor(connection.Type), StrokeThickness = 1 };
|
||||||
Canvas.SetZIndex(connection.BezierPath, -1);
|
Canvas.SetZIndex(connection.BezierPath, -1);
|
||||||
canvas.Children.Add(connection.BezierPath);
|
canvas.Children.Add(connection.BezierPath);
|
||||||
}
|
}
|
||||||
if (connection.ArrowPath == null)
|
if (connection.ArrowPath == null)
|
||||||
{
|
{
|
||||||
connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), Fill = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 };
|
connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetLineColor(connection.Type), Fill = BezierLineDrawer.GetLineColor(connection.Type), StrokeThickness = 1 };
|
||||||
Canvas.SetZIndex(connection.ArrowPath, -1);
|
Canvas.SetZIndex(connection.ArrowPath, -1);
|
||||||
canvas.Children.Add(connection.ArrowPath);
|
canvas.Children.Add(connection.ArrowPath);
|
||||||
}
|
}
|
||||||
@@ -2106,7 +2303,7 @@ namespace Serein.WorkBench
|
|||||||
|
|
||||||
|
|
||||||
// 拖动时重新绘制
|
// 拖动时重新绘制
|
||||||
public static void UpdateBezierLine(Canvas canvas, Connection connection)
|
public static void UpdateBezierLineInDragging(Canvas canvas, Connection connection)
|
||||||
{
|
{
|
||||||
if (isUpdating)
|
if (isUpdating)
|
||||||
return;
|
return;
|
||||||
@@ -2117,14 +2314,14 @@ namespace Serein.WorkBench
|
|||||||
{
|
{
|
||||||
if (connection != null && connection.BezierPath == null)
|
if (connection != null && connection.BezierPath == null)
|
||||||
{
|
{
|
||||||
connection.BezierPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 };
|
connection.BezierPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetLineColor(connection.Type), StrokeThickness = 1 };
|
||||||
//Canvas.SetZIndex(connection.BezierPath, -1);
|
//Canvas.SetZIndex(connection.BezierPath, -1);
|
||||||
canvas.Children.Add(connection.BezierPath);
|
canvas.Children.Add(connection.BezierPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connection != null && connection.ArrowPath == null)
|
if (connection != null && connection.ArrowPath == null)
|
||||||
{
|
{
|
||||||
connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), Fill = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 };
|
connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetLineColor(connection.Type), Fill = BezierLineDrawer.GetLineColor(connection.Type), StrokeThickness = 1 };
|
||||||
//Canvas.SetZIndex(connection.ArrowPath, -1);
|
//Canvas.SetZIndex(connection.ArrowPath, -1);
|
||||||
canvas.Children.Add(connection.ArrowPath);
|
canvas.Children.Add(connection.ArrowPath);
|
||||||
}
|
}
|
||||||
@@ -2188,22 +2385,24 @@ namespace Serein.WorkBench
|
|||||||
public required NodeControlBase Start { get; set; } // 起始
|
public required NodeControlBase Start { get; set; } // 起始
|
||||||
public required NodeControlBase End { get; set; } // 结束
|
public required NodeControlBase End { get; set; } // 结束
|
||||||
|
|
||||||
private Storyboard? _animationStoryboard; // 动画Storyboard
|
|
||||||
|
|
||||||
public void RemoveFromCanvas(Canvas canvas)
|
public void RemoveFromCanvas()
|
||||||
{
|
{
|
||||||
canvas.Children.Remove(BezierPath); // 移除线
|
Canvas.Children.Remove(BezierPath); // 移除线
|
||||||
canvas.Children.Remove(ArrowPath); // 移除线
|
Canvas.Children.Remove(ArrowPath); // 移除线
|
||||||
_animationStoryboard?.Stop(); // 停止动画
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重新绘制
|
||||||
|
/// </summary>
|
||||||
public void Refresh()
|
public void Refresh()
|
||||||
{
|
{
|
||||||
// BsControl.Draw(Canvas, this);
|
|
||||||
BezierLineDrawer.UpdateBezierLine(Canvas, Start, End, BezierPath, ArrowPath);
|
BezierLineDrawer.UpdateBezierLine(Canvas, Start, End, BezierPath, ArrowPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class BezierLineDrawer
|
public static class BezierLineDrawer
|
||||||
{
|
{
|
||||||
public enum Localhost
|
public enum Localhost
|
||||||
@@ -2214,7 +2413,14 @@ namespace Serein.WorkBench
|
|||||||
Bottom,
|
Bottom,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绘制曲线
|
/// <summary>
|
||||||
|
/// 绘制曲线
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="canvas">所在画布</param>
|
||||||
|
/// <param name="startElement">起始控件</param>
|
||||||
|
/// <param name="endElement">终点控件</param>
|
||||||
|
/// <param name="bezierPath">曲线</param>
|
||||||
|
/// <param name="arrowPath">箭头</param>
|
||||||
public static void UpdateBezierLine(Canvas canvas,
|
public static void UpdateBezierLine(Canvas canvas,
|
||||||
FrameworkElement startElement,
|
FrameworkElement startElement,
|
||||||
FrameworkElement endElement,
|
FrameworkElement endElement,
|
||||||
@@ -2223,6 +2429,8 @@ namespace Serein.WorkBench
|
|||||||
{
|
{
|
||||||
Point startPoint = startElement.TranslatePoint(new Point(startElement.ActualWidth / 2, startElement.ActualHeight / 2), canvas);
|
Point startPoint = startElement.TranslatePoint(new Point(startElement.ActualWidth / 2, startElement.ActualHeight / 2), canvas);
|
||||||
Point endPoint = CalculateEndpointOutsideElement(endElement, canvas, startPoint, out Localhost localhost);
|
Point endPoint = CalculateEndpointOutsideElement(endElement, canvas, startPoint, out Localhost localhost);
|
||||||
|
// 根据终点位置决定起点位置 (位于控件的边缘)
|
||||||
|
startPoint = CalculateEdgePoint(startElement, localhost, canvas);
|
||||||
|
|
||||||
PathFigure pathFigure = new PathFigure { StartPoint = startPoint };
|
PathFigure pathFigure = new PathFigure { StartPoint = startPoint };
|
||||||
BezierSegment bezierSegment;
|
BezierSegment bezierSegment;
|
||||||
@@ -2246,8 +2454,11 @@ namespace Serein.WorkBench
|
|||||||
Point3 = endPoint,
|
Point3 = endPoint,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
var minZ = canvas.Children.OfType<UIElement>()//linq语句,取Zindex的最大值
|
||||||
|
.Select(x => Grid.GetZIndex(x))
|
||||||
|
.Min();
|
||||||
|
Grid.SetZIndex(bezierPath, minZ - 1);
|
||||||
|
// Canvas.SetZIndex(bezierPath, 0);
|
||||||
pathFigure.Segments.Add(bezierSegment);
|
pathFigure.Segments.Add(bezierSegment);
|
||||||
|
|
||||||
PathGeometry pathGeometry = new PathGeometry();
|
PathGeometry pathGeometry = new PathGeometry();
|
||||||
@@ -2260,7 +2471,7 @@ namespace Serein.WorkBench
|
|||||||
|
|
||||||
private static Point CalculateBezierTangent(Point startPoint, Point controlPoint1, Point controlPoint2, Point endPoint)
|
private static Point CalculateBezierTangent(Point startPoint, Point controlPoint1, Point controlPoint2, Point endPoint)
|
||||||
{
|
{
|
||||||
double t = 10.0; // 末端点
|
double t = 11; // 末端点
|
||||||
|
|
||||||
// 计算贝塞尔曲线在 t = 1 处的一阶导数
|
// 计算贝塞尔曲线在 t = 1 处的一阶导数
|
||||||
double dx = 3 * Math.Pow(1 - t, 2) * (controlPoint1.X - startPoint.X) +
|
double dx = 3 * Math.Pow(1 - t, 2) * (controlPoint1.X - startPoint.X) +
|
||||||
@@ -2301,6 +2512,31 @@ namespace Serein.WorkBench
|
|||||||
arrowPath.Data = arrowGeometry;
|
arrowPath.Data = arrowGeometry;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// 计算起点位于控件边缘的四个中心点之一
|
||||||
|
private static Point CalculateEdgePoint(FrameworkElement element, Localhost localhost, Canvas canvas)
|
||||||
|
{
|
||||||
|
Point point = new Point();
|
||||||
|
|
||||||
|
switch (localhost)
|
||||||
|
{
|
||||||
|
case Localhost.Right:
|
||||||
|
point = new Point(0, element.ActualHeight / 2); // 左边中心
|
||||||
|
break;
|
||||||
|
case Localhost.Left:
|
||||||
|
point = new Point(element.ActualWidth, element.ActualHeight / 2); // 右边中心
|
||||||
|
break;
|
||||||
|
case Localhost.Bottom:
|
||||||
|
point = new Point(element.ActualWidth / 2, 0); // 上边中心
|
||||||
|
break;
|
||||||
|
case Localhost.Top:
|
||||||
|
point = new Point(element.ActualWidth / 2, element.ActualHeight); // 下边中心
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将相对控件的坐标转换到画布中的全局坐标
|
||||||
|
return element.TranslatePoint(point, canvas);
|
||||||
|
}
|
||||||
|
|
||||||
// 计算终点落点位置
|
// 计算终点落点位置
|
||||||
private static Point CalculateEndpointOutsideElement(FrameworkElement element, Canvas canvas, Point startPoint, out Localhost localhost)
|
private static Point CalculateEndpointOutsideElement(FrameworkElement element, Canvas canvas, Point startPoint, out Localhost localhost)
|
||||||
{
|
{
|
||||||
@@ -2322,35 +2558,57 @@ namespace Serein.WorkBench
|
|||||||
(false, false) => Localhost.Top,
|
(false, false) => Localhost.Top,
|
||||||
};
|
};
|
||||||
|
|
||||||
double halfWidth = element.ActualWidth / 2 + 6;
|
double halfWidth = element.ActualWidth / 2 + 10;
|
||||||
double halfHeight = element.ActualHeight / 2 + 6;
|
double halfHeight = element.ActualHeight / 2 + 10;
|
||||||
double margin = 0;
|
|
||||||
|
|
||||||
|
|
||||||
|
#region 固定中位
|
||||||
|
|
||||||
|
//if (localhost == Localhost.Left)
|
||||||
|
//{
|
||||||
|
// centerPoint.X -= halfWidth;
|
||||||
|
//}
|
||||||
|
//else if (localhost == Localhost.Right)
|
||||||
|
//{
|
||||||
|
// centerPoint.X -= -halfWidth;
|
||||||
|
//}
|
||||||
|
//else if (localhost == Localhost.Top)
|
||||||
|
//{
|
||||||
|
// centerPoint.Y -= halfHeight;
|
||||||
|
//}
|
||||||
|
//else if (localhost == Localhost.Bottom)
|
||||||
|
//{
|
||||||
|
// centerPoint.Y -= -halfHeight;
|
||||||
|
//}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 落点自由移动
|
||||||
|
double margin = 0;
|
||||||
if (localhost == Localhost.Left)
|
if (localhost == Localhost.Left)
|
||||||
{
|
{
|
||||||
centerPoint.X -= halfWidth;
|
centerPoint.X -= halfWidth;
|
||||||
centerPoint.Y -= direction.Y / Math.Abs(direction.X) * halfHeight - margin;
|
centerPoint.Y -= direction.Y / (1 + Math.Abs(direction.X)) * halfHeight - margin;
|
||||||
}
|
}
|
||||||
else if (localhost == Localhost.Right)
|
else if (localhost == Localhost.Right)
|
||||||
{
|
{
|
||||||
centerPoint.X -= -halfWidth;
|
centerPoint.X -= -halfWidth;
|
||||||
centerPoint.Y -= direction.Y / Math.Abs(direction.X) * halfHeight - margin;
|
centerPoint.Y -= direction.Y / (1 + Math.Abs(direction.X)) * halfHeight - margin;
|
||||||
}
|
}
|
||||||
else if (localhost == Localhost.Top)
|
else if (localhost == Localhost.Top)
|
||||||
{
|
{
|
||||||
centerPoint.Y -= halfHeight;
|
centerPoint.Y -= halfHeight;
|
||||||
centerPoint.X -= direction.X / Math.Abs(direction.Y) * halfWidth - margin;
|
centerPoint.X -= direction.X / (1 + Math.Abs(direction.Y)) * halfWidth - margin;
|
||||||
}
|
}
|
||||||
else if (localhost == Localhost.Bottom)
|
else if (localhost == Localhost.Bottom)
|
||||||
{
|
{
|
||||||
centerPoint.Y -= -halfHeight;
|
centerPoint.Y -= -halfHeight;
|
||||||
centerPoint.X -= direction.X / Math.Abs(direction.Y) * halfWidth - margin;
|
centerPoint.X -= direction.X / (1 + Math.Abs(direction.Y)) * halfWidth - margin;
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
return centerPoint;
|
return centerPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SolidColorBrush GetStroke(ConnectionType currentConnectionType)
|
public static SolidColorBrush GetLineColor(ConnectionType currentConnectionType)
|
||||||
{
|
{
|
||||||
return currentConnectionType switch
|
return currentConnectionType switch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -67,23 +67,35 @@ namespace Serein.WorkBench.Node.ViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool isInterrupt;
|
||||||
public bool IsInterrupt
|
public bool IsInterrupt
|
||||||
{
|
{
|
||||||
get => Node.DebugSetting.IsInterrupt;
|
get => isInterrupt;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value)
|
isInterrupt = value;
|
||||||
{
|
|
||||||
Node.Interrupt();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Node.CancelInterrupt();
|
|
||||||
}
|
|
||||||
OnPropertyChanged(nameof(IsInterrupt));
|
OnPropertyChanged(nameof(IsInterrupt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//public bool IsInterrupt
|
||||||
|
//{
|
||||||
|
// get => Node.DebugSetting.IsInterrupt;
|
||||||
|
// set
|
||||||
|
// {
|
||||||
|
// if (value)
|
||||||
|
// {
|
||||||
|
// Node.Interrupt();
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// Node.CancelInterrupt();
|
||||||
|
// }
|
||||||
|
// OnPropertyChanged(nameof(IsInterrupt));
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
//public bool IsProtectionParameter
|
//public bool IsProtectionParameter
|
||||||
//{
|
//{
|
||||||
// get => MethodDetails.IsProtectionParameter;
|
// get => MethodDetails.IsProtectionParameter;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<DockPanel>
|
<DockPanel>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="*"/>
|
<!--<RowDefinition Height="*"/>-->
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
@@ -23,13 +23,13 @@
|
|||||||
<!--<ColumnDefinition Width="*" />-->
|
<!--<ColumnDefinition Width="*" />-->
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<GroupBox Grid.Row="0" Header="条件" Margin="5">
|
<!--<GroupBox Grid.Row="0" Header="条件" Margin="5">
|
||||||
<ListBox x:Name="ConditionsListBox" Background="#A8D8EA"/>
|
<ListBox x:Name="ConditionsListBox" Background="#A8D8EA"/>
|
||||||
</GroupBox>
|
</GroupBox>-->
|
||||||
<GroupBox Grid.Row="1" Header="动作" Margin="5">
|
<GroupBox Grid.Row="0" Header="动作" Margin="5">
|
||||||
<ListBox x:Name="ActionsListBox" Background="#FFCFDF"/>
|
<ListBox x:Name="ActionsListBox" Background="#FFCFDF"/>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
<GroupBox Grid.Row="2" Header="触发器" Margin="5">
|
<GroupBox Grid.Row="1" Header="触发器" Margin="5">
|
||||||
<ListBox x:Name="FlipflopsListBox" Background="#FFFFD2"/>
|
<ListBox x:Name="FlipflopsListBox" Background="#FFFFD2"/>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -26,12 +26,14 @@
|
|||||||
<Compile Remove="Themes\ConditionControl.xaml.cs" />
|
<Compile Remove="Themes\ConditionControl.xaml.cs" />
|
||||||
<Compile Remove="Themes\ConditionControlModel.cs" />
|
<Compile Remove="Themes\ConditionControlModel.cs" />
|
||||||
<Compile Remove="Themes\ExplicitDataControl.xaml.cs" />
|
<Compile Remove="Themes\ExplicitDataControl.xaml.cs" />
|
||||||
|
<Compile Remove="Themes\ObjectViewerControl1.xaml.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Page Remove="Node\FlipflopRegionControl.xaml" />
|
<Page Remove="Node\FlipflopRegionControl.xaml" />
|
||||||
<Page Remove="Themes\ConditionControl.xaml" />
|
<Page Remove="Themes\ConditionControl.xaml" />
|
||||||
<Page Remove="Themes\ExplicitDataControl.xaml" />
|
<Page Remove="Themes\ExplicitDataControl.xaml" />
|
||||||
|
<Page Remove="Themes\ObjectViewerControl1.xaml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
16
WorkBench/Themes/InputDialog.xaml
Normal file
16
WorkBench/Themes/InputDialog.xaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<Window x:Class="Serein.WorkBench.Themes.InputDialog"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:Serein.WorkBench.Themes"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="InputDialog" Height="450" Width="800">
|
||||||
|
<StackPanel Margin="10">
|
||||||
|
<TextBox x:Name="InputTextBox" Width="200" Margin="0,0,0,10" />
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||||
|
<Button Content="确认" Click="ConfirmButton_Click" Margin="5" />
|
||||||
|
<Button Content="取消" Click="CancelButton_Click" Margin="5" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Window>
|
||||||
42
WorkBench/Themes/InputDialog.xaml.cs
Normal file
42
WorkBench/Themes/InputDialog.xaml.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace Serein.WorkBench.Themes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// InputDialog.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class InputDialog : Window
|
||||||
|
{
|
||||||
|
public string InputValue { get; private set; }
|
||||||
|
|
||||||
|
public InputDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
InputValue = InputTextBox.Text;
|
||||||
|
DialogResult = true; // 设置返回结果为 true
|
||||||
|
Close(); // 关闭窗口
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
DialogResult = false; // 设置返回结果为 false
|
||||||
|
Close(); // 关闭窗口
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
WorkBench/Themes/ObjectViewerControl.xaml
Normal file
29
WorkBench/Themes/ObjectViewerControl.xaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<UserControl x:Class="Serein.WorkBench.Themes.ObjectViewerControl"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:Serein.WorkBench.Themes"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<!-- 按钮 -->
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<!-- 树视图 -->
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<!--Click="RefreshButton_Click"
|
||||||
|
Click="TimerRefreshButton_Click"-->
|
||||||
|
<!--<Button Grid.Row="0" HorizontalAlignment="Left" Margin="4,2,4,2" Content="刷新" Width="100" Height="20" Name="RefreshButton"/>-->
|
||||||
|
<!--<Button Grid.Row="0" HorizontalAlignment="Left" Margin="14,2,4,2" Content="监视" Width="100" Height="20" Name="TimerRefreshButton"/>-->
|
||||||
|
<Button Grid.Row="0" HorizontalAlignment="Left" Margin="14,2,4,2" Content="添加监视表达式" Width="100" Height="20" Name="AddMonitorExpressionButton" Click="AddMonitorExpressionButton_Click"/>
|
||||||
|
</StackPanel>
|
||||||
|
<!-- 刷新按钮 -->
|
||||||
|
|
||||||
|
<!-- 树视图,用于显示对象属性 -->
|
||||||
|
<TreeView FontSize="13" x:Name="ObjectTreeView" Grid.Row="1" />
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
452
WorkBench/Themes/ObjectViewerControl.xaml.cs
Normal file
452
WorkBench/Themes/ObjectViewerControl.xaml.cs
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
using Serein.NodeFlow.Base;
|
||||||
|
using Serein.NodeFlow.Tool.SereinExpression;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using static Serein.WorkBench.Themes.TypeViewerWindow;
|
||||||
|
using static System.Collections.Specialized.BitVector32;
|
||||||
|
|
||||||
|
namespace Serein.WorkBench.Themes
|
||||||
|
{
|
||||||
|
|
||||||
|
public class FlowDataDetails
|
||||||
|
{
|
||||||
|
/// <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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ObjectViewerControl.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class ObjectViewerControl : UserControl
|
||||||
|
{
|
||||||
|
private object _objectInstance;
|
||||||
|
public string NodeGuid { get;set; }
|
||||||
|
|
||||||
|
// private NodeModelBase _nodeFlowData;
|
||||||
|
|
||||||
|
public ObjectViewerControl()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加载对象信息,展示其成员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">要展示的对象</param>
|
||||||
|
//public void LoadObjectInformation(NodeModelBase nodeModel)
|
||||||
|
public void LoadObjectInformation(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
return;
|
||||||
|
//IsTimerRefres = false;
|
||||||
|
//TimerRefreshButton.Content = "定时刷新";
|
||||||
|
_objectInstance = obj;
|
||||||
|
RefreshObjectTree(obj);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddMonitorExpressionButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 刷新对象属性树
|
||||||
|
/// </summary>
|
||||||
|
public void RefreshObjectTree(object obj)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
return;
|
||||||
|
// _objectInstance = obj;
|
||||||
|
var objectType = obj.GetType();
|
||||||
|
|
||||||
|
FlowDataDetails flowDataDetails = new FlowDataDetails
|
||||||
|
{
|
||||||
|
Name = objectType.Name,
|
||||||
|
DataType = objectType,
|
||||||
|
DataValue = obj
|
||||||
|
};
|
||||||
|
var rootNode = new TreeViewItem
|
||||||
|
{
|
||||||
|
Header = objectType.Name,
|
||||||
|
Tag = flowDataDetails,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加占位符节点
|
||||||
|
AddPlaceholderNode(rootNode);
|
||||||
|
ObjectTreeView.Items.Clear();
|
||||||
|
ObjectTreeView.Items.Add(rootNode);
|
||||||
|
|
||||||
|
// 监听展开事件
|
||||||
|
rootNode.Expanded += TreeViewItem_Expanded;
|
||||||
|
|
||||||
|
// 自动展开第一层
|
||||||
|
rootNode.IsExpanded = true; // 直接展开根节点
|
||||||
|
|
||||||
|
// 加载根节点的属性和字段
|
||||||
|
if (rootNode.Items.Count == 1 && rootNode.Items[0] is TreeViewItem placeholder && placeholder.Header.ToString() == "Loading...")
|
||||||
|
{
|
||||||
|
rootNode.Items.Clear();
|
||||||
|
AddMembersToTreeNode(rootNode, obj, objectType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加父节点
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node"></param>
|
||||||
|
private static void AddPlaceholderNode(TreeViewItem node)
|
||||||
|
{
|
||||||
|
node.Items.Add(new TreeViewItem { Header = "Loading..." });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 展开子项事件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private static 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 FlowDataDetails flowDataDetails) // FlowDataDetails flowDataDetails object obj
|
||||||
|
{
|
||||||
|
AddMembersToTreeNode(item, flowDataDetails.DataValue, flowDataDetails.DataType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 反射对象数据添加子节点
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="treeViewNode"></param>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
private static void AddMembersToTreeNode(TreeViewItem treeViewNode, object obj, Type type)
|
||||||
|
{
|
||||||
|
// 获取属性和字段
|
||||||
|
var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||||
|
foreach (var member in members)
|
||||||
|
{
|
||||||
|
TreeViewItem memberNode = ConfigureTreeViewItem(obj, member);
|
||||||
|
treeViewNode.Items.Add(memberNode);
|
||||||
|
|
||||||
|
//if (ConfigureTreeItemMenu(memberNode, member, out ContextMenu? contextMenu))
|
||||||
|
//{
|
||||||
|
// memberNode.ContextMenu = contextMenu; // 设置子项节点的事件
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置右键菜单功能
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <param name="member"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static TreeViewItem ConfigureTreeViewItem(object obj, MemberInfo member)
|
||||||
|
{
|
||||||
|
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
|
||||||
|
|
||||||
|
if (member is PropertyInfo property)
|
||||||
|
{
|
||||||
|
FlowDataDetails flowDataDetails = new FlowDataDetails
|
||||||
|
{
|
||||||
|
ItemType = TreeItemType.Property,
|
||||||
|
DataType = property.PropertyType,
|
||||||
|
Name = property.Name,
|
||||||
|
DataValue = property,
|
||||||
|
};
|
||||||
|
|
||||||
|
memberNode.Tag = flowDataDetails;
|
||||||
|
|
||||||
|
string propertyValue = GetPropertyValue(obj, property);
|
||||||
|
memberNode.Header = $"{property.Name} : {property.PropertyType.Name} = {propertyValue}";
|
||||||
|
|
||||||
|
if (!property.PropertyType.IsPrimitive && property.PropertyType != typeof(string))
|
||||||
|
{
|
||||||
|
AddPlaceholderNode(memberNode);
|
||||||
|
memberNode.Expanded += TreeViewItem_Expanded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (member is FieldInfo field)
|
||||||
|
{
|
||||||
|
FlowDataDetails flowDataDetails = new FlowDataDetails
|
||||||
|
{
|
||||||
|
ItemType = TreeItemType.Field,
|
||||||
|
DataType = field.FieldType,
|
||||||
|
Name = field.Name,
|
||||||
|
DataValue = field,
|
||||||
|
};
|
||||||
|
|
||||||
|
memberNode.Tag = flowDataDetails;
|
||||||
|
|
||||||
|
|
||||||
|
string fieldValue = GetFieldValue(obj, field);
|
||||||
|
memberNode.Header = $"{field.Name} : {field.FieldType.Name} = {fieldValue}";
|
||||||
|
|
||||||
|
if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
|
||||||
|
{
|
||||||
|
AddPlaceholderNode(memberNode);
|
||||||
|
memberNode.Expanded += TreeViewItem_Expanded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return memberNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取属性类型的成员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <param name="property"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static string GetPropertyValue(object obj, PropertyInfo property)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var value = property.GetValue(obj);
|
||||||
|
return value?.ToString() ?? "null";
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return "Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取字段类型的成员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <param name="field"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static string GetFieldValue(object obj, FieldInfo field)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var value = field.GetValue(obj);
|
||||||
|
return value?.ToString() ?? "null";
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return "Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据成员类别配置右键菜单
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="memberNode"></param>
|
||||||
|
/// <param name="member"></param>
|
||||||
|
/// <param name="contextMenu"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static 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 = ObjectViewerControl.GetNodeFullPath(memberNode);
|
||||||
|
string copyValue = "@Get " + fullPath;
|
||||||
|
Clipboard.SetDataObject(copyValue);
|
||||||
|
}));
|
||||||
|
//contextMenu.Items.Add(MainWindow.CreateMenuItem($"监视中断", (s, e) =>
|
||||||
|
//{
|
||||||
|
// string fullPath = GetNodeFullPath(memberNode);
|
||||||
|
// Clipboard.SetDataObject(fullPath);
|
||||||
|
// OpenInputDialog((exp) =>
|
||||||
|
// {
|
||||||
|
// if (node.DebugSetting.InterruptExpression.Contains(exp))
|
||||||
|
// {
|
||||||
|
// Console.WriteLine("表达式已存在");
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// node.DebugSetting.InterruptExpression.Add(exp);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
//}));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
contextMenu = new ContextMenu();
|
||||||
|
}
|
||||||
|
return isChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前节点的完整路径,例如 "node1.node2.node3.node4"
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">目标节点</param>
|
||||||
|
/// <returns>节点路径</returns>
|
||||||
|
private static string GetNodeFullPath(TreeViewItem node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
FlowDataDetails flowDataDetails = (FlowDataDetails)node.Tag;
|
||||||
|
var parent = GetParentTreeViewItem(node);
|
||||||
|
if (parent != null)
|
||||||
|
{
|
||||||
|
// 递归获取父节点的路径,并拼接当前节点的 Header
|
||||||
|
return $"{GetNodeFullPath(parent)}.{flowDataDetails.Name}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 没有父节点,则说明这是根节点,直接返回 Header
|
||||||
|
return "";
|
||||||
|
// return typeNodeDetails.Name.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定节点的父级节点
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">目标节点</param>
|
||||||
|
/// <returns>父节点</returns>
|
||||||
|
private static TreeViewItem GetParentTreeViewItem(TreeViewItem node)
|
||||||
|
{
|
||||||
|
DependencyObject parent = VisualTreeHelper.GetParent(node);
|
||||||
|
while (parent != null && !(parent is TreeViewItem))
|
||||||
|
{
|
||||||
|
parent = VisualTreeHelper.GetParent(parent);
|
||||||
|
}
|
||||||
|
return parent as TreeViewItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static InputDialog OpenInputDialog(Action<string> action)
|
||||||
|
{
|
||||||
|
var inputDialog = new InputDialog();
|
||||||
|
inputDialog.Closed += (s, e) =>
|
||||||
|
{
|
||||||
|
if (inputDialog.DialogResult == true)
|
||||||
|
{
|
||||||
|
string userInput = inputDialog.InputValue;
|
||||||
|
action?.Invoke(userInput);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
inputDialog.ShowDialog();
|
||||||
|
return inputDialog;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// 刷新按钮的点击事件
|
||||||
|
///// </summary>
|
||||||
|
//private void RefreshButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
//{
|
||||||
|
// RefreshObjectTree();
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private bool IsTimerRefres = false;
|
||||||
|
//private void TimerRefreshButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
//{
|
||||||
|
// if (IsTimerRefres)
|
||||||
|
// {
|
||||||
|
// IsTimerRefres = false;
|
||||||
|
// TimerRefreshButton.Content = "定时刷新";
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// IsTimerRefres = true;
|
||||||
|
// TimerRefreshButton.Content = "取消刷新";
|
||||||
|
|
||||||
|
// _ = Task.Run(async () => {
|
||||||
|
// while (true)
|
||||||
|
// {
|
||||||
|
// if (IsTimerRefres)
|
||||||
|
// {
|
||||||
|
// Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
// {
|
||||||
|
// RefreshObjectTree(); // 刷新UI
|
||||||
|
// });
|
||||||
|
// await Task.Delay(100);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// IsTimerRefres = false;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
8
WorkBench/Themes/ObjectViewerControl1.xaml
Normal file
8
WorkBench/Themes/ObjectViewerControl1.xaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<UserControl x:Class="RealTimeObjectViewer.ObjectViewerControl"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:Serein.WorkBench.Themes">
|
||||||
|
<Grid>
|
||||||
|
<TreeView x:Name="ObjectTreeView" />
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
146
WorkBench/Themes/ObjectViewerControl1.xaml.cs
Normal file
146
WorkBench/Themes/ObjectViewerControl1.xaml.cs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace Serein.WorkBench.Themes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ObjectViewerWindow.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class ObjectViewerControl : UserControl
|
||||||
|
{
|
||||||
|
public ObjectViewerControl()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private object _objectInstance;
|
||||||
|
private Action _closeCallback;
|
||||||
|
|
||||||
|
public void LoadObjectInformation(object obj,Action closeCallback)
|
||||||
|
{
|
||||||
|
if (obj == null || closeCallback == null)
|
||||||
|
return;
|
||||||
|
_closeCallback = closeCallback;
|
||||||
|
_objectInstance = obj;
|
||||||
|
var objectType = obj.GetType();
|
||||||
|
var rootNode = new TreeViewItem { Header = objectType.Name, Tag = obj };
|
||||||
|
|
||||||
|
// 添加占位符节点
|
||||||
|
AddPlaceholderNode(rootNode);
|
||||||
|
ObjectTreeView.Items.Clear();
|
||||||
|
ObjectTreeView.Items.Add(rootNode);
|
||||||
|
|
||||||
|
// 监听展开事件
|
||||||
|
rootNode.Expanded += TreeViewItem_Expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddPlaceholderNode(TreeViewItem node)
|
||||||
|
{
|
||||||
|
node.Items.Add(new TreeViewItem { Header = "Loading..." });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var item = (TreeViewItem)sender;
|
||||||
|
|
||||||
|
if (item.Items.Count == 1 && item.Items[0] is TreeViewItem placeholder && placeholder.Header.ToString() == "Loading...")
|
||||||
|
{
|
||||||
|
item.Items.Clear();
|
||||||
|
if (item.Tag is object obj)
|
||||||
|
{
|
||||||
|
var objectType = obj.GetType();
|
||||||
|
AddMembersToTreeNode(item, obj, objectType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddMembersToTreeNode(TreeViewItem node, object obj, Type type)
|
||||||
|
{
|
||||||
|
// 获取属性和字段
|
||||||
|
var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||||
|
foreach (var member in members)
|
||||||
|
{
|
||||||
|
TreeViewItem memberNode = ConfigureTreeViewItem(obj, member);
|
||||||
|
node.Items.Add(memberNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TreeViewItem ConfigureTreeViewItem(object obj, MemberInfo member)
|
||||||
|
{
|
||||||
|
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
|
||||||
|
|
||||||
|
if (member is PropertyInfo property)
|
||||||
|
{
|
||||||
|
string propertyValue = GetPropertyValue(obj, property);
|
||||||
|
memberNode.Header = $"{property.Name} : {property.PropertyType.Name} = {propertyValue}";
|
||||||
|
|
||||||
|
if (!property.PropertyType.IsPrimitive && property.PropertyType != typeof(string))
|
||||||
|
{
|
||||||
|
AddPlaceholderNode(memberNode);
|
||||||
|
memberNode.Expanded += TreeViewItem_Expanded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (member is FieldInfo field)
|
||||||
|
{
|
||||||
|
string fieldValue = GetFieldValue(obj, field);
|
||||||
|
memberNode.Header = $"{field.Name} : {field.FieldType.Name} = {fieldValue}";
|
||||||
|
|
||||||
|
if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
|
||||||
|
{
|
||||||
|
AddPlaceholderNode(memberNode);
|
||||||
|
memberNode.Expanded += TreeViewItem_Expanded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return memberNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetPropertyValue(object obj, PropertyInfo property)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var value = property.GetValue(obj);
|
||||||
|
return value?.ToString() ?? "null";
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return "Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetFieldValue(object obj, FieldInfo field)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var value = field.GetValue(obj);
|
||||||
|
return value?.ToString() ?? "null";
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return "Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Window_Closed(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
_closeCallback?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
xmlns:local="clr-namespace:Serein.WorkBench.Themes"
|
xmlns:local="clr-namespace:Serein.WorkBench.Themes"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Topmost="True"
|
Topmost="True"
|
||||||
|
|
||||||
Title="TypeViewerWindow" Height="300" Width="300">
|
Title="TypeViewerWindow" Height="300" Width="300">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ namespace Serein.WorkBench.Themes
|
|||||||
if (Type == null)
|
if (Type == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TypeNodeDetails typeNodeDetails = new TypeNodeDetails
|
NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
|
||||||
{
|
{
|
||||||
Name = Type.Name,
|
Name = Type.Name,
|
||||||
DataType = Type,
|
DataType = Type,
|
||||||
@@ -64,7 +64,7 @@ namespace Serein.WorkBench.Themes
|
|||||||
if (item.Items.Count == 1 && item.Items[0] is TreeViewItem placeholder && placeholder.Header.ToString() == "Loading...")
|
if (item.Items.Count == 1 && item.Items[0] is TreeViewItem placeholder && placeholder.Header.ToString() == "Loading...")
|
||||||
{
|
{
|
||||||
item.Items.Clear();
|
item.Items.Clear();
|
||||||
if (item.Tag is TypeNodeDetails typeNodeDetails)
|
if (item.Tag is NodeFlowDataObjectDetails typeNodeDetails)
|
||||||
{
|
{
|
||||||
AddMembersToTreeNode(item, typeNodeDetails.DataType);
|
AddMembersToTreeNode(item, typeNodeDetails.DataType);
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ namespace Serein.WorkBench.Themes
|
|||||||
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
|
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
|
||||||
if (member is PropertyInfo property)
|
if (member is PropertyInfo property)
|
||||||
{
|
{
|
||||||
TypeNodeDetails typeNodeDetails = new TypeNodeDetails
|
NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
|
||||||
{
|
{
|
||||||
ItemType = TreeItemType.Property,
|
ItemType = TreeItemType.Property,
|
||||||
DataType = property.PropertyType,
|
DataType = property.PropertyType,
|
||||||
@@ -124,7 +124,7 @@ namespace Serein.WorkBench.Themes
|
|||||||
}
|
}
|
||||||
else if (member is MethodInfo method)
|
else if (member is MethodInfo method)
|
||||||
{
|
{
|
||||||
TypeNodeDetails typeNodeDetails = new TypeNodeDetails
|
NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
|
||||||
{
|
{
|
||||||
ItemType = TreeItemType.Method,
|
ItemType = TreeItemType.Method,
|
||||||
DataType = typeof(MethodInfo),
|
DataType = typeof(MethodInfo),
|
||||||
@@ -139,7 +139,7 @@ namespace Serein.WorkBench.Themes
|
|||||||
}
|
}
|
||||||
else if (member is FieldInfo field)
|
else if (member is FieldInfo field)
|
||||||
{
|
{
|
||||||
TypeNodeDetails typeNodeDetails = new TypeNodeDetails
|
NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
|
||||||
{
|
{
|
||||||
ItemType = TreeItemType.Field,
|
ItemType = TreeItemType.Field,
|
||||||
DataType = field.FieldType,
|
DataType = field.FieldType,
|
||||||
@@ -201,7 +201,7 @@ namespace Serein.WorkBench.Themes
|
|||||||
if (node == null)
|
if (node == null)
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
TypeNodeDetails typeNodeDetails = (TypeNodeDetails)node.Tag;
|
NodeFlowDataObjectDetails typeNodeDetails = (NodeFlowDataObjectDetails)node.Tag;
|
||||||
var parent = GetParentTreeViewItem(node);
|
var parent = GetParentTreeViewItem(node);
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
{
|
{
|
||||||
@@ -210,10 +210,8 @@ namespace Serein.WorkBench.Themes
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return "";
|
|
||||||
|
|
||||||
|
|
||||||
// 没有父节点,则说明这是根节点,直接返回 Header
|
// 没有父节点,则说明这是根节点,直接返回 Header
|
||||||
|
return "";
|
||||||
// return typeNodeDetails.Name.ToString();
|
// return typeNodeDetails.Name.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,7 +233,7 @@ namespace Serein.WorkBench.Themes
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class TypeNodeDetails
|
public class NodeFlowDataObjectDetails
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 属性名称
|
/// 属性名称
|
||||||
@@ -245,8 +243,6 @@ namespace Serein.WorkBench.Themes
|
|||||||
/// 属性类型
|
/// 属性类型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TreeItemType ItemType { get; set; }
|
public TreeItemType ItemType { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 数据类型
|
/// 数据类型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user