diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs
index 1d83946..c98293f 100644
--- a/Library/Api/IFlowEnvironment.cs
+++ b/Library/Api/IFlowEnvironment.cs
@@ -45,7 +45,7 @@ namespace Serein.Library.Api
/// 环境中流程起始节点发生了改变
///
///
- public delegate void StartNodeChangeHandler(StartNodeChangeEventArgs eventArgs);
+ public delegate void StartNodeChangeHandler(StartNodeChangeEventArgs eventArgs);
#endregion
#region 环境事件签名
@@ -211,19 +211,134 @@ namespace Serein.Library.Api
}
#endregion
+
+ ///
+ /// 被监视的对象改变事件
+ ///
+ ///
+ public delegate void MonitorObjectChangeHandler(MonitorObjectEventArgs eventArgs);
+ ///
+ /// 节点中断状态改变事件(开启了中断/取消了中断)
+ ///
+ ///
+ public delegate void NodeInterruptStateChangeHandler(NodeInterruptStateChangeEventArgs eventArgs);
+ ///
+ /// 节点触发中断事件
+ ///
+ ///
+ public delegate void NodeInterruptTriggerHandler(NodeInterruptTriggerEventArgs eventArgs);
+
+ ///
+ /// 监视的节点数据发生变化
+ ///
+ public class MonitorObjectEventArgs : FlowEventArgs
+ {
+ public MonitorObjectEventArgs(string nodeGuid,object newData)
+ {
+ NodeGuid = nodeGuid;
+ NewData = newData;
+ }
+
+ ///
+ /// 中断的节点Guid
+ ///
+ public string NodeGuid { get; protected set; }
+
+ ///
+ /// 新的数据
+ ///
+ public object NewData { get; protected set; }
+ }
+
+ ///
+ /// 节点中断状态改变事件参数
+ ///
+ public class NodeInterruptStateChangeEventArgs : FlowEventArgs
+ {
+ public NodeInterruptStateChangeEventArgs(string nodeGuid,InterruptClass @class)
+ {
+ NodeGuid = nodeGuid;
+ Class = @class;
+ }
+
+ ///
+ /// 中断的节点Guid
+ ///
+ public string NodeGuid { get; protected set; }
+ public InterruptClass Class { get; protected set; }
+ }
+ ///
+ /// 节点触发了中断事件参数
+ ///
+ public class NodeInterruptTriggerEventArgs : FlowEventArgs
+ {
+ public NodeInterruptTriggerEventArgs(string nodeGuid)
+ {
+ NodeGuid = nodeGuid;
+ }
+
+ ///
+ /// 中断的节点Guid
+ ///
+ public string NodeGuid { get; protected set; }
+ }
+
public interface IFlowEnvironment
{
ChannelFlowInterrupt ChannelFlowInterrupt { get; set; }
- event FlowRunCompleteHandler OnFlowRunComplete;
- event ProjectLoadedHandler OnProjectLoaded;
+ ///
+ /// 加载Dll
+ ///
event LoadDLLHandler OnDllLoad;
+
+ ///
+ /// 项目加载完成
+ ///
+ event ProjectLoadedHandler OnProjectLoaded;
+
+ ///
+ /// 节点连接属性改变事件
+ ///
event NodeConnectChangeHandler OnNodeConnectChange;
+
+ ///
+ /// 节点创建事件
+ ///
event NodeCreateHandler OnNodeCreate;
+
+ ///
+ /// 移除节点事件
+ ///
event NodeRemoteHandler OnNodeRemote;
+
+ ///
+ /// 起始节点变化事件
+ ///
event StartNodeChangeHandler OnStartNodeChange;
+ ///
+ /// 流程运行完成事件
+ ///
+ event FlowRunCompleteHandler OnFlowRunComplete;
+
+ ///
+ /// 被监视的对象改变事件
+ ///
+ event MonitorObjectChangeHandler OnMonitorObjectChange;
+
+ ///
+ /// 节点中断状态变化事件
+ ///
+ event NodeInterruptStateChangeHandler OnNodeInterruptStateChange;
+
+ ///
+ /// 节点触发中断
+ ///
+ event NodeInterruptTriggerHandler OnNodeInterruptTrigger;
+
+
///
/// 保存当前项目
///
@@ -252,6 +367,7 @@ namespace Serein.Library.Api
///
bool TryGetMethodDetails(string methodName,out MethodDetails md);
+
///
/// 开始运行
///
@@ -261,6 +377,8 @@ namespace Serein.Library.Api
///
void Exit();
+
+
///
/// 设置流程起点节点
///
@@ -293,6 +411,27 @@ namespace Serein.Library.Api
void RemoteNode(string nodeGuid);
- }
+ ///
+ /// 设置节点中断级别
+ ///
+ /// 被中断的节点Guid
+ /// 新的中断级别
+ ///
+ bool NodeInterruptChange(string nodeGuid,InterruptClass interruptClass);
+ ///
+ /// ///
+ /// 设置节点数据监视状态
+ ///
+ /// 需要监视的节点Guid
+ /// 是否监视
+ void SetNodeFLowDataMonitorState(string nodeGuid, bool isMonitor);
+
+ ///
+ /// 节点数据更新通知
+ ///
+ ///
+ void FlowDataUpdateNotification(string nodeGuid, object flowData);
+
+ }
}
diff --git a/Library/Api/ISereinIoc.cs b/Library/Api/ISereinIoc.cs
index 83c3f9d..f58169c 100644
--- a/Library/Api/ISereinIoc.cs
+++ b/Library/Api/ISereinIoc.cs
@@ -32,6 +32,9 @@ namespace Serein.Library.Api
object Get(string name);
void CustomRegisterInstance(string name, object instance, bool needInjectProperty = true);
+ ISereinIOC Run(string name, Action action);
+
+
///
/// 创建目标类型的对象, 并注入依赖项
///
diff --git a/Library/Entity/NodeDebugSetting.cs b/Library/Entity/NodeDebugSetting.cs
index c605267..9008fb7 100644
--- a/Library/Entity/NodeDebugSetting.cs
+++ b/Library/Entity/NodeDebugSetting.cs
@@ -12,14 +12,17 @@ namespace Serein.Library.Entity
public bool IsEnable { get; set; } = true;
///
- /// 是否中断(调试中断功能)
+ /// 是否监视数据改变
///
- public bool IsInterrupt { get; set; } = false;
+ public bool IsMonitorFlowData { get; set; } = false;
///
/// 中断级别,暂时停止继续执行后继分支。
///
public InterruptClass InterruptClass { get; set; } = InterruptClass.None;
+
+
+ public List InterruptExpression { get; } = new List();
}
///
@@ -32,15 +35,15 @@ namespace Serein.Library.Entity
///
None,
///
- /// 分支中断,当前节点。
+ /// 分支中断,中断进入当前节点的分支。
///
Branch,
///
- /// 分组中断,相同中断分组的节点。
+ /// 分组中断,中断进入指定节点分组的分支。(暂未实现相关)
///
Group,
///
- /// 全局中断,其它所有节点。
+ /// 全局中断,中断全局所有节点的运行。(暂未实现相关)
///
Global,
}
diff --git a/Library/Utils/SereinIoc.cs b/Library/Utils/SereinIoc.cs
index 03c238a..21c1479 100644
--- a/Library/Utils/SereinIoc.cs
+++ b/Library/Utils/SereinIoc.cs
@@ -366,6 +366,27 @@ namespace Serein.Library.Utils
#endregion
#region run()
+ public ISereinIOC Run(string name, Action 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(Action action)
{
var service = GetOrRegisterInstantiate();
diff --git a/Library/Web/WebServer.cs b/Library/Web/WebServer.cs
index 436461e..fa34465 100644
--- a/Library/Web/WebServer.cs
+++ b/Library/Web/WebServer.cs
@@ -123,8 +123,16 @@ namespace Serein.Library.Web
// 停止服务器
public void Stop()
{
- listener?.Stop(); // 停止监听
- listener?.Close(); // 关闭监听器
+
+ try
+ {
+ listener?.Stop(); // 停止监听
+ listener?.Close(); // 关闭监听器
+ }
+ catch (Exception EX)
+ {
+ Console.WriteLine(EX);
+ }
}
}
diff --git a/NodeFlow/Base/NodeModelBaseData.cs b/NodeFlow/Base/NodeModelBaseData.cs
index 4623382..06ef4ad 100644
--- a/NodeFlow/Base/NodeModelBaseData.cs
+++ b/NodeFlow/Base/NodeModelBaseData.cs
@@ -81,7 +81,7 @@ namespace Serein.NodeFlow.Base
///
/// 当前传递数据(执行了节点对应的方法,才会存在值)
///
- public object? FlowData { get; set; } = null;
+ protected object? FlowData { get; set; } = null;
}
diff --git a/NodeFlow/Base/NodeModelBaseFunc.cs b/NodeFlow/Base/NodeModelBaseFunc.cs
index 208e698..06f4035 100644
--- a/NodeFlow/Base/NodeModelBaseFunc.cs
+++ b/NodeFlow/Base/NodeModelBaseFunc.cs
@@ -8,6 +8,7 @@ using Serein.NodeFlow.Tool.SereinExpression;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Linq.Expressions;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
@@ -34,7 +35,6 @@ namespace Serein.NodeFlow.Base
public void Interrupt()
{
this.DebugSetting.InterruptClass = InterruptClass.Branch;
- this.DebugSetting.IsInterrupt = true;
}
///
@@ -43,7 +43,6 @@ namespace Serein.NodeFlow.Base
public void CancelInterrupt()
{
this.DebugSetting.InterruptClass = InterruptClass.None;
- this.DebugSetting.IsInterrupt = false;
CancelInterruptCallback?.Invoke();
CancelInterruptCallback = null;
}
@@ -106,93 +105,64 @@ namespace Serein.NodeFlow.Base
///
public async Task StartExecute(IDynamicContext context)
{
- CancellationTokenSource cts = null;
- try
+ Stack stack = new Stack();
+ stack.Push(this);
+ var cts = context.SereinIoc.Get(FlowStarter.FlipFlopCtsName);
+ while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环
{
- cts = context.SereinIoc.Get(FlowStarter.FlipFlopCtsName);
+ // 从栈中弹出一个节点作为当前节点进行处理
+ var currentNode = stack.Pop();
-
- Stack stack = new Stack();
- stack.Push(this);
- while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环
+ // 设置方法执行的对象
+ if (currentNode.MethodDetails?.ActingInstance == null && currentNode.MethodDetails?.ActingInstanceType is not null)
{
- // 从栈中弹出一个节点作为当前节点进行处理
- 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
+ currentNode.MethodDetails.ActingInstance ??= context.SereinIoc.GetOrRegisterInstantiate(currentNode.MethodDetails.ActingInstanceType);
}
- }
- finally
- {
- cts?.Dispose();
- }
- }
- public static bool TryCreateInterruptTask(IDynamicContext context, NodeModelBase currentNode, out Task? task)
- {
- bool haveTask;
- Console.WriteLine($"[{currentNode.MethodDetails.MethodName}]在当前分支中断");
+ #region 执行相关
- if (currentNode.DebugSetting.InterruptClass == InterruptClass.None)
- {
- haveTask = false;
- task = null;
- currentNode.DebugSetting.IsInterrupt = false; // 纠正设置
- }
- else if (currentNode.DebugSetting.InterruptClass == InterruptClass.Branch) // 中断当前分支
- {
- currentNode.DebugSetting.IsInterrupt = true;
- haveTask = true;
- task = context.FlowEnvironment.ChannelFlowInterrupt.CreateChannelWithTimeoutAsync(currentNode.Guid, TimeSpan.FromSeconds(60 * 30)); // 中断30分钟
- }
- else
- {
- haveTask = false;
- task = null;
- }
+ // 首先执行上游分支
+ var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream];
+ for (int i = upstreamNodes.Count - 1; i >= 0; i--)
+ {
+ if (upstreamNodes[i].DebugSetting.IsEnable) // 排除未启用的上游节点
+ {
+ upstreamNodes[i].PreviousNode = currentNode;
+ var upNewFlowData = await upstreamNodes[i].ExecutingAsync(context); // 执行流程节点的上游分支
+ await FlowRefreshDataOrInterrupt(context, upstreamNodes[i], upNewFlowData); // 执行上游分支后刷新上游节点数据
+ }
+ }
- 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
+ }
}
///
@@ -203,12 +173,11 @@ namespace Serein.NodeFlow.Base
public virtual async Task
public RunState FlowState { get; private set; } = RunState.NoStart;
public RunState FlipFlopState { get; private set; } = RunState.NoStart;
-
///
/// 运行时的IOC容器
///
private ISereinIOC SereinIOC { get; } = null;
-
///
/// 结束运行时需要执行的方法
///
private Action ExitAction { get; set; } = null;
-
///
/// 运行的上下文
///
private IDynamicContext Context { get; set; } = null;
-
private void CheckStartState()
{
if (IsStopStart)
@@ -81,30 +78,52 @@ namespace Serein.NodeFlow
}
+ //
+ // 开始运行
+ //
+ // 起始节点
+ // 运行环境
+ // 环境中已加载的所有节点方法
+ // 触发器节点
+ //
+
///
/// 开始运行
///
- /// 起始节点
/// 运行环境
- /// 环境中已加载的所有节点方法
- /// 触发器节点
+ /// 环境中已加载的所有节点
+ /// 初始化方法
+ /// 加载时方法
+ /// 结束时方法
///
- public async Task RunAsync(NodeModelBase startNode,
- IFlowEnvironment env,
- List runNodeMd,
+ public async Task RunAsync(IFlowEnvironment env,
+ List nodes,
List initMethods,
List loadingMethods,
- List exitMethods,
- List flipflopNodes)
+ List exitMethods)
{
FlowState = RunState.Running; // 开始运行
-
- if (startNode == null) {
+ NodeModelBase? startNode = nodes.FirstOrDefault(node => node.IsStart);
+ if (startNode is null) {
FlowState = RunState.Completion; // 不存在起点,退出流程
return;
}
+ #region 获取所有触发器,以及已加载节点的方法信息
+ List runNodeMd;
+ List 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 选择运行环境的上下文
// 判断使用哪一种流程上下文
@@ -146,7 +165,7 @@ namespace Serein.NodeFlow
IsStopStart = true;
}
}
- CheckStartState();
+ CheckStartState(); // 初始化IOC后检查状态
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
#region 检查并修正初始化、加载时、退出时方法作用的对象,保证后续不会报错(已注释)
@@ -204,7 +219,6 @@ namespace Serein.NodeFlow
#region 设置流程退出时的回调函数
ExitAction = () =>
{
-
SereinIOC.Run(web => {
web?.Stop();
});
@@ -214,23 +228,18 @@ namespace Serein.NodeFlow
((Action)md.MethodDelegate).Invoke(md.ActingInstance, [Context]);
}
- //if (Context != null && Context.NodeRunCts != null && !Context.NodeRunCts.IsCancellationRequested)
- //{
- // Context.NodeRunCts.Cancel();
- //}
-
- //if (FlipFlopCts != null && !FlipFlopCts.IsCancellationRequested)
- //{
- // FlipFlopCts?.Cancel();
- // FlipFlopCts?.Dispose();
- //}
+ if (_flipFlopCts != null && !_flipFlopCts.IsCancellationRequested)
+ {
+ _flipFlopCts?.Cancel();
+ _flipFlopCts?.Dispose();
+ }
FlowState = RunState.Completion;
FlipFlopState = RunState.Completion;
};
#endregion
#region 开始启动流程
- CancellationTokenSource FlipFlopCts = null;
+
try
{
@@ -238,8 +247,8 @@ namespace Serein.NodeFlow
{
FlipFlopState = RunState.Running;
// 如果存在需要启动的触发器,则开始启动
- FlipFlopCts = new CancellationTokenSource();
- SereinIOC.CustomRegisterInstance(FlipFlopCtsName, FlipFlopCts,false);
+ _flipFlopCts = new CancellationTokenSource();
+ SereinIOC.CustomRegisterInstance(FlipFlopCtsName, _flipFlopCts,false);
// 使用 TaskCompletionSource 创建未启动的触发器任务
var tasks = flipflopNodes.Select(async node =>
@@ -250,15 +259,13 @@ namespace Serein.NodeFlow
}
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);
}
}
-
- //FlipFlopCts?.Dispose();
}
catch (Exception ex)
{
@@ -266,7 +273,6 @@ namespace Serein.NodeFlow
}
finally
{
- FlipFlopCts?.Dispose();
FlowState = RunState.Completion;
}
#endregion
@@ -290,72 +296,72 @@ namespace Serein.NodeFlow
///
private async Task FlipflopExecute(IFlowEnvironment flowEnvironment,SingleFlipflopNode singleFlipFlopNode)
{
- CancellationTokenSource cts = null;
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>)del :
- (Func>)del;
-
- bool t = md.ExplicitDatas.Length == 0;
try
{
-
-
- cts = Context.SereinIoc.Get(FlipFlopCtsName);
-
- while (!cts.IsCancellationRequested)
+
+ while (!_flipFlopCts.IsCancellationRequested)
{
-
- singleFlipFlopNode.FlowData = await singleFlipFlopNode.ExecutingAsync(context);
+ var newFlowData = await singleFlipFlopNode.ExecutingAsync(context);
+ await NodeModelBase.FlowRefreshDataOrInterrupt(context, singleFlipFlopNode, newFlowData); // 全局触发器触发后刷新该触发器的节点数据
if (singleFlipFlopNode.NextOrientation != ConnectionType.None)
{
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) // 排除未启用的后继节点
{
nextNodes[i].PreviousNode = singleFlipFlopNode;
- await nextNodes[i].StartExecute(context); // 执行流程节点的后继分支
+ await nextNodes[i].StartExecute(context); // 启动执行触发器后继分支的节点
}
}
}
- //if(t)
- //{
- // IFlipflopContext flipflopContext = await ((Func>)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>)del.Clone()).Invoke(md.ActingInstance, parameters);// 开始等待全局触发器的触发
- // var connectionType = flipflopContext.State.ToContentType();
- // if (connectionType != ConnectionType.None)
- // {
- // await GlobalFlipflopExecute(context, singleFlipFlopNode, connectionType, cts);
- // }
- //}
-
}
}
catch (Exception ex)
{
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>)del :
+ // (Func>)del;
+
+ //if(t)
+ //{
+ // IFlipflopContext flipflopContext = await ((Func>)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>)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
+
///
/// 全局触发器开始执行相关分支
///
@@ -364,9 +370,10 @@ namespace Serein.NodeFlow
/// 分支类型
///
public async Task GlobalFlipflopExecute(IDynamicContext context, SingleFlipflopNode singleFlipFlopNode,
- ConnectionType connectionType, CancellationTokenSource cts)
+
+ ConnectionType connectionType, CancellationTokenSource cts)
{
-
+
bool skip = true;
Stack stack = new Stack();
@@ -400,9 +407,9 @@ namespace Serein.NodeFlow
else
{
currentNode.FlowData = await currentNode.ExecutingAsync(context);
-
- if (currentNode.NextOrientation == ConnectionType.None)
+
+ if (currentNode.NextOrientation == ConnectionType.None)
{
break; // 不再执行
}
@@ -418,13 +425,13 @@ namespace Serein.NodeFlow
nextNodes[i].PreviousNode = currentNode;
stack.Push(nextNodes[i]);
}
- }
- }
+ }}
+#endif
- public void Exit()
- {
- ExitAction?.Invoke();
- }
}
}
+
+
+
+
diff --git a/NodeFlow/Model/CompositeConditionNode.cs b/NodeFlow/Model/CompositeConditionNode.cs
index 2aee3e8..4e02700 100644
--- a/NodeFlow/Model/CompositeConditionNode.cs
+++ b/NodeFlow/Model/CompositeConditionNode.cs
@@ -38,7 +38,7 @@ namespace Serein.NodeFlow.Model
break;
}
}
- return Task.FromResult( PreviousNode?.FlowData);
+ return Task.FromResult( PreviousNode?.GetFlowData());
}
diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs
index 8f82ffe..ad258eb 100644
--- a/NodeFlow/Model/SingleConditionNode.cs
+++ b/NodeFlow/Model/SingleConditionNode.cs
@@ -39,7 +39,7 @@ namespace Serein.NodeFlow.Model
}
else
{
- result = PreviousNode?.FlowData;
+ result = PreviousNode?.GetFlowData();
}
try
{
diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs
index 3faaaf5..048360b 100644
--- a/NodeFlow/Model/SingleExpOpNode.cs
+++ b/NodeFlow/Model/SingleExpOpNode.cs
@@ -21,7 +21,7 @@ namespace Serein.NodeFlow.Model
//public override async Task Executing(IDynamicContext context)
public override Task ExecutingAsync(IDynamicContext context)
{
- var data = PreviousNode?.FlowData;
+ var data = PreviousNode?.GetFlowData();
try
{
@@ -34,7 +34,7 @@ namespace Serein.NodeFlow.Model
}
else
{
- result = PreviousNode?.FlowData;
+ result = data;
}
NextOrientation = ConnectionType.IsSucceed;
@@ -44,7 +44,7 @@ namespace Serein.NodeFlow.Model
{
NextOrientation = ConnectionType.IsError;
RuningException = ex;
- return Task.FromResult(PreviousNode?.FlowData);
+ return Task.FromResult(data);
}
}
diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs
index 7ec4903..983905e 100644
--- a/NodeFlow/Model/SingleFlipflopNode.cs
+++ b/NodeFlow/Model/SingleFlipflopNode.cs
@@ -22,12 +22,11 @@ namespace Serein.NodeFlow.Model
public override async Task ExecutingAsync(IDynamicContext context)
{
#region 执行前中断
- if (DebugSetting.IsInterrupt && TryCreateInterruptTask(context, this, out Task? task)) // 执行触发前
+ if (DebugSetting.InterruptClass != InterruptClass.None && TryCreateInterruptTask(context, this, out Task? task)) // 执行触发前
{
string guid = this.Guid.ToString();
this.CancelInterruptCallback ??= () => context.FlowEnvironment.ChannelFlowInterrupt.TriggerSignal(guid);
var cancelType = await task!;
- task?.ToString();
await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支");
}
#endregion
@@ -42,24 +41,9 @@ namespace Serein.NodeFlow.Model
Task flipflopTask = md.ExplicitDatas.Length switch
{
0 => ((Func>)del).Invoke(md.ActingInstance),
- _ => ((Func>)del).Invoke(md.ActingInstance, GetParameters(context, md)), // 执行流程中的触发器方法时获取入参参数
+ _ => ((Func>)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;
- //if (flipflopTask == null)
- //{
- // throw new FlipflopException(base.MethodDetails.MethodName + "触发器返回值非 Task 类型");
- //}
+
IFlipflopContext flipflopContext = (await flipflopTask) ?? throw new FlipflopException("没有返回上下文");
NextOrientation = flipflopContext.State.ToContentType();
if(flipflopContext.TriggerData is null || flipflopContext.TriggerData.Type == Library.NodeFlow.Tool.TriggerType.Overtime)
@@ -72,7 +56,7 @@ namespace Serein.NodeFlow.Model
{
NextOrientation = ConnectionType.None;
RuningException = ex;
- throw;
+ return null;
}
catch (Exception ex)
{
diff --git a/WorkBench/MainWindow.xaml b/WorkBench/MainWindow.xaml
index 22992b3..89b2b62 100644
--- a/WorkBench/MainWindow.xaml
+++ b/WorkBench/MainWindow.xaml
@@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Serein.WorkBench"
xmlns:custom="clr-namespace:Serein.WorkBench.Node.View"
+ xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
Title="Dynamic Node Flow" Height="700" Width="1200"
AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver"
Loaded="Window_Loaded"
@@ -38,37 +39,26 @@
-
-
-
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs
index 4f22e42..042f9b8 100644
--- a/WorkBench/MainWindow.xaml.cs
+++ b/WorkBench/MainWindow.xaml.cs
@@ -25,6 +25,7 @@ using System.Windows.Media.Animation;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;
using System.Windows.Threading;
+using System.Xml.Linq;
using DataObject = System.Windows.DataObject;
namespace Serein.WorkBench
@@ -62,10 +63,15 @@ namespace Serein.WorkBench
///
/// 存储所有与节点有关的控件
+ /// 任何情景下都尽量避免直接操作 ViewModel 中的 NodeModel 节点,
+ /// 而是应该调用 FlowEnvironment 提供接口进行操作,
+ /// 因为 Workbench 应该更加关注UI视觉效果,而非直接干扰流程环境运行的逻辑。
+ /// 之所以暴露 NodeModel 属性,因为有些场景下不可避免的需要直接获取节点的属性。
///
private Dictionary NodeControls { get; } = [];
+
///
- /// 存储所有的连接
+ /// 存储所有的连接。考虑集成在运行环境中。
///
private List Connections { get; } = [];
@@ -174,9 +180,14 @@ namespace Serein.WorkBench
FlowEnvironment.OnNodeRemote += FlowEnvironment_NodeRemoteEvent;
FlowEnvironment.OnFlowRunComplete += FlowEnvironment_OnFlowRunComplete;
+
+ FlowEnvironment.OnMonitorObjectChange += FlowEnvironment_OnMonitorObjectChange;
+ FlowEnvironment.OnNodeInterruptStateChange += FlowEnvironment_OnNodeInterruptStateChange;
+ FlowEnvironment.OnNodeInterruptTrigger += FlowEnvironment_OnNodeInterruptTrigger;
+
}
-
+
private void InitUI()
{
@@ -191,13 +202,6 @@ namespace Serein.WorkBench
//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 窗体加载方法
@@ -295,41 +299,6 @@ namespace Serein.WorkBench
}
- ///
- /// 运行环境成功加载了节点,需要在画布上创建节点控件
- ///
- ///
- ///
- //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); // 配置节点,并放置在画布上
- //}
-
///
/// 节点连接关系变更
///
@@ -342,10 +311,8 @@ namespace Serein.WorkBench
{
string fromNodeGuid = eventArgs.FromNodeGuid;
string toNodeGuid = eventArgs.ToNodeGuid;
- if (!NodeControls.TryGetValue(fromNodeGuid, out var fromNode) || !NodeControls.TryGetValue(toNodeGuid, out var toNode))
- {
- return;
- }
+ NodeControlBase fromNode = GuidToControl(fromNodeGuid);
+ NodeControlBase toNode = GuidToControl(toNodeGuid);
ConnectionType connectionType = eventArgs.ConnectionType;
if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create)
{
@@ -375,7 +342,7 @@ namespace Serein.WorkBench
.ToList();
foreach (var connection in removeConnections)
{
- connection.RemoveFromCanvas(FlowChartCanvas);
+ connection.RemoveFromCanvas();
Connections.Remove(connection);
}
}
@@ -389,14 +356,7 @@ namespace Serein.WorkBench
private void FlowEnvironment_NodeRemoteEvent(NodeRemoteEventArgs eventArgs)
{
var nodeGuid = eventArgs.NodeGuid;
- if (!NodeControls.TryGetValue(nodeGuid, out NodeControlBase nodeControl))
- {
- return;
- }
- if(nodeControl is null)
- {
- return;
- }
+ NodeControlBase nodeControl = GuidToControl(nodeGuid);
if (selectNodeControls.Count > 0)
{
if (selectNodeControls.Contains(nodeControl))
@@ -479,23 +439,13 @@ namespace Serein.WorkBench
{
string oldNodeGuid = eventArgs.OldNodeGuid;
string newNodeGuid = eventArgs.NewNodeGuid;
- if (!NodeControls.TryGetValue(newNodeGuid, out var newStartNodeControl))
- {
- return;
- }
- if (newStartNodeControl == null)
- {
- return;
- }
+ NodeControlBase newStartNodeControl = GuidToControl(newNodeGuid);
+
if (!string.IsNullOrEmpty(oldNodeGuid))
{
- NodeControls.TryGetValue(oldNodeGuid, out var oldStartNodeControl);
- if (oldStartNodeControl != null)
- {
- oldStartNodeControl.BorderBrush = Brushes.Black;
- oldStartNodeControl.BorderThickness = new Thickness(0);
- }
-
+ NodeControlBase oldStartNodeControl = GuidToControl(oldNodeGuid);
+ oldStartNodeControl.BorderBrush = Brushes.Black;
+ oldStartNodeControl.BorderThickness = new Thickness(0);
}
newStartNodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"));
@@ -504,10 +454,89 @@ namespace Serein.WorkBench
}
+
+ ///
+ /// 被监视的对象发生改变(节点执行了一次)
+ ///
+ ///
+ 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);
+ }
+
+ }
+
+
+ ///
+ /// 节点中断状态改变。
+ ///
+ ///
+ 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;
+ }
+
+
+ }
+
+ ///
+ /// 节点触发了中断
+ ///
+ ///
+ ///
+ private void FlowEnvironment_OnNodeInterruptTrigger(NodeInterruptTriggerEventArgs eventArgs)
+ {
+ string nodeGuid = eventArgs.NodeGuid;
+ NodeControlBase nodeControl = GuidToControl(nodeGuid);
+
+ Console.WriteLine("节点触发了中断");
+ }
+
+
+ ///
+ /// Guid 转 NodeControl
+ ///
+ /// 节点Guid
+ /// 节点Model
+ /// 无法获取节点、Guid/节点为null时报错
+ 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
+
#region 加载项目文件后触发事件相关方法
-
+
///
/// 运行环节加载了项目文件,需要创建节点控件
///
@@ -634,7 +663,7 @@ namespace Serein.WorkBench
///
/// 配置节点右键菜单
///
- ///
+ /// 任何情景下都尽量避免直接操作 ViewModel 中的 NodeModel 节点,而是应该调用 FlowEnvironment 提供接口进行操作。 因为 Workbench 应该更加关注UI视觉效果,而非直接干扰流程环境运行的逻辑。 之所以暴露 NodeModel 属性,因为有些场景下不可避免的需要直接获取节点的属性。
private void ConfigureContextMenu(NodeControlBase nodeControl)
{
var contextMenu = new ContextMenu();
@@ -650,29 +679,42 @@ namespace Serein.WorkBench
}
var nodeGuid = nodeControl?.ViewModel?.Node?.Guid;
- MenuItem? debugMenu = null;
- debugMenu = CreateMenuItem("在此中断", (s, e) =>
- {
- if (nodeControl!.ViewModel.DebugSetting.IsInterrupt)
- {
- nodeControl.ViewModel.IsInterrupt = false;
- debugMenu!.Header = "在此中断";
- }
- else
- {
- nodeControl.ViewModel.IsInterrupt = true;
- debugMenu!.Header = "取消中断";
- }
- });
- contextMenu.Items.Add(debugMenu);
+ #region 右键菜单功能 - 中断
+ 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.RemoteNode(nodeGuid)));
-
-
-
contextMenu.Items.Add(CreateMenuItem("添加 真分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsSucceed)));
contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsFail)));
contextMenu.Items.Add(CreateMenuItem("添加 异常分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsError)));
@@ -680,20 +722,40 @@ namespace Serein.WorkBench
+ #region 右键菜单功能 - 控件对齐
+
var AvoidMenu = new MenuItem();
- //AvoidMenu.Items.Add(CreateMenuItem("水平对齐", (s, e) => AlignHorizontallyAvoidOverlap(selectNodeControls)));
- //AvoidMenu.Items.Add(CreateMenuItem("垂直对齐", (s, e) => VerticalAlignAvoidOverlap(selectNodeControls)));
- AvoidMenu.Items.Add(CreateMenuItem("群组对齐", (s, e) => {
- AlignControlsWithGrouping(selectNodeControls);
- UpdateConnectedLines();
+ AvoidMenu.Items.Add(CreateMenuItem("群组对齐", (s, e) =>
+ {
+ AlignControlsWithGrouping(selectNodeControls, AlignMode.Grouping);
}));
AvoidMenu.Items.Add(CreateMenuItem("规划对齐", (s, e) =>
{
- AlignControlsWithDynamicProgramming(selectNodeControls);
- UpdateConnectedLines();
+ AlignControlsWithGrouping(selectNodeControls, AlignMode.Planning);
}));
+ 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 = "对齐";
- contextMenu.Items.Add(AvoidMenu);
+ contextMenu.Items.Add(AvoidMenu);
+
+
+ #endregion
nodeControl.ContextMenu = contextMenu;
}
@@ -747,6 +809,21 @@ namespace Serein.WorkBench
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
#region 拖拽DLL文件到左侧功能区,加载相关节点清单
@@ -1205,7 +1282,9 @@ namespace Serein.WorkBench
{
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);
//}
- 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
+ public enum AlignMode
+ {
+ ///
+ /// 水平对齐
+ ///
+ Horizontal,
+ ///
+ /// 垂直对齐
+ ///
+ Vertical,
+ ///
+ /// 水平中心对齐
+ ///
+ HorizontalCenter,
+ ///
+ /// 垂直中心对齐
+ ///
+ VerticalCenter,
+
+ ///
+ /// 规划对齐
+ ///
+ Planning,
+ ///
+ /// 群组对齐
+ ///
+ Grouping,
+ }
+
+
+ public void AlignControlsWithGrouping(List 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 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 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 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
#region 窗体静态方法
@@ -1936,6 +2132,7 @@ namespace Serein.WorkBench
private void ButtonDebugFlipflopNode_Click(object sender, RoutedEventArgs e)
{
FlowEnvironment?.Exit(); // 在运行平台上点击了退出
+
}
///
@@ -2079,19 +2276,19 @@ namespace Serein.WorkBench
public static Connection Draw(Canvas canvas, Connection connection)
{
connection.Canvas = canvas;
- UpdateBezierLine(canvas, connection);
+ UpdateBezierLineInDragging(canvas, connection);
//MakeDraggable(canvas, connection, connection.Start);
//MakeDraggable(canvas, connection, connection.End);
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.Children.Add(connection.BezierPath);
}
if (connection.ArrowPath == null)
{
- connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), Fill = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 };
+ 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.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)
return;
@@ -2117,14 +2314,14 @@ namespace Serein.WorkBench
{
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.Children.Add(connection.BezierPath);
}
if (connection != null && connection.ArrowPath == null)
{
- connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), Fill = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 };
+ 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.Children.Add(connection.ArrowPath);
}
@@ -2188,22 +2385,24 @@ namespace Serein.WorkBench
public required NodeControlBase Start { 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(ArrowPath); // 移除线
- _animationStoryboard?.Stop(); // 停止动画
+ Canvas.Children.Remove(BezierPath); // 移除线
+ Canvas.Children.Remove(ArrowPath); // 移除线
}
+ ///
+ /// 重新绘制
+ ///
public void Refresh()
{
- // BsControl.Draw(Canvas, this);
BezierLineDrawer.UpdateBezierLine(Canvas, Start, End, BezierPath, ArrowPath);
}
}
+
+
public static class BezierLineDrawer
{
public enum Localhost
@@ -2214,7 +2413,14 @@ namespace Serein.WorkBench
Bottom,
}
- // 绘制曲线
+ ///
+ /// 绘制曲线
+ ///
+ /// 所在画布
+ /// 起始控件
+ /// 终点控件
+ /// 曲线
+ /// 箭头
public static void UpdateBezierLine(Canvas canvas,
FrameworkElement startElement,
FrameworkElement endElement,
@@ -2223,6 +2429,8 @@ namespace Serein.WorkBench
{
Point startPoint = startElement.TranslatePoint(new Point(startElement.ActualWidth / 2, startElement.ActualHeight / 2), canvas);
Point endPoint = CalculateEndpointOutsideElement(endElement, canvas, startPoint, out Localhost localhost);
+ // 根据终点位置决定起点位置 (位于控件的边缘)
+ startPoint = CalculateEdgePoint(startElement, localhost, canvas);
PathFigure pathFigure = new PathFigure { StartPoint = startPoint };
BezierSegment bezierSegment;
@@ -2246,8 +2454,11 @@ namespace Serein.WorkBench
Point3 = endPoint,
};
}
-
-
+ var minZ = canvas.Children.OfType()//linq语句,取Zindex的最大值
+ .Select(x => Grid.GetZIndex(x))
+ .Min();
+ Grid.SetZIndex(bezierPath, minZ - 1);
+ // Canvas.SetZIndex(bezierPath, 0);
pathFigure.Segments.Add(bezierSegment);
PathGeometry pathGeometry = new PathGeometry();
@@ -2260,7 +2471,7 @@ namespace Serein.WorkBench
private static Point CalculateBezierTangent(Point startPoint, Point controlPoint1, Point controlPoint2, Point endPoint)
{
- double t = 10.0; // 末端点
+ double t = 11; // 末端点
// 计算贝塞尔曲线在 t = 1 处的一阶导数
double dx = 3 * Math.Pow(1 - t, 2) * (controlPoint1.X - startPoint.X) +
@@ -2301,6 +2512,31 @@ namespace Serein.WorkBench
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)
{
@@ -2322,35 +2558,57 @@ namespace Serein.WorkBench
(false, false) => Localhost.Top,
};
- double halfWidth = element.ActualWidth / 2 + 6;
- double halfHeight = element.ActualHeight / 2 + 6;
- double margin = 0;
+ double halfWidth = element.ActualWidth / 2 + 10;
+ double halfHeight = element.ActualHeight / 2 + 10;
+
+ #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)
{
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)
{
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)
{
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)
{
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;
}
- public static SolidColorBrush GetStroke(ConnectionType currentConnectionType)
+ public static SolidColorBrush GetLineColor(ConnectionType currentConnectionType)
{
return currentConnectionType switch
{
diff --git a/WorkBench/Node/NodeControlViewModelBase.cs b/WorkBench/Node/NodeControlViewModelBase.cs
index eea3145..191ec64 100644
--- a/WorkBench/Node/NodeControlViewModelBase.cs
+++ b/WorkBench/Node/NodeControlViewModelBase.cs
@@ -67,23 +67,35 @@ namespace Serein.WorkBench.Node.ViewModel
}
}
+ private bool isInterrupt;
public bool IsInterrupt
{
- get => Node.DebugSetting.IsInterrupt;
+ get => isInterrupt;
set
{
- if (value)
- {
- Node.Interrupt();
- }
- else
- {
- Node.CancelInterrupt();
- }
+ isInterrupt = value;
OnPropertyChanged(nameof(IsInterrupt));
}
}
+
+ //public bool IsInterrupt
+ //{
+ // get => Node.DebugSetting.IsInterrupt;
+ // set
+ // {
+ // if (value)
+ // {
+ // Node.Interrupt();
+ // }
+ // else
+ // {
+ // Node.CancelInterrupt();
+ // }
+ // OnPropertyChanged(nameof(IsInterrupt));
+ // }
+ //}
+
//public bool IsProtectionParameter
//{
// get => MethodDetails.IsProtectionParameter;
diff --git a/WorkBench/Node/View/DllControlControl.xaml b/WorkBench/Node/View/DllControlControl.xaml
index 77403bb..2db012e 100644
--- a/WorkBench/Node/View/DllControlControl.xaml
+++ b/WorkBench/Node/View/DllControlControl.xaml
@@ -14,7 +14,7 @@
-
+
@@ -23,13 +23,13 @@
-
+
+
-
+
diff --git a/WorkBench/Serein.WorkBench.csproj b/WorkBench/Serein.WorkBench.csproj
index 94ef726..96ae77d 100644
--- a/WorkBench/Serein.WorkBench.csproj
+++ b/WorkBench/Serein.WorkBench.csproj
@@ -26,12 +26,14 @@
+
+
diff --git a/WorkBench/Themes/InputDialog.xaml b/WorkBench/Themes/InputDialog.xaml
new file mode 100644
index 0000000..ef5d497
--- /dev/null
+++ b/WorkBench/Themes/InputDialog.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
diff --git a/WorkBench/Themes/InputDialog.xaml.cs b/WorkBench/Themes/InputDialog.xaml.cs
new file mode 100644
index 0000000..0f4762c
--- /dev/null
+++ b/WorkBench/Themes/InputDialog.xaml.cs
@@ -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
+{
+ ///
+ /// InputDialog.xaml 的交互逻辑
+ ///
+ 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(); // 关闭窗口
+ }
+ }
+}
diff --git a/WorkBench/Themes/ObjectViewerControl.xaml b/WorkBench/Themes/ObjectViewerControl.xaml
new file mode 100644
index 0000000..ab28307
--- /dev/null
+++ b/WorkBench/Themes/ObjectViewerControl.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WorkBench/Themes/ObjectViewerControl.xaml.cs b/WorkBench/Themes/ObjectViewerControl.xaml.cs
new file mode 100644
index 0000000..116477a
--- /dev/null
+++ b/WorkBench/Themes/ObjectViewerControl.xaml.cs
@@ -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
+ {
+ ///
+ /// 属性名称
+ ///
+ public string Name { get; set; }
+ ///
+ /// 属性类型
+ ///
+ public TreeItemType ItemType { get; set; }
+ ///
+ /// 数据类型
+ ///
+ public Type DataType { get; set; }
+ ///
+ /// 数据
+ ///
+ public object DataValue { get; set; }
+ ///
+ /// 数据路径
+ ///
+ public string DataPath { get; set; }
+ }
+
+
+ ///
+ /// ObjectViewerControl.xaml 的交互逻辑
+ ///
+ public partial class ObjectViewerControl : UserControl
+ {
+ private object _objectInstance;
+ public string NodeGuid { get;set; }
+
+ // private NodeModelBase _nodeFlowData;
+
+ public ObjectViewerControl()
+ {
+ InitializeComponent();
+ }
+
+ ///
+ /// 加载对象信息,展示其成员
+ ///
+ /// 要展示的对象
+ //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)
+ {
+
+ }
+
+
+ ///
+ /// 刷新对象属性树
+ ///
+ 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);
+ }
+ }
+
+
+
+
+
+
+
+
+
+ ///
+ /// 添加父节点
+ ///
+ ///
+ private static void AddPlaceholderNode(TreeViewItem node)
+ {
+ node.Items.Add(new TreeViewItem { Header = "Loading..." });
+ }
+
+ ///
+ /// 展开子项事件
+ ///
+ ///
+ ///
+ 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);
+ }
+ }
+ }
+
+ ///
+ /// 反射对象数据添加子节点
+ ///
+ ///
+ ///
+ ///
+ 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; // 设置子项节点的事件
+ //}
+
+ }
+ }
+
+ ///
+ /// 配置右键菜单功能
+ ///
+ ///
+ ///
+ ///
+ 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;
+ }
+
+ ///
+ /// 获取属性类型的成员
+ ///
+ ///
+ ///
+ ///
+ private static string GetPropertyValue(object obj, PropertyInfo property)
+ {
+ try
+ {
+ var value = property.GetValue(obj);
+ return value?.ToString() ?? "null";
+ }
+ catch
+ {
+ return "Error";
+ }
+ }
+
+
+ ///
+ /// 获取字段类型的成员
+ ///
+ ///
+ ///
+ ///
+ private static string GetFieldValue(object obj, FieldInfo field)
+ {
+ try
+ {
+ var value = field.GetValue(obj);
+ return value?.ToString() ?? "null";
+ }
+ catch
+ {
+ return "Error";
+ }
+ }
+
+ ///
+ /// 根据成员类别配置右键菜单
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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;
+ }
+
+ ///
+ /// 获取当前节点的完整路径,例如 "node1.node2.node3.node4"
+ ///
+ /// 目标节点
+ /// 节点路径
+ 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();
+ }
+ }
+
+ ///
+ /// 获取指定节点的父级节点
+ ///
+ /// 目标节点
+ /// 父节点
+ 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 action)
+ {
+ var inputDialog = new InputDialog();
+ inputDialog.Closed += (s, e) =>
+ {
+ if (inputDialog.DialogResult == true)
+ {
+ string userInput = inputDialog.InputValue;
+ action?.Invoke(userInput);
+ }
+ };
+ inputDialog.ShowDialog();
+ return inputDialog;
+
+ }
+
+
+
+
+
+
+ /////
+ ///// 刷新按钮的点击事件
+ /////
+ //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;
+ // });
+ // }
+
+ //}
+
+
+
+
+
+ }
+}
+
+
diff --git a/WorkBench/Themes/ObjectViewerControl1.xaml b/WorkBench/Themes/ObjectViewerControl1.xaml
new file mode 100644
index 0000000..bdd3cf4
--- /dev/null
+++ b/WorkBench/Themes/ObjectViewerControl1.xaml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/WorkBench/Themes/ObjectViewerControl1.xaml.cs b/WorkBench/Themes/ObjectViewerControl1.xaml.cs
new file mode 100644
index 0000000..567db2d
--- /dev/null
+++ b/WorkBench/Themes/ObjectViewerControl1.xaml.cs
@@ -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
+{
+ ///
+ /// ObjectViewerWindow.xaml 的交互逻辑
+ ///
+ 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();
+ }
+ }
+}
diff --git a/WorkBench/Themes/TypeViewerWindow.xaml b/WorkBench/Themes/TypeViewerWindow.xaml
index 792888c..36ff4ab 100644
--- a/WorkBench/Themes/TypeViewerWindow.xaml
+++ b/WorkBench/Themes/TypeViewerWindow.xaml
@@ -6,6 +6,7 @@
xmlns:local="clr-namespace:Serein.WorkBench.Themes"
mc:Ignorable="d"
Topmost="True"
+
Title="TypeViewerWindow" Height="300" Width="300">
diff --git a/WorkBench/Themes/TypeViewerWindow.xaml.cs b/WorkBench/Themes/TypeViewerWindow.xaml.cs
index 3354ff3..cb0da9b 100644
--- a/WorkBench/Themes/TypeViewerWindow.xaml.cs
+++ b/WorkBench/Themes/TypeViewerWindow.xaml.cs
@@ -32,7 +32,7 @@ namespace Serein.WorkBench.Themes
if (Type == null)
return;
- TypeNodeDetails typeNodeDetails = new TypeNodeDetails
+ NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
{
Name = Type.Name,
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...")
{
item.Items.Clear();
- if (item.Tag is TypeNodeDetails typeNodeDetails)
+ if (item.Tag is NodeFlowDataObjectDetails typeNodeDetails)
{
AddMembersToTreeNode(item, typeNodeDetails.DataType);
}
@@ -103,7 +103,7 @@ namespace Serein.WorkBench.Themes
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
if (member is PropertyInfo property)
{
- TypeNodeDetails typeNodeDetails = new TypeNodeDetails
+ NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
{
ItemType = TreeItemType.Property,
DataType = property.PropertyType,
@@ -124,7 +124,7 @@ namespace Serein.WorkBench.Themes
}
else if (member is MethodInfo method)
{
- TypeNodeDetails typeNodeDetails = new TypeNodeDetails
+ NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
{
ItemType = TreeItemType.Method,
DataType = typeof(MethodInfo),
@@ -139,7 +139,7 @@ namespace Serein.WorkBench.Themes
}
else if (member is FieldInfo field)
{
- TypeNodeDetails typeNodeDetails = new TypeNodeDetails
+ NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
{
ItemType = TreeItemType.Field,
DataType = field.FieldType,
@@ -201,7 +201,7 @@ namespace Serein.WorkBench.Themes
if (node == null)
return string.Empty;
- TypeNodeDetails typeNodeDetails = (TypeNodeDetails)node.Tag;
+ NodeFlowDataObjectDetails typeNodeDetails = (NodeFlowDataObjectDetails)node.Tag;
var parent = GetParentTreeViewItem(node);
if (parent != null)
{
@@ -210,10 +210,8 @@ namespace Serein.WorkBench.Themes
}
else
{
- return "";
-
-
// 没有父节点,则说明这是根节点,直接返回 Header
+ return "";
// return typeNodeDetails.Name.ToString();
}
}
@@ -235,7 +233,7 @@ namespace Serein.WorkBench.Themes
- public class TypeNodeDetails
+ public class NodeFlowDataObjectDetails
{
///
/// 属性名称
@@ -245,8 +243,6 @@ namespace Serein.WorkBench.Themes
/// 属性类型
///
public TreeItemType ItemType { get; set; }
-
-
///
/// 数据类型
///