对象预览器支持了值类型集合成员的简单预览

This commit is contained in:
fengjiayi
2024-09-24 22:39:43 +08:00
parent 8a502b77d4
commit 06f6d2f34b
28 changed files with 1674 additions and 859 deletions

View File

@@ -9,22 +9,22 @@ namespace Serein.Library.Core.NodeFlow
/// </summary>
public class DynamicContext: IDynamicContext
{
public DynamicContext(ISereinIOC sereinIoc, IFlowEnvironment flowEnvironment)
public DynamicContext(/*ISereinIOC sereinIoc, */IFlowEnvironment flowEnvironment)
{
SereinIoc = sereinIoc;
FlowEnvironment = flowEnvironment;
//SereinIoc = sereinIoc;
Env = flowEnvironment;
}
public NodeRunCts NodeRunCts { get; set; }
public ISereinIOC SereinIoc { get; }
public IFlowEnvironment FlowEnvironment { get; }
//public ISereinIOC SereinIoc { get; }
public IFlowEnvironment Env { get; }
public Task CreateTimingTask(Action action, int time = 100, int count = -1)
{
if (NodeRunCts == null)
{
NodeRunCts = SereinIoc.GetOrRegisterInstantiate<NodeRunCts>();
NodeRunCts = Env.IOC.GetOrRegisterInstantiate<NodeRunCts>();
}
// 使用局部变量,避免捕获外部的 `action`
Action localAction = action;

View File

@@ -13,21 +13,21 @@ namespace Serein.Library.Framework.NodeFlow
/// </summary>
public class DynamicContext : IDynamicContext
{
public DynamicContext(ISereinIOC sereinIoc, IFlowEnvironment flowEnvironment)
public DynamicContext(/*ISereinIOC sereinIoc,*/ IFlowEnvironment flowEnvironment)
{
SereinIoc = sereinIoc;
FlowEnvironment = flowEnvironment;
// SereinIoc = sereinIoc;
Env = flowEnvironment;
}
public NodeRunCts NodeRunCts { get; set; }
public ISereinIOC SereinIoc { get; }
public IFlowEnvironment FlowEnvironment { get; }
// public ISereinIOC SereinIoc { get; }
public IFlowEnvironment Env { get; }
public Task CreateTimingTask(Action action, int time = 100, int count = -1)
{
if(NodeRunCts == null)
{
NodeRunCts = SereinIoc.GetOrRegisterInstantiate<NodeRunCts>();
NodeRunCts = Env.IOC.GetOrRegisterInstantiate<NodeRunCts>();
}
// 使用局部变量,避免捕获外部的 `action`
Action localAction = action;

View File

@@ -4,10 +4,23 @@ using System.Threading.Tasks;
namespace Serein.Library.Api
{
/// <summary>
/// 流程上下文
/// </summary>
public interface IDynamicContext
{
IFlowEnvironment FlowEnvironment { get; }
ISereinIOC SereinIoc { get; }
Task CreateTimingTask(Action action, int time = 100, int count = -1);
/// <summary>
/// 运行环境
/// </summary>
IFlowEnvironment Env { get; }
/// <summary>
/// 定时循环触发
/// </summary>
/// <param name="callback"></param>
/// <param name="time"></param>
/// <param name="count"></param>
/// <returns></returns>
Task CreateTimingTask(Action callback, int time = 100, int count = -1);
}
}

View File

@@ -47,6 +47,26 @@ namespace Serein.Library.Api
/// </summary>
/// <param name="eventArgs"></param>
public delegate void StartNodeChangeHandler(StartNodeChangeEventArgs eventArgs);
/// <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 ExpInterruptTriggerHandler(InterruptTriggerEventArgs eventArgs);
#endregion
#region
@@ -210,45 +230,35 @@ namespace Serein.Library.Api
/// </summary>
public string NewNodeGuid { get; private set; }
}
#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 ExpInterruptTriggerHandler(InterruptTriggerEventArgs eventArgs);
/// <summary>
/// 监视的节点数据发生变化
/// </summary>
public class MonitorObjectEventArgs : FlowEventArgs
{
public MonitorObjectEventArgs(string nodeGuid,object newData)
public enum ObjSourceType
{
NodeFlowData,
IOCObj,
}
public MonitorObjectEventArgs(string nodeGuid, object monitorData, ObjSourceType objSourceType)
{
NodeGuid = nodeGuid;
NewData = newData;
NewData = monitorData;
ObjSource = objSourceType;
}
/// <summary>
/// 中断的节点Guid
/// </summary>
public string NodeGuid { get; protected set; }
public ObjSourceType ObjSource { get; protected set; }
/// <summary>
/// 新的数据
/// </summary>
public object NewData { get; protected set; }
public object NewData { get; protected set; }
}
/// <summary>
@@ -256,7 +266,7 @@ namespace Serein.Library.Api
/// </summary>
public class NodeInterruptStateChangeEventArgs : FlowEventArgs
{
public NodeInterruptStateChangeEventArgs(string nodeGuid,InterruptClass @class)
public NodeInterruptStateChangeEventArgs(string nodeGuid, InterruptClass @class)
{
NodeGuid = nodeGuid;
Class = @class;
@@ -283,6 +293,10 @@ namespace Serein.Library.Api
/// 表达式中断
/// </summary>
Exp,
/// <summary>
/// 对象监视中断
/// </summary>
Obj,
}
public InterruptTriggerEventArgs(string nodeGuid, string expression, InterruptTriggerType type)
@@ -299,26 +313,62 @@ namespace Serein.Library.Api
public string Expression { get; protected set; }
public InterruptTriggerType Type { get; protected set; }
}
#endregion
/// <summary>
/// IOC容器发生变化
/// </summary>
public delegate void IOCMembersChangedHandler();
/// <summary>
/// 流程事件签名基类
/// </summary>
public class IOCMembersChangedEventArgs : FlowEventArgs
{
public enum EventType
{
/// <summary>
/// 登记了类型
/// </summary>
Registered,
/// <summary>
/// 构建了类型
/// </summary>
Completeuild,
}
public IOCMembersChangedEventArgs(Type[] types, object[] dependencies, object[] unfinishedDependencies)
{
this.Types = types;
this.Dependencies = dependencies;
this.UnfinishedDependencies = unfinishedDependencies;
}
public Type[] Types { get; protected set; }
public object[] Dependencies { get; private set; }
public object[] UnfinishedDependencies { get; private set; }
}
public interface IFlowEnvironment
{
#region
/// <summary>
/// IOC容器
/// </summary>
ISereinIOC IOC { get; }
/// <summary>
/// 环境名称
/// </summary>
string EnvName {get;}
string EnvName { get; }
/// <summary>
/// 是否全局中断
/// </summary>
bool IsGlobalInterrupt { get; }
/// <summary>
/// 设置中断时的中断级别
/// </summary>
//InterruptClass EnvInterruptClass { get; set; }
bool IsGlobalInterrupt { get; }
#endregion
/// <summary>
/// 调试管理
/// </summary>
//ChannelFlowInterrupt ChannelFlowInterrupt { get; set; }
#region
/// <summary>
/// 加载Dll
@@ -371,6 +421,10 @@ namespace Serein.Library.Api
event ExpInterruptTriggerHandler OnInterruptTrigger;
#endregion
#region Workbench
/// <summary>
/// 保存当前项目
/// </summary>
@@ -397,13 +451,20 @@ namespace Serein.Library.Api
/// <param name="name"></param>
/// <param name="md"></param>
/// <returns></returns>
bool TryGetMethodDetails(string methodName,out MethodDetails md);
bool TryGetMethodDetails(string methodName, out MethodDetails md);
/// <summary>
/// 开始运行
/// </summary>
Task StartAsync();
/// <summary>
/// 从选定的节点开始运行
/// </summary>
/// <param name="startNodeGuid"></param>
/// <returns></returns>
Task StartFlowInSelectNodeAsync(string startNodeGuid);
/// <summary>
/// 结束运行
/// </summary>
@@ -442,7 +503,6 @@ namespace Serein.Library.Api
/// <param name="nodeGuid">待移除的节点Guid</param>
void RemoteNode(string nodeGuid);
/// <summary>
/// 设置节点中断级别
/// </summary>
@@ -452,38 +512,42 @@ namespace Serein.Library.Api
bool SetNodeInterrupt(string nodeGuid, InterruptClass interruptClass);
/// <summary>
/// 添加中断表达式
/// 添加作用于某个对象的中断表达式
/// </summary>
/// <param name="nodeGuid"></param>
/// <param name="expression"></param>
/// <returns></returns>
bool AddInterruptExpression(string nodeGuid,string expression);
bool AddInterruptExpression(object obj, string expression);
/// <summary>
/// 添加作用于指定节点的中断表达式
/// </summary>
/// <param name="nodeGuid"></param>
/// <param name="expression"></param>
/// <returns></returns>
// bool AddInterruptExpression(string nodeGuid,string expression);
// <summary>
// 设置节点数据监视状态
// </summary>
// <param name="nodeGuid">需要监视的节点Guid</param>
// <param name="isMonitor">是否监视</param>
// void SetNodeFLowDataMonitorState(string nodeGuid, bool isMonitor);
/// <summary>
/// 设置节点数据监视状态
/// 监视指定对象
/// </summary>
/// <param name="nodeGuid">需要监视的节点Guid</param>
/// <param name="isMonitor">是否监视</param>
void SetNodeFLowDataMonitorState(string nodeGuid, bool isMonitor);
/// <param name="obj">需要监视的对象</param>
/// <param name="isMonitor">是否启用监视</param>
void SetMonitorObjState(object obj, bool isMonitor);
/// <summary>
/// 流程启动器调用,节点数据更新通知
/// 检查一个对象是否处于监听状态如果是则传出与该对象相关的表达式用于中断如果不是则返回false。
/// </summary>
/// <param name="nodeGuid">更新了数据的节点Guid</param>
/// <param name="flowData">更新的数据</param>
void FlowDataNotification(string nodeGuid, object flowData);
/// <param name="obj">判断的对象</param>
/// <param name="exps">表达式</param>
/// <returns></returns>
bool CheckObjMonitorState(object obj, out List<string> exps);
/// <summary>
/// 流程启动器调用,节点触发了中断
/// </summary>
/// <param name="nodeGuid">被中断的节点Guid</param>
/// <param name="expression">被触发的表达式</param>
/// <param name="type">中断类型。0主动监视1表达式</param>
void TriggerInterrupt(string nodeGuid,string expression, InterruptTriggerEventArgs.InterruptTriggerType type);
/// <summary>
/// 全局中断
@@ -494,6 +558,26 @@ namespace Serein.Library.Api
Task<CancelType> GetOrCreateGlobalInterruptAsync();
#endregion
#region Start
/// <summary>
/// 流程启动器调用,监视数据更新通知
/// </summary>
/// <param name="nodeGuid">更新了数据的节点Guid</param>
/// <param name="flowData">更新的数据</param>
void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType);
/// <summary>
/// 流程启动器调用,节点触发了中断
/// </summary>
/// <param name="nodeGuid">被中断的节点Guid</param>
/// <param name="expression">被触发的表达式</param>
/// <param name="type">中断类型。0主动监视1表达式</param>
void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type);
#endregion
}
}

View File

@@ -15,31 +15,63 @@ namespace Serein.Library.Api
/// 注册实例
/// </summary>
ISereinIOC Register(Type type, params object[] parameters);
/// <summary>
/// 注册实例
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parameters"></param>
/// <returns></returns>
ISereinIOC Register<T>(params object[] parameters);
/// <summary>
/// 注册接口的实例
/// </summary>
/// <typeparam name="TService"></typeparam>
/// <typeparam name="TImplementation"></typeparam>
/// <param name="parameters"></param>
/// <returns></returns>
ISereinIOC Register<TService, TImplementation>(params object[] parameters) where TImplementation : TService;
/// <summary>
/// 获取或创建并注入目标类型
/// 获取或创建并注入目标类型会记录到IOC容器中。
/// </summary>
T GetOrRegisterInstantiate<T>();
/// <summary>
/// 获取或创建并注入目标类型
/// 获取或创建并注入目标类型会记录到IOC容器中。
/// </summary>
object GetOrRegisterInstantiate(Type type);
/// <summary>
/// 获取类型的实例
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
object Get(Type type);
T Get<T>(string name);
object Get(string name);
void CustomRegisterInstance(string name, object instance, bool needInjectProperty = true);
ISereinIOC Run<T>(string name, Action<T> action);
/// <summary>
/// 获取指定名称的实例
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
T Get<T>(string key);
/// <summary>
/// 创建目标类型的对象, 并注入依赖项
/// 通过名称注册实例
/// </summary>
/// <param name="key">注入名称</param>
/// <param name="instance">实例对象</param>
/// <param name="needInjectProperty">是否需要注入依赖项</param>
void CustomRegisterInstance(string key, object instance, bool needInjectProperty = true);
/// <summary>
/// 用于临时实例的创建不注册到IOC容器中依赖项注入失败时也不记录。
/// </summary>
object Instantiate(Type type, params object[] parameters);
/// <summary>
/// 实例化注册的类型,并注入依赖项
/// </summary>
/// <returns></returns>
ISereinIOC Build();
ISereinIOC Run<T>(Action<T> action);
ISereinIOC Run<T1, T2>(Action<T1, T2> action);
ISereinIOC Run<T1, T2, T3>(Action<T1, T2, T3> action);

View File

@@ -16,7 +16,7 @@ namespace Serein.Library.Entity
/// <summary>
/// 是否监视数据改变
/// </summary>
public bool IsMonitorFlowData { get; set; } = false;
// public bool IsMonitorFlowData { get; set; } = false;
/// <summary>
/// 中断级别,暂时停止继续执行后继分支。
@@ -26,8 +26,7 @@ namespace Serein.Library.Entity
/// <summary>
/// 中断表达式
/// </summary>
public List<string> InterruptExpressions { get; } = new List<string>();
// public List<string> InterruptExpressions { get; } = new List<string>();
/// <summary>
/// 取消中断的回调函数

View File

@@ -14,7 +14,7 @@ namespace Serein.Library.Utils
/// <summary>
/// IOC管理容器
/// </summary>
public class SereinIOC : ISereinIOC
public class SereinIOC/* : ISereinIOC*/
{
/// <summary>
/// 类型集合,暂放待实例化的类型,完成实例化之后移除
@@ -22,7 +22,7 @@ namespace Serein.Library.Utils
private readonly ConcurrentDictionary<string, Type> _typeMappings;
/// <summary>
/// 实例集合(包含已完成注入、未完成注入的对象实例,计划在未来的版本中区分:)
/// 已完成注入的实例集合
/// </summary>
private readonly ConcurrentDictionary<string, object> _dependencies;
@@ -34,10 +34,6 @@ namespace Serein.Library.Utils
private readonly ConcurrentDictionary<string, List<(object,PropertyInfo)>> _unfinishedDependencies;
/// <summary>
/// 待实例化的类型
/// </summary>
// private readonly List<Type> _waitingForInstantiation;
public SereinIOC()
{
@@ -49,13 +45,13 @@ namespace Serein.Library.Utils
public void InitRegister()
{
_dependencies[typeof(ISereinIOC).FullName] = this;
//_dependencies[typeof(ISereinIOC).FullName] = this;
Register<IRouter, Router>();
/*foreach (var type in _typeMappings.Values)
{
Register(type);
}
Build();*/
//foreach (var type in _typeMappings.Values)
//{
// Register(type);
//}
//Build();
}
#region
@@ -65,21 +61,19 @@ namespace Serein.Library.Utils
/// </summary>
/// <param name="type">目标类型</param>
/// <param name="parameters">参数</param>
public ISereinIOC Register(Type type, params object[] parameters)
public bool Register(Type type, params object[] parameters)
{
RegisterType(type?.FullName, type);
return this;
return RegisterType(type?.FullName, type);
}
/// <summary>
/// 注册类型
/// </summary>
/// <param name="type">目标类型</param>
/// <param name="parameters">参数</param>
public ISereinIOC Register<T>(params object[] parameters)
public bool Register<T>(params object[] parameters)
{
var type = typeof(T);
RegisterType(type.FullName, type);
return this;
return RegisterType(type.FullName, type);
}
/// <summary>
@@ -87,14 +81,20 @@ namespace Serein.Library.Utils
/// </summary>
/// <param name="type">目标类型</param>
/// <param name="parameters">参数</param>
public ISereinIOC Register<TService, TImplementation>(params object[] parameters)
public bool Register<TService, TImplementation>(params object[] parameters)
where TImplementation : TService
{
RegisterType(typeof(TService).FullName, typeof(TImplementation));
return this;
return RegisterType(typeof(TService).FullName, typeof(TImplementation));
}
#endregion
/// <summary>
/// 尝试从容器中获取对象如果不存在目标类型的对象则将类型信息登记到容器并实例化注入依赖项。如果依然无法注册则返回null。
/// </summary>
public T GetOrRegisterInstantiate<T>()
{
return (T)GetOrRegisterInstantiate(typeof(T));
}
/// <summary>
/// 尝试从容器中获取对象如果不存在目标类型的对象则将类型信息登记到容器并实例化注入依赖项。如果依然无法注册则返回null。
@@ -134,16 +134,25 @@ namespace Serein.Library.Utils
}
/// <summary>
/// 尝试从容器中获取对象如果不存在目标类型的对象则将类型信息登记到容器并实例化注入依赖项。如果依然无法注册则返回null
/// 用于临时实例的创建不登记到IOC容器中依赖项注入失败时也不记录
/// </summary>
public T GetOrRegisterInstantiate<T>()
/// <param name="controllerType"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public object Instantiate(Type controllerType, params object[] parameters)
{
return (T)GetOrRegisterInstantiate(typeof(T));
var instance = Activator.CreateInstance(controllerType, parameters); // CreateInstance(controllerType, parameters); // 创建目标类型的实例
if (instance != null)
{
InjectDependencies(instance, false); // 完成创建后注入实例需要的依赖项
}
return instance;
}
#region
public void CustomRegisterInstance(string name,object instance, bool needInjectProperty = true)
public void CustomRegisterInstance(string name, object instance, bool needInjectProperty = true)
{
// 不存在时才允许创建
if (!_dependencies.ContainsKey(name))
@@ -151,7 +160,7 @@ namespace Serein.Library.Utils
_dependencies.TryAdd(name, instance);
}
if (needInjectProperty)
if (needInjectProperty)
{
InjectDependencies(instance); // 注入实例需要的依赖项
}
@@ -180,7 +189,7 @@ namespace Serein.Library.Utils
{
return (T)Get(name);
}
public object Get(string name)
private object Get(string name)
{
object value;
if (!_dependencies.TryGetValue(name, out value))
@@ -190,23 +199,10 @@ namespace Serein.Library.Utils
return value;
}
#endregion
/// <summary>
/// 根据类型生成对应的实例并注入其中的依赖项类型信息不登记到IOC容器中,类型创建后自动注入其它需要此类型的对象
/// </summary>
/// <param name="controllerType"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public object Instantiate(Type controllerType, params object[] parameters)
{
var instance = CreateInstance(controllerType, parameters); // 创建目标类型的实例
if(instance != null)
{
InjectDependencies(instance); // 完成创建后注入实例需要的依赖项
}
return instance;
}
#region
@@ -214,7 +210,7 @@ namespace Serein.Library.Utils
/// 清空容器对象
/// </summary>
/// <returns></returns>
public ISereinIOC Reset()
public bool Reset()
{
// 检查是否存在非托管资源
foreach (var instancei in _dependencies.Values)
@@ -228,14 +224,14 @@ namespace Serein.Library.Utils
_typeMappings?.Clear();
_dependencies?.Clear();
// _waitingForInstantiation?.Clear();
return this;
return true;
}
/// <summary>
/// 实例化所有已注册的类型,并尝试绑定
/// </summary>
/// <returns></returns>
public ISereinIOC Build()
public bool Build()
{
InitRegister();
// 遍历已注册类型
@@ -265,7 +261,7 @@ namespace Serein.Library.Utils
// TryInstantiateWaitingDependencies();
return this;
return true;
}
#endregion
@@ -277,11 +273,16 @@ namespace Serein.Library.Utils
/// </summary>
/// <param name="typeFull"></param>
/// <param name="type"></param>
private void RegisterType(string typeFull, Type type)
private bool RegisterType(string typeFull, Type type)
{
if (!_typeMappings.ContainsKey(typeFull))
{
_typeMappings[typeFull] = type;
return true;
}
else
{
return false;
}
}
@@ -310,8 +311,9 @@ namespace Serein.Library.Utils
/// <summary>
/// 注入目标实例的依赖项
/// </summary>
/// <param name="instance"></param>
private bool InjectDependencies(object instance)
/// <param name="instance">实例</param>
/// <param name="isRecord">未完成依赖项注入时是否记录</param>
private bool InjectDependencies(object instance,bool isRecord = true)
{
var properties = instance.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public).ToArray()
@@ -327,7 +329,7 @@ namespace Serein.Library.Utils
{
property.SetValue(instance, dependencyInstance); // 尝试写入到目标实例的属性中
}
else
else if(isRecord)
{
// 存在依赖项,但目标类型的实例暂未加载,需要等待需要实例完成注册
var unfinishedDependenciesList = _unfinishedDependencies.GetOrAdd(propertyType.FullName, _ = new List<(object, PropertyInfo)>());
@@ -366,66 +368,62 @@ namespace Serein.Library.Utils
#endregion
#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 bool 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 void Run<T>(Action<T> action)
{
var service = GetOrRegisterInstantiate<T>();
if (service != null)
{
action(service);
}
return this;
}
public ISereinIOC Run<T1, T2>(Action<T1, T2> action)
public void Run<T1, T2>(Action<T1, T2> action)
{
var service1 = GetOrRegisterInstantiate<T1>();
var service2 = GetOrRegisterInstantiate<T2>();
action(service1, service2);
return this;
}
public ISereinIOC Run<T1, T2, T3>(Action<T1, T2, T3> action)
public void Run<T1, T2, T3>(Action<T1, T2, T3> action)
{
var service1 = GetOrRegisterInstantiate<T1>();
var service2 = GetOrRegisterInstantiate<T2>();
var service3 = GetOrRegisterInstantiate<T3>();
action(service1, service2, service3);
return this;
}
public ISereinIOC Run<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action)
public void Run<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action)
{
var service1 = GetOrRegisterInstantiate<T1>();
var service2 = GetOrRegisterInstantiate<T2>();
var service3 = GetOrRegisterInstantiate<T3>();
var service4 = GetOrRegisterInstantiate<T4>();
action(service1, service2, service3, service4);
return this;
}
public ISereinIOC Run<T1, T2, T3, T4, T5>(Action<T1, T2, T3, T4, T5> action)
public void Run<T1, T2, T3, T4, T5>(Action<T1, T2, T3, T4, T5> action)
{
var service1 = GetOrRegisterInstantiate<T1>();
var service2 = GetOrRegisterInstantiate<T2>();
@@ -433,10 +431,9 @@ namespace Serein.Library.Utils
var service4 = GetOrRegisterInstantiate<T4>();
var service5 = GetOrRegisterInstantiate<T5>();
action(service1, service2, service3, service4, service5);
return this;
}
public ISereinIOC Run<T1, T2, T3, T4, T5, T6>(Action<T1, T2, T3, T4, T5, T6> action)
public void Run<T1, T2, T3, T4, T5, T6>(Action<T1, T2, T3, T4, T5, T6> action)
{
var service1 = GetOrRegisterInstantiate<T1>();
var service2 = GetOrRegisterInstantiate<T2>();
@@ -445,10 +442,9 @@ namespace Serein.Library.Utils
var service5 = GetOrRegisterInstantiate<T5>();
var service6 = GetOrRegisterInstantiate<T6>();
action(service1, service2, service3, service4, service5, service6);
return this;
}
public ISereinIOC Run<T1, T2, T3, T4, T5, T6, T7>(Action<T1, T2, T3, T4, T5, T6, T7> action)
public void Run<T1, T2, T3, T4, T5, T6, T7>(Action<T1, T2, T3, T4, T5, T6, T7> action)
{
var service1 = GetOrRegisterInstantiate<T1>();
var service2 = GetOrRegisterInstantiate<T2>();
@@ -458,10 +454,9 @@ namespace Serein.Library.Utils
var service6 = GetOrRegisterInstantiate<T6>();
var service7 = GetOrRegisterInstantiate<T7>();
action(service1, service2, service3, service4, service5, service6, service7);
return this;
}
public ISereinIOC Run<T1, T2, T3, T4, T5, T6, T7, T8>(Action<T1, T2, T3, T4, T5, T6, T7, T8> action)
public void Run<T1, T2, T3, T4, T5, T6, T7, T8>(Action<T1, T2, T3, T4, T5, T6, T7, T8> action)
{
var service1 = GetOrRegisterInstantiate<T1>();
var service2 = GetOrRegisterInstantiate<T2>();
@@ -472,7 +467,6 @@ namespace Serein.Library.Utils
var service7 = GetOrRegisterInstantiate<T7>();
var service8 = GetOrRegisterInstantiate<T8>();
action(service1, service2, service3, service4, service5, service6, service7, service8);
return this;
}

View File

@@ -9,12 +9,12 @@ namespace Serein.NodeFlow.Base
/// </summary>
public abstract partial class NodeModelBase :IDynamicFlowNode
{
private static readonly ConnectionType[] ct = [ConnectionType.IsSucceed,
ConnectionType.IsFail,
ConnectionType.IsError,
ConnectionType.Upstream];
public NodeModelBase()
{
ConnectionType[] ct = [ConnectionType.IsSucceed,
ConnectionType.IsFail,
ConnectionType.IsError,
ConnectionType.Upstream];
PreviousNodes = [];
SuccessorNodes = [];
foreach (ConnectionType ctType in ct)

View File

@@ -98,7 +98,7 @@ namespace Serein.NodeFlow.Base
{
Stack<NodeModelBase> stack = new Stack<NodeModelBase>();
stack.Push(this);
var cts = context.SereinIoc.Get<CancellationTokenSource>(FlowStarter.FlipFlopCtsName);
var cts = context.Env.IOC.Get<CancellationTokenSource>(FlowStarter.FlipFlopCtsName);
while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环
{
// 节点执行异常时跳过执行
@@ -109,7 +109,7 @@ namespace Serein.NodeFlow.Base
// 设置方法执行的对象
if (currentNode.MethodDetails?.ActingInstance == null && currentNode.MethodDetails?.ActingInstanceType is not null)
{
currentNode.MethodDetails.ActingInstance ??= context.SereinIoc.GetOrRegisterInstantiate(currentNode.MethodDetails.ActingInstanceType);
currentNode.MethodDetails.ActingInstance ??= context.Env.IOC.GetOrRegisterInstantiate(currentNode.MethodDetails.ActingInstanceType);
}
#region
@@ -330,52 +330,139 @@ namespace Serein.NodeFlow.Base
}
/// <summary>
/// 更新节点数据,并检查监视表达式
/// 更新节点数据,并检查监视表达式是否生效
/// </summary>
/// <param name="newData"></param>
public static async Task RefreshFlowDataAndExpInterrupt(IDynamicContext context, NodeModelBase nodeModel, object? newData = null)
public static async Task RefreshFlowDataAndExpInterrupt(IDynamicContext context,NodeModelBase nodeModel, object? newData = null)
{
string guid = nodeModel.Guid;
// 检查是否存在监视表达式
if (newData is not null && nodeModel.DebugSetting.InterruptExpressions.Count > 0)
if(newData is not null)
{
// 表达式环境下判断是否需要执行中断
bool isExpInterrupt = false;
string? exp = "";
// 判断执行监视表达式,直到为 true 时退出
for (int i = 0; i < nodeModel.DebugSetting.InterruptExpressions.Count && !isExpInterrupt; i++)
{
exp = nodeModel.DebugSetting.InterruptExpressions[i];
isExpInterrupt = SereinConditionParser.To(newData, exp);
}
if (isExpInterrupt) // 触发中断
{
InterruptClass interruptClass = InterruptClass.Branch; // 分支中断
if (context.FlowEnvironment.SetNodeInterrupt(nodeModel.Guid, interruptClass))
{
context.FlowEnvironment.TriggerInterrupt(guid, exp, InterruptTriggerEventArgs.InterruptTriggerType.Exp);
var cancelType = await nodeModel.DebugSetting.GetInterruptTask();
await Console.Out.WriteLineAsync($"[{nodeModel.MethodDetails.MethodName}]中断已{cancelType},开始执行后继分支");
}
}
await MonitorObjExpInterrupt(context, nodeModel, newData, 0); // 首先监视对象
await MonitorObjExpInterrupt(context, nodeModel, newData, 1); // 然后监视节点
nodeModel.FlowData = newData; // 替换数据
}
//if(context.Env.CheckObjMonitorState(newData, out List<string> exps)) // 如果新的数据处于查看状态通知UI进行更新交给运行环境判断
//{
// context.Env.MonitorObjectNotification(guid, newData); // 对象处于监视状态通知UI更新数据显示
// if (exps.Count > 0)
// {
// // 表达式环境下判断是否需要执行中断
// bool isExpInterrupt = false;
// string? exp = "";
// // 判断执行监视表达式,直到为 true 时退出
// for (int i = 0; i < exps.Count && !isExpInterrupt; i++)
// {
// exp = exps[i];
// isExpInterrupt = SereinConditionParser.To(newData, exp);
// }
// if (isExpInterrupt) // 触发中断
// {
// InterruptClass interruptClass = InterruptClass.Branch; // 分支中断
// if (context.Env.SetNodeInterrupt(nodeModel.Guid, interruptClass))
// {
// context.Env.TriggerInterrupt(guid, exp, InterruptTriggerEventArgs.InterruptTriggerType.Obj);
// var cancelType = await nodeModel.DebugSetting.GetInterruptTask();
// await Console.Out.WriteLineAsync($"[{newData}]中断已{cancelType},开始执行后继分支");
// }
// }
// }
//}
//if (newData is not null && nodeModel.DebugSetting.InterruptExpressions.Count > 0) // 检查节点是否存在监视表达式
//{
// // 表达式环境下判断是否需要执行中断
// bool isExpInterrupt = false;
// string? exp = "";
// // 判断执行监视表达式,直到为 true 时退出
// for (int i = 0; i < nodeModel.DebugSetting.InterruptExpressions.Count && !isExpInterrupt; i++)
// {
// exp = nodeModel.DebugSetting.InterruptExpressions[i];
// isExpInterrupt = SereinConditionParser.To(newData, exp);
// }
// if (isExpInterrupt) // 触发中断
// {
// InterruptClass interruptClass = InterruptClass.Branch; // 分支中断
// if (context.Env.SetNodeInterrupt(nodeModel.Guid, interruptClass))
// {
// context.Env.TriggerInterrupt(guid, exp, InterruptTriggerEventArgs.InterruptTriggerType.Exp);
// var cancelType = await nodeModel.DebugSetting.GetInterruptTask();
// await Console.Out.WriteLineAsync($"[{nodeModel.MethodDetails.MethodName}]中断已{cancelType},开始执行后继分支");
// }
// }
//}
//else if (nodeModel.DebugSetting.InterruptClass != InterruptClass.None)
//{
// var cancelType = await nodeModel.DebugSetting.InterruptTask;
// await Console.Out.WriteLineAsync($"[{nodeModel.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支");
//}
nodeModel.FlowData = newData; // 替换数据
// 节点是否监视了数据,如果是,调用环境接口触发其相关事件。
if (nodeModel.DebugSetting.IsMonitorFlowData)
//if (nodeModel.DebugSetting.IsMonitorFlowData)
//{
// // 节点是否监视了数据,如果是,调用环境接口触发其相关事件。
// context.Env.FlowDataNotification(guid, newData);
//}
}
private static async Task MonitorObjExpInterrupt(IDynamicContext context, NodeModelBase nodeModel, object data, int type)
{
MonitorObjectEventArgs.ObjSourceType sourceType;
object key;
if(type == 0)
{
context.FlowEnvironment.FlowDataNotification(guid, newData);
key = data;
sourceType = MonitorObjectEventArgs.ObjSourceType.IOCObj;
}
else
{
key = nodeModel.Guid;
sourceType = MonitorObjectEventArgs.ObjSourceType.IOCObj;
}
if (context.Env.CheckObjMonitorState(key, out List<string> exps)) // 如果新的数据处于查看状态通知UI进行更新交给运行环境判断
{
context.Env.MonitorObjectNotification(nodeModel.Guid, data, sourceType); // 对象处于监视状态通知UI更新数据显示
if (exps.Count > 0)
{
// 表达式环境下判断是否需要执行中断
bool isExpInterrupt = false;
string? exp = "";
// 判断执行监视表达式,直到为 true 时退出
for (int i = 0; i < exps.Count && !isExpInterrupt; i++)
{
exp = exps[i];
isExpInterrupt = SereinConditionParser.To(data, exp);
}
if (isExpInterrupt) // 触发中断
{
InterruptClass interruptClass = InterruptClass.Branch; // 分支中断
if (context.Env.SetNodeInterrupt(nodeModel.Guid, interruptClass))
{
context.Env.TriggerInterrupt(nodeModel.Guid, exp, InterruptTriggerEventArgs.InterruptTriggerType.Obj);
var cancelType = await nodeModel.DebugSetting.GetInterruptTask();
await Console.Out.WriteLineAsync($"[{data}]中断已{cancelType},开始执行后继分支");
}
}
}
}
}
/// <summary>
/// 释放对象

View File

@@ -8,6 +8,7 @@ using Serein.Library.Utils;
using Serein.NodeFlow.Base;
using Serein.NodeFlow.Model;
using Serein.NodeFlow.Tool;
using System.Collections;
using System.Collections.Concurrent;
using System.Reflection;
using System.Xml.Linq;
@@ -41,10 +42,11 @@ namespace Serein.NodeFlow
/// <summary>
/// 运行环境
/// </summary>
public class FlowEnvironment : IFlowEnvironment
public class FlowEnvironment : IFlowEnvironment, ISereinIOC
{
public FlowEnvironment()
{
sereinIOC = new SereinIOC();
ChannelFlowInterrupt = new ChannelFlowInterrupt();
LoadedAssemblyPaths = new List<string>();
LoadedAssemblies = new List<Assembly>();
@@ -113,6 +115,8 @@ namespace Serein.NodeFlow
#endregion
#region
/// <summary>
/// 环境名称
/// </summary>
@@ -121,38 +125,46 @@ namespace Serein.NodeFlow
/// <summary>
/// 是否全局中断
/// </summary>
public bool IsGlobalInterrupt { get; set; }
public bool IsGlobalInterrupt { get; set; }
/// <summary>
/// 流程中断器
/// </summary>
public ChannelFlowInterrupt ChannelFlowInterrupt { get; set; }
public ISereinIOC IOC { get => this; }
#endregion
#region
/// <summary>
/// 容器管理
/// </summary>
private SereinIOC sereinIOC;
/// <summary>
/// 存储加载的程序集路径
/// </summary>
public List<string> LoadedAssemblyPaths { get; }
private List<string> LoadedAssemblyPaths { get; }
/// <summary>
/// 存储加载的程序集
/// </summary>
public List<Assembly> LoadedAssemblies { get; }
private List<Assembly> LoadedAssemblies { get; }
/// <summary>
/// 存储所有方法信息
/// </summary>
public List<MethodDetails> MethodDetailss { get; }
private List<MethodDetails> MethodDetailss { get; }
/// <summary>
/// 环境加载的节点集合
/// </summary>
public Dictionary<string, NodeModelBase> Nodes { get; }
private Dictionary<string, NodeModelBase> Nodes { get; }
/// <summary>
/// 存放触发器节点(运行时全部调用)
/// </summary>
public List<SingleFlipflopNode> FlipflopNodes { get; }
private List<SingleFlipflopNode> FlipflopNodes { get; }
/// <summary>
/// 起始节点私有属性
@@ -162,7 +174,7 @@ namespace Serein.NodeFlow
/// <summary>
/// 起始节点
/// </summary>
public NodeModelBase StartNode
private NodeModelBase StartNode
{
get
{
@@ -171,7 +183,7 @@ namespace Serein.NodeFlow
set
{
if (_startNode is not null)
{
{
_startNode.IsStart = false;
}
value.IsStart = true;
@@ -179,12 +191,16 @@ namespace Serein.NodeFlow
}
}
/// <summary>
/// 流程启动器每次运行时都会重新new一个
/// </summary>
private FlowStarter? flowStarter;
#endregion
#region
/// <summary>
/// 异步运行
/// </summary>
@@ -204,13 +220,34 @@ namespace Serein.NodeFlow
await flowStarter.RunAsync(this, nodes, initMethods, loadingMethods, exitMethods);
if(flowStarter?.FlipFlopState == RunState.NoStart)
if (flowStarter?.FlipFlopState == RunState.NoStart)
{
this.Exit(); // 未运行触发器时,才会调用结束方法
}
flowStarter = null;
}
public async Task StartFlowInSelectNodeAsync(string startNodeGuid)
{
if (flowStarter is null)
{
return;
}
if (flowStarter.FlowState == RunState.Running || flowStarter.FlipFlopState == RunState.Running)
{
NodeModelBase? nodeModel = GuidToModel(startNodeGuid);
if (nodeModel is null || nodeModel is SingleFlipflopNode)
{
return;
}
await flowStarter.StartFlowInSelectNodeAsync(nodeModel);
}
else
{
return;
}
}
/// <summary>
/// 退出
/// </summary>
@@ -221,7 +258,7 @@ namespace Serein.NodeFlow
foreach (var node in Nodes.Values)
{
if(node is not null)
if (node is not null)
{
node.ReleaseFlowData(); // 退出时释放对象计数
}
@@ -244,32 +281,6 @@ namespace Serein.NodeFlow
}
/// <summary>
/// 运行环节加载了项目文件,需要创建节点控件
/// </summary>
/// <param name="nodeInfo"></param>
/// <param name="methodDetailss"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private NodeControlType GetNodeControlType(NodeInfo nodeInfo)
{
// 创建控件实例
NodeControlType controlType = nodeInfo.Type switch
{
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" => NodeControlType.Action,// 动作节点控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" => NodeControlType.Flipflop, // 触发器节点控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" => NodeControlType.ExpCondition,// 条件表达式控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => NodeControlType.ExpOp, // 操作表达式控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => NodeControlType.ConditionRegion, // 条件区域控件
_ => NodeControlType.None,
};
return controlType;
}
#region
/// <summary>
/// 加载项目文件
@@ -304,7 +315,7 @@ namespace Serein.NodeFlow
foreach (var nodeInfo in project.Nodes)
{
var controlType = GetNodeControlType(nodeInfo);
if(controlType == NodeControlType.None)
if (controlType == NodeControlType.None)
{
continue;
}
@@ -319,9 +330,9 @@ namespace Serein.NodeFlow
continue;
}
TryAddNode(nodeModel);
if(nodeInfo.ChildNodeGuids?.Length > 0)
if (nodeInfo.ChildNodeGuids?.Length > 0)
{
regionChildNodes.Add((nodeModel,nodeInfo.ChildNodeGuids));
regionChildNodes.Add((nodeModel, nodeInfo.ChildNodeGuids));
OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, nodeInfo.Position));
}
else
@@ -331,7 +342,7 @@ namespace Serein.NodeFlow
}
}
// 加载区域的子项
foreach((NodeModelBase region, string[] childNodeGuids) item in regionChildNodes)
foreach ((NodeModelBase region, string[] childNodeGuids) item in regionChildNodes)
{
foreach (var childNodeGuid in item.childNodeGuids)
{
@@ -438,8 +449,8 @@ namespace Serein.NodeFlow
var nodeModel = CreateNode(nodeControlType, methodDetails);
TryAddNode(nodeModel);
if(flowStarter?.FlowState != RunState.Completion
&& nodeControlType == NodeControlType.Flipflop
if (flowStarter?.FlowState != RunState.Completion
&& nodeControlType == NodeControlType.Flipflop
&& nodeModel is SingleFlipflopNode flipflopNode)
{
// 当前添加节点属于触发器,且当前正在运行,则加载到运行环境中
@@ -553,9 +564,9 @@ namespace Serein.NodeFlow
{
fromNode.SuccessorNodes[connectionType].Remove(toNode);
toNode.PreviousNodes[connectionType].Remove(fromNode);
if(toNode is SingleFlipflopNode flipflopNode)
if (toNode is SingleFlipflopNode flipflopNode)
{
if (flowStarter?.FlowState != RunState.Completion
if (flowStarter?.FlowState != RunState.Completion
&& flipflopNode.NotExitPreviousNode())
{
// 被父节点移除连接关系的子节点若为触发器,且无上级节点,则当前流程正在运行,则加载到运行环境中
@@ -586,7 +597,7 @@ namespace Serein.NodeFlow
md = null;
return false;
}
}
@@ -618,7 +629,7 @@ namespace Serein.NodeFlow
else if (interruptClass == InterruptClass.Branch)
{
nodeModel.DebugSetting.CancelInterruptCallback?.Invoke();
nodeModel.DebugSetting.GetInterruptTask = () =>
nodeModel.DebugSetting.GetInterruptTask = () =>
{
TriggerInterrupt(nodeGuid, "", InterruptTriggerEventArgs.InterruptTriggerType.Monitor);
return ChannelFlowInterrupt.GetOrCreateChannelAsync(nodeGuid);
@@ -627,7 +638,7 @@ namespace Serein.NodeFlow
{
ChannelFlowInterrupt.TriggerSignal(nodeGuid);
};
}
else if (interruptClass == InterruptClass.Global) // 全局……做不了omg
{
@@ -645,66 +656,130 @@ namespace Serein.NodeFlow
/// <param name="nodeGuid"></param>
/// <param name="expression"></param>
/// <returns></returns>
public bool AddInterruptExpression(string nodeGuid, string expression)
public bool AddInterruptExpression(object obj, string expression)
{
var nodeModel = GuidToModel(nodeGuid);
if (nodeModel is null) return false;
if (string.IsNullOrEmpty(expression))
if (dictMonitorObjExpInterrupt.TryGetValue(obj, out var condition))
{
nodeModel.DebugSetting.InterruptExpressions.Clear();// 暂时删除等UI做好了
condition.Clear(); // 暂时
condition.Add(expression);// 暂时
return true;
}
if (nodeModel.DebugSetting.InterruptExpressions.Contains(expression))
{
Console.WriteLine("表达式已存在");
return false;
}
else
{
nodeModel.DebugSetting.InterruptExpressions.Clear();// 暂时删除等UI做好了
nodeModel.DebugSetting.InterruptExpressions.Add(expression);
var exps = new List<string>();
exps.Add(expression);
dictMonitorObjExpInterrupt.TryAdd(obj, exps);
return true;
}
}
//public bool AddInterruptExpression(string nodeGuid, string expression)
//{
// var nodeModel = GuidToModel(nodeGuid);
// if (nodeModel is null) return false;
// if (string.IsNullOrEmpty(expression))
// {
// nodeModel.DebugSetting.InterruptExpressions.Clear(); // 传入空表达式时清空
// return true;
// }
// if (nodeModel.DebugSetting.InterruptExpressions.Contains(expression))
// {
// Console.WriteLine("表达式已存在");
// return false;
// }
// else
// {
// nodeModel.DebugSetting.InterruptExpressions.Clear();// 暂时删除等UI做好了
// nodeModel.DebugSetting.InterruptExpressions.Add(expression);
// return true;
// }
//}
/// <summary>
/// 监视节点的数据
/// 监视节点的数据(暂时注释)
/// </summary>
/// <param name="nodeGuid">需要监视的节点Guid</param>
public void SetNodeFLowDataMonitorState(string nodeGuid, bool isMonitor)
{
var nodeModel = GuidToModel(nodeGuid);
if (nodeModel is null) return;
nodeModel.DebugSetting.IsMonitorFlowData = isMonitor;
if (isMonitor)
{
var obj = nodeModel.GetFlowData();
if(obj is not null)
{
FlowDataNotification(nodeGuid, obj);
//public void SetMonitorObjState(string nodeGuid, bool isMonitor)
//{
// var nodeModel = GuidToModel(nodeGuid);
// if (nodeModel is null) return;
// nodeModel.DebugSetting.IsMonitorFlowData = isMonitor;
// if (isMonitor)
// {
// var obj = nodeModel.GetFlowData();
// if(obj is not null)
// {
// FlowDataNotification(nodeGuid, obj);
// }
// }
// else
// {
// // 不再监视的节点清空表达式
// nodeModel.DebugSetting.InterruptExpressions.Clear();
// }
//}
/// <summary>
/// 要监视的对象,以及与其关联的表达式
/// </summary>
private ConcurrentDictionary<object, List<string>> dictMonitorObjExpInterrupt = [];
/// <summary>
/// 设置对象的监视状态
/// </summary>
/// <param name="obj"></param>
/// <param name="isMonitor"></param>
/// <returns></returns>
public void SetMonitorObjState(object obj, bool isMonitor)
{
if (obj is null) { return; }
var isExist = dictMonitorObjExpInterrupt.ContainsKey(obj);
if (isExist)
{
if (!isMonitor) // 对象存在且需要不监视
{
dictMonitorObjExpInterrupt.Remove(obj, out _);
}
}
else
{
if (isMonitor) // 对象不存在且需要监视,添加在集合中。
{
dictMonitorObjExpInterrupt.TryAdd(obj, new List<string>()); ;
}
}
}
/// <summary>
/// 节点数据更新通知
/// 检查一个对象是否处于监听状态如果是则传出与该对象相关的表达式用于中断如果不是则返回false。
/// </summary>
/// <param name="nodeGuid"></param>
public void FlowDataNotification(string nodeGuid, object flowData)
/// <param name="obj"></param>
/// <returns></returns>
public bool CheckObjMonitorState(object obj, out List<string>? exps)
{
OnMonitorObjectChange?.Invoke(new MonitorObjectEventArgs(nodeGuid, flowData));
if (obj is null) { exps = null; return false; }
return dictMonitorObjExpInterrupt.TryGetValue(obj, out exps);
}
/// <summary>
///
/// 启动器调用,节点数据更新通知
/// </summary>
/// <param name="nodeGuid"></param>
public void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType)
{
OnMonitorObjectChange?.Invoke(new MonitorObjectEventArgs(nodeGuid, monitorData, sourceType));
}
/// <summary>
/// 启动器调用,节点触发了中断。
/// </summary>
/// <param name="nodeGuid">节点</param>
/// <param name="expression">表达式</param>
/// <param name="type">类型0节点1表达式</param>
/// <param name="type">类型0用户主动的中断1表达式中断</param>
public void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type)
{
OnInterruptTrigger?.Invoke(new InterruptTriggerEventArgs(nodeGuid, expression, type));
@@ -719,16 +794,6 @@ namespace Serein.NodeFlow
/// <summary>
/// Guid 转 NodeModel
/// </summary>
@@ -796,13 +861,37 @@ namespace Serein.NodeFlow
return (null, []);
}
}
/// <summary>
/// 运行环节加载了项目文件,需要创建节点控件
/// </summary>
/// <param name="nodeInfo"></param>
/// <param name="methodDetailss"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private NodeControlType GetNodeControlType(NodeInfo nodeInfo)
{
// 创建控件实例
NodeControlType controlType = nodeInfo.Type switch
{
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" => NodeControlType.Action,// 动作节点控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" => NodeControlType.Flipflop, // 触发器节点控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" => NodeControlType.ExpCondition,// 条件表达式控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => NodeControlType.ExpOp, // 操作表达式控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => NodeControlType.ConditionRegion, // 条件区域控件
_ => NodeControlType.None,
};
return controlType;
}
/// <summary>
/// 创建节点
/// </summary>
/// <param name="nodeBase"></param>
private NodeModelBase CreateNode(NodeControlType nodeControlType,MethodDetails? methodDetails = null)
private NodeModelBase CreateNode(NodeControlType nodeControlType, MethodDetails? methodDetails = null)
{
// 确定创建的节点类型
Type? nodeType = nodeControlType switch
@@ -929,13 +1018,127 @@ namespace Serein.NodeFlow
}
#endregion
#region
#endregion
#region IOC容器相关
ISereinIOC ISereinIOC.Reset()
{
sereinIOC.Reset();
return this;
}
ISereinIOC ISereinIOC.Register(Type type, params object[] parameters)
{
sereinIOC.Register(type, parameters);
return this;
}
ISereinIOC ISereinIOC.Register<T>(params object[] parameters)
{
sereinIOC.Register<T>(parameters);
return this;
}
ISereinIOC ISereinIOC.Register<TService, TImplementation>(params object[] parameters)
{
sereinIOC.Register<TService, TImplementation>(parameters);
return this;
}
T ISereinIOC.GetOrRegisterInstantiate<T>()
{
return sereinIOC.GetOrRegisterInstantiate<T>();
}
object ISereinIOC.GetOrRegisterInstantiate(Type type)
{
return sereinIOC.GetOrRegisterInstantiate(type);
}
object ISereinIOC.Get(Type type)
{
return sereinIOC.Get(type);
}
T ISereinIOC.Get<T>(string key)
{
return sereinIOC.Get<T>(key);
}
void ISereinIOC.CustomRegisterInstance(string key, object instance, bool needInjectProperty)
{
sereinIOC.CustomRegisterInstance(key, instance, needInjectProperty);
}
object ISereinIOC.Instantiate(Type type, params object[] parameters)
{
return sereinIOC.Instantiate(type, parameters);
}
ISereinIOC ISereinIOC.Build()
{
sereinIOC.Build();
return this;
}
ISereinIOC ISereinIOC.Run<T>(Action<T> action)
{
sereinIOC.Run(action);
return this;
}
ISereinIOC ISereinIOC.Run<T1, T2>(Action<T1, T2> action)
{
sereinIOC.Run(action);
return this;
}
ISereinIOC ISereinIOC.Run<T1, T2, T3>(Action<T1, T2, T3> action)
{
sereinIOC.Run(action);
return this;
}
ISereinIOC ISereinIOC.Run<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action)
{
sereinIOC.Run(action);
return this;
}
ISereinIOC ISereinIOC.Run<T1, T2, T3, T4, T5>(Action<T1, T2, T3, T4, T5> action)
{
sereinIOC.Run(action);
return this;
}
ISereinIOC ISereinIOC.Run<T1, T2, T3, T4, T5, T6>(Action<T1, T2, T3, T4, T5, T6> action)
{
sereinIOC.Run(action);
return this;
}
ISereinIOC ISereinIOC.Run<T1, T2, T3, T4, T5, T6, T7>(Action<T1, T2, T3, T4, T5, T6, T7> action)
{
sereinIOC.Run(action);
return this;
}
ISereinIOC ISereinIOC.Run<T1, T2, T3, T4, T5, T6, T7, T8>(Action<T1, T2, T3, T4, T5, T6, T7, T8> action)
{
sereinIOC.Run(action);
return this;
}
#endregion
}
public static class FlowFunc
{
@@ -961,7 +1164,7 @@ namespace Serein.NodeFlow
}
public static Type? ControlTypeToModel(this NodeControlType nodeControlType )
public static Type? ControlTypeToModel(this NodeControlType nodeControlType)
{
// 确定创建的节点类型
Type? nodeType = nodeControlType switch
@@ -1000,7 +1203,7 @@ namespace Serein.NodeFlow
ConnectionType.Upstream];
foreach (ConnectionType ctType in ct)
{
if(node.PreviousNodes[ctType].Count > 0)
if (node.PreviousNodes[ctType].Count > 0)
{
return false;
}

View File

@@ -21,9 +21,13 @@ namespace Serein.NodeFlow
/// <param name="methodDetails"></param>
public class FlowStarter
{
/// <summary>
/// 全局触发器CTS
/// </summary>
public const string FlipFlopCtsName = "<>.FlowFlipFlopCts";
public FlowStarter()
{
SereinIOC = new SereinIOC();
}
/// <summary>
@@ -44,22 +48,25 @@ namespace Serein.NodeFlow
/// </summary>
Completion,
}
/// <summary>
/// 起点流程运行状态
/// </summary>
public RunState FlowState { get; private set; } = RunState.NoStart;
/// <summary>
/// 全局触发器运行状态
/// </summary>
public RunState FlipFlopState { get; private set; } = RunState.NoStart;
/// <summary>
/// 控制触发器
/// </summary>
private CancellationTokenSource _flipFlopCts = null;
public const string FlipFlopCtsName = "<>.FlowFlipFlopCts";
/// <summary>
/// 是否停止启动
/// </summary>
private bool IsStopStart = false;
public bool IsStopStart = false;
/// <summary>
/// 运行状态
/// </summary>
public RunState FlowState { get; private set; } = RunState.NoStart;
public RunState FlipFlopState { get; private set; } = RunState.NoStart;
/// <summary>
/// 运行时的IOC容器
/// </summary>
private ISereinIOC SereinIOC { get; } = null;
/// <summary>
/// 结束运行时需要执行的方法
/// </summary>
@@ -77,15 +84,17 @@ namespace Serein.NodeFlow
}
}
/// <summary>
/// 从选定的节点开始运行
/// </summary>
/// <param name="startNode"></param>
/// <returns></returns>
public async Task StartFlowInSelectNodeAsync(NodeModelBase startNode)
{
if (Context is null) return;
await startNode.StartExecute(Context); // 开始运行时从选定节点开始运行
}
// <summary>
// 开始运行
// </summary>
// <param name="startNode">起始节点</param>
// <param name="env">运行环境</param>
// <param name="runNodeMd">环境中已加载的所有节点方法</param>
// <param name="flipflopNodes">触发器节点</param>
// <returns></returns>
/// <summary>
/// 开始运行
@@ -130,11 +139,11 @@ namespace Serein.NodeFlow
var isNetFramework = false;
if (isNetFramework)
{
Context = new Serein.Library.Framework.NodeFlow.DynamicContext(SereinIOC, env);
Context = new Serein.Library.Framework.NodeFlow.DynamicContext(env);
}
else
{
Context = new Serein.Library.Core.NodeFlow.DynamicContext(SereinIOC, env); // 从起始节点启动流程时创建上下文
Context = new Serein.Library.Core.NodeFlow.DynamicContext(env); // 从起始节点启动流程时创建上下文
}
#endregion
@@ -151,13 +160,14 @@ namespace Serein.NodeFlow
{
nodeMd.ActingInstance = null;
}
SereinIOC.Reset(); // 开始运行时清空ioc中注册的实例
env.IOC.Reset(); // 开始运行时清空ioc中注册的实例
env.IOC.CustomRegisterInstance(typeof(ISereinIOC).FullName, env);
// 初始化ioc容器中的类型对象
foreach (var md in thisRuningMds)
{
if (md.ActingInstanceType != null)
{
SereinIOC.Register(md.ActingInstanceType);
env.IOC.Register(md.ActingInstanceType);
}
else
{
@@ -168,11 +178,11 @@ namespace Serein.NodeFlow
CheckStartState(); // 初始化IOC后检查状态
SereinIOC.Build(); // 流程启动前的初始化
env.IOC.Build(); // 流程启动前的初始化
foreach (var md in thisRuningMds)
{
md.ActingInstance = SereinIOC.GetOrRegisterInstantiate(md.ActingInstanceType);
md.ActingInstance = env.IOC.GetOrRegisterInstantiate(md.ActingInstanceType);
if(md.ActingInstance is null)
{
await Console.Out.WriteLineAsync($"{md.MethodName} - 无法获取类型[{md.ActingInstanceType}]的实例");
@@ -206,20 +216,20 @@ namespace Serein.NodeFlow
{
((Action<object, object?[]?>)md.MethodDelegate).Invoke(md.ActingInstance, [Context]);
}
Context.SereinIoc.Build(); // 绑定初始化时注册的类型
Context.Env.IOC.Build(); // 绑定初始化时注册的类型
foreach (var md in loadingMethods) // 加载
{
//object?[]? data = [md.ActingInstance, args];
//md.MethodDelegate.DynamicInvoke(data);
((Action<object, object?[]?>)md.MethodDelegate).Invoke(md.ActingInstance, [Context]);
}
Context.SereinIoc.Build(); // 预防有人在加载时才注册类型,再绑定一次
Context.Env.IOC.Build(); // 预防有人在加载时才注册类型,再绑定一次
#endregion
#region 退
ExitAction = () =>
{
SereinIOC.Run<WebServer>(web => {
env.IOC.Run<WebServer>(web => {
web?.Stop();
});
@@ -248,7 +258,7 @@ namespace Serein.NodeFlow
FlipFlopState = RunState.Running;
// 如果存在需要启动的触发器,则开始启动
_flipFlopCts = new CancellationTokenSource();
SereinIOC.CustomRegisterInstance(FlipFlopCtsName, _flipFlopCts,false);
env.IOC.CustomRegisterInstance(FlipFlopCtsName, _flipFlopCts,false);
// 使用 TaskCompletionSource 创建未启动的触发器任务
var tasks = flipflopNodes.Select(async node =>
@@ -270,7 +280,6 @@ namespace Serein.NodeFlow
catch (Exception ex)
{
await Console.Out.WriteLineAsync(ex.ToString());
// await Console.Out.WriteLineAsync(ex.Message);
}
finally
{
@@ -279,26 +288,26 @@ namespace Serein.NodeFlow
#endregion
}
public void AddFlipflopInRuning(SingleFlipflopNode singleFlipFlopNode, IFlowEnvironment flowEnvironment)
public void AddFlipflopInRuning(SingleFlipflopNode singleFlipFlopNode, IFlowEnvironment env)
{
_ = Task.Run(async () =>
{
// 设置对象
singleFlipFlopNode.MethodDetails.ActingInstance = SereinIOC.GetOrRegisterInstantiate(singleFlipFlopNode.MethodDetails.ActingInstanceType);
await FlipflopExecute(flowEnvironment,singleFlipFlopNode); // 启动触发器
singleFlipFlopNode.MethodDetails.ActingInstance = env.IOC.GetOrRegisterInstantiate(singleFlipFlopNode.MethodDetails.ActingInstanceType);
await FlipflopExecute(env,singleFlipFlopNode); // 启动触发器
});
}
/// <summary>
/// 启动全局触发器
/// </summary>
/// <param name="flowEnvironment">流程运行全局环境</param>
/// <param name="env">流程运行全局环境</param>
/// <param name="singleFlipFlopNode">需要全局监听信号的触发器</param>
/// <returns></returns>
private async Task FlipflopExecute(IFlowEnvironment flowEnvironment,SingleFlipflopNode singleFlipFlopNode)
private async Task FlipflopExecute(IFlowEnvironment env,SingleFlipflopNode singleFlipFlopNode)
{
var context = new DynamicContext(SereinIOC, flowEnvironment); // 启动全局触发器时新建上下文
var context = new DynamicContext(env); // 启动全局触发器时新建上下文
try
{
@@ -368,75 +377,6 @@ namespace Serein.NodeFlow
}
#if false
/// <summary>
/// 全局触发器开始执行相关分支
/// </summary>
/// <param name="context">上下文</param>
/// <param name="singleFlipFlopNode">被触发的全局触发器</param>
/// <param name="connectionType">分支类型</param>
/// <returns></returns>
public async Task GlobalFlipflopExecute(IDynamicContext context, SingleFlipflopNode singleFlipFlopNode,
ConnectionType connectionType, CancellationTokenSource cts)
{
bool skip = true;
Stack<NodeModelBase> stack = new Stack<NodeModelBase>();
stack.Push(singleFlipFlopNode);
while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环
{
// 从栈中弹出一个节点作为当前节点进行处理
var currentNode = stack.Pop();
// 设置方法执行的对象
//if (currentNode.MethodDetails?.ActingInstance == null && currentNode.MethodDetails?.ActingInstanceType is not null)
//{
// currentNode.MethodDetails.ActingInstance ??= context.SereinIoc.GetOrRegisterInstantiate(currentNode.MethodDetails.ActingInstanceType);
//}
// 首先执行上游分支
var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream];
for (int i = upstreamNodes.Count - 1; i >= 0; i--)
{
upstreamNodes[i].PreviousNode = currentNode;
await upstreamNodes[i].StartExecute(context); // 执行全局触发器的上游分支
}
// 当前节点是已经触发了的全局触发器,所以跳过,难道每次都要判断一次?
if (skip)
{
skip = false;
}
else
{
currentNode.FlowData = await currentNode.ExecutingAsync(context);
if (currentNode.NextOrientation == ConnectionType.None)
{
break; // 不再执行
}
connectionType = currentNode.NextOrientation;
}
// 获取下一分支
var nextNodes = currentNode.SuccessorNodes[connectionType];
// 将下一个节点集合中的所有节点逆序推入栈中
for (int i = nextNodes.Count - 1; i >= 0; i--)
{
nextNodes[i].PreviousNode = currentNode;
stack.Push(nextNodes[i]);
}
}}
#endif
}
}

View File

@@ -1,4 +1,5 @@
using System;
using Serein.Library.Api;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -12,5 +13,7 @@ namespace Serein.NodeFlow
/// 节点的命名空间
/// </summary>
public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}";
}
}

View File

@@ -28,6 +28,7 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Collections.NonGeneric" Version="4.3.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -11,6 +11,13 @@
<ResourceDictionary Source="/Themes/MethodDetailsControl.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style TargetType="{x:Type TextBlock }">
<!--<Setter Property="FontFamily" Value="Comic Sans MS"/>-->
<Setter Property="Foreground" Value="#071042"/>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Serein.WorkBench"
xmlns:custom="clr-namespace:Serein.WorkBench.Node.View"
xmlns:nodeView="clr-namespace:Serein.WorkBench.Node.View"
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
Title="Dynamic Node Flow" Height="900" Width="1400"
AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver"
@@ -21,51 +21,49 @@
<Window.InputBindings>
<KeyBinding Key="Escape" Command="{Binding CancelConnectionCommand}"/>
</Window.InputBindings>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0" Background="#F5F5F5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="3"></RowDefinition>
<RowDefinition Height="3*"></RowDefinition>
<!--<RowDefinition Height="3"></RowDefinition>-->
<!--<RowDefinition Height="3*"></RowDefinition>-->
</Grid.RowDefinitions>
<Grid Margin="2,2,2,5" Grid.Row="0" Grid.ColumnSpan="2" >
<Grid Margin="2,2,1,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="UnloadAllButton_Click" HorizontalAlignment="Right" Margin="5,5,5,5"/>-->
</Grid>
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto" Grid.ColumnSpan="2">
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto">
<StackPanel Orientation="Horizontal">
<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:ConditionRegionControl x:Name="ConditionRegionControl" Margin="10" AllowDrop="True" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
<nodeView:ExpOpNodeControl x:Name="ExpOpNodeControl" Margin="10" AllowDrop="True" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
<nodeView:ConditionNodeControl x:Name="ConditionNodeControl" Margin="10" AllowDrop="True" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
<nodeView:ConditionRegionControl x:Name="ConditionRegionControl" Margin="10" AllowDrop="True" PreviewMouseMove="BaseNodeControl_PreviewMouseMove"/>
</StackPanel>
</ScrollViewer>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" MaxHeight="400" Grid.ColumnSpan="2" Margin="0,72,0,5" Grid.RowSpan="3">
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto" MaxHeight="400" Grid.RowSpan="2">
<StackPanel x:Name="DllStackPanel" Margin="5"/>
</ScrollViewer>
<GridSplitter Grid.Row="3" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Center" ResizeBehavior="PreviousAndNext" Background="Gray" Grid.ColumnSpan="2"/>
<Grid Grid.Row="4" Grid.ColumnSpan="2">
<themes:ObjectViewerControl x:Name="ObjectViewer"/>
</Grid>
<!--<GridSplitter Grid.Row="3" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Center" ResizeBehavior="PreviousAndNext" Background="Gray"/>-->
</Grid>
</DockPanel>
<!--<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Background="Gray" DragDelta="GridSplitter_DragDelta"/>-->
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Background="Gray" />
<Grid Grid.Column="2" >
@@ -79,18 +77,23 @@
<StackPanel Grid.Row="0" Background="#F5F5F5" Orientation="Horizontal" >
<Button x:Name="ButtonDebugRun" Content="运行" Width="100" Margin="10" Click="ButtonDebugRun_Click"></Button>
<Button x:Name="ButtonDebugFlipflopNode" Content="结束" Width="100" Margin="10" Click="ButtonDebugFlipflopNode_Click"></Button>
<Button x:Name="ButtonStartFlowInSelectNode" Content="从选定节点开始" Width="100" Margin="10" Click="ButtonStartFlowInSelectNode_Click"></Button>
<!--<Button x:Name="ButtonReflushCanvasConfig" Content="重置画布设置" Width="100" Margin="10" Click="ButtonReflushCanvasConfig_Click"></Button>-->
<!--<Button x:Name="ButtonLoadCanvasConfig" Content="加载画布设置" Width="100" Margin="10" Click="ButtonLoadCanvasConfig_Click"></Button>-->
</StackPanel>
<!--HorizontalAlignment="Center"
VerticalAlignment="Center"-->
<StackPanel Grid.Row="1"
x:Name="FlowChartStackPanel"
ClipToBounds="True">
<Canvas
x:Name="FlowChartCanvas"
Background="#D9FFEA"
Background="#E1FBEA"
AllowDrop="True"
Width="1000"
Width="700"
Height="700"
MouseLeftButtonDown ="FlowChartCanvas_MouseLeftButtonDown"
MouseLeftButtonUp="FlowChartCanvas_MouseLeftButtonUp"
@@ -132,14 +135,6 @@
Canvas.Left="0"
Canvas.Top="{Binding ActualHeight, ElementName=FlowChartCanvas, Mode=OneWay, Converter={StaticResource BottomThumbPositionConverter}}"/>-->
<!-- Bottom-Right Thumb -->
<Thumb x:Name="BottomRightThumb"
Width="40" Height="40"
DragDelta="Thumb_DragDelta_BottomRight"
Cursor="SizeNWSE"
Canvas.Left="{Binding ActualWidth, ElementName=FlowChartCanvas, Mode=OneWay, Converter={StaticResource RightThumbPositionConverter}}"
Canvas.Top="{Binding ActualHeight, ElementName=FlowChartCanvas, Mode=OneWay, Converter={StaticResource BottomThumbPositionConverter}}"/>
<!-- Left Thumb -->
<!--<Thumb x:Name="LeftThumb"
Width="10" Height="10"
@@ -149,15 +144,6 @@
Canvas.Top="{Binding ActualHeight, ElementName=FlowChartCanvas, Mode=OneWay, Converter={StaticResource VerticalCenterThumbPositionConverter}}"/>-->
<!-- Right Thumb -->
<!--Canvas.Left="{Binding ActualWidth, ElementName=FlowChartCanvas, Mode=OneWay, Converter={StaticResource RightThumbPositionConverter}}"
Canvas.Top="{Binding ActualHeight, ElementName=FlowChartCanvas, Mode=OneWay, Converter={StaticResource VerticalCenterThumbPositionConverter}}"-->
<Thumb x:Name="RightThumb" Width="5" Cursor="SizeWE" Canvas.Top="0" Canvas.Right="0" DragDelta="Thumb_DragDelta_Right">
<Thumb.Template>
<ControlTemplate>
<Border Background="#B1B9F8" Width="5" Height="{Binding RelativeSource={RelativeSource AncestorType=Canvas}, Path=ActualHeight}" />
</ControlTemplate>
</Thumb.Template>
</Thumb>
<!-- Top Thumb -->
<!--<Thumb x:Name="TopThumb"
@@ -168,24 +154,59 @@ Canvas.Top="{Binding ActualHeight, ElementName=FlowChartCanvas, Mode=OneWay, Con
Canvas.Top="0"/>-->
<!-- Bottom Thumb -->
<!-- Bottom-Right Thumb -->
<Thumb x:Name="BottomRightThumb"
Width="15" Height="15"
DragDelta="Thumb_DragDelta_BottomRight"
Cursor="SizeNWSE"
Canvas.Left="{Binding ActualWidth, Converter={StaticResource RightThumbPositionConverter}, ElementName=FlowChartCanvas, Mode=OneWay}"
Canvas.Top="{Binding ActualHeight, Converter={StaticResource BottomThumbPositionConverter}, ElementName=FlowChartCanvas, Mode=OneWay}"/>
<!--Canvas.Left="{Binding ActualWidth, ElementName=FlowChartCanvas, Mode=OneWay, Converter={StaticResource RightThumbPositionConverter}}"
Canvas.Top="{Binding ActualHeight, ElementName=FlowChartCanvas, Mode=OneWay, Converter={StaticResource VerticalCenterThumbPositionConverter}}"-->
<Thumb x:Name="RightThumb" Width="5" Cursor="SizeWE" Canvas.Top="0" Canvas.Right="0" DragDelta="Thumb_DragDelta_Right">
<Thumb.Template>
<ControlTemplate>
<Border Background="#B1B9F8" Width="5" Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType={x:Type Canvas}}}" />
</ControlTemplate>
</Thumb.Template>
</Thumb>
<!--Canvas.Left="{Binding ActualWidth, ElementName=FlowChartCanvas, Mode=OneWay, Converter={StaticResource HorizontalCenterThumbPositionConverter}}"
Canvas.Top="{Binding ActualHeight, ElementName=FlowChartCanvas, Mode=OneWay, Converter={StaticResource BottomThumbPositionConverter}}"-->
<Thumb x:Name="BottomThumb" Height="5" Cursor="SizeNS" Canvas.Bottom="0" Canvas.Left="0" DragDelta="Thumb_DragDelta_Bottom">
<Thumb.Template>
<ControlTemplate>
<Border Background="#B1B9F8" Height="5" Width="{Binding RelativeSource={RelativeSource AncestorType=Canvas}, Path=ActualWidth}" />
<Border Background="#B1B9F8" Height="5" Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type Canvas}}}" />
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Canvas>
</StackPanel>
</Grid>
<GridSplitter Grid.Column="3" Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Background="Gray" />
<!--IOC容器属性-->
<Grid Grid.Column="4" >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" >
<!--<themes:LazyTreeView x:Name="lazyTreeView" />-->
</Grid>
<Grid Grid.Row="1" >
<themes:IOCObjectViewControl x:Name="IOCObjectViewer">
<!--<x:Arguments>
<x:String>Apple</x:String>
</x:Arguments>-->
</themes:IOCObjectViewControl>
</Grid>
<Grid Grid.Row="3" Margin="0,3,0,0" Grid.RowSpan="2">
<themes:ObjectViewerControl x:Name="ViewObjectViewer"></themes:ObjectViewerControl>
</Grid>
</Grid>
</Grid>

View File

@@ -148,25 +148,15 @@ namespace Serein.WorkBench
ViewModel = new MainWindowViewModel(this);
FlowEnvironment = ViewModel.FlowEnvironment;
ObjectViewer.FlowEnvironment = FlowEnvironment;
ViewObjectViewer.FlowEnvironment = FlowEnvironment;
InitFlowEnvironmentEvent(); // 配置环境事件
logWindow = new LogWindow();
logWindow.Show();
// 重定向 Console 输出
var logTextWriter = new LogTextWriter(msg => logWindow.AppendText(msg), () => logWindow.Clear());;
Console.SetOut(logTextWriter);
logWindow = InitConsoleOut(); // 重定向 Console 输出
InitCanvasUI();
InitCanvasUI(); // 配置画布
var project = App.FlowProjectData;
if (project == null)
{
return;
}
InitializeCanvas(project.Basic.Canvas.Width, project.Basic.Canvas.Lenght);// 设置画布大小
FlowEnvironment.LoadProject(project, App.FileDataPath); // 加载项目
FlowEnvironment.LoadProject(App.FlowProjectData, App.FileDataPath); // 加载项目
}
private void InitFlowEnvironmentEvent()
@@ -186,9 +176,6 @@ namespace Serein.WorkBench
FlowEnvironment.OnInterruptTrigger += FlowEnvironment_OnInterruptTrigger;
}
private void InitCanvasUI()
{
canvasTransformGroup = new TransformGroup();
@@ -202,7 +189,15 @@ namespace Serein.WorkBench
//FlowChartCanvas.RenderTransformOrigin = new Point(0.5, 0.5);
}
private LogWindow InitConsoleOut()
{
var logWindow = new LogWindow();
logWindow.Show();
// 重定向 Console 输出
var logTextWriter = new LogTextWriter(msg => logWindow.AppendText(msg), () => logWindow.Clear()); ;
Console.SetOut(logTextWriter);
return logWindow;
}
#region
private void Window_Loaded(object sender, RoutedEventArgs e)
@@ -215,15 +210,20 @@ namespace Serein.WorkBench
}
private void Window_ContentRendered(object sender, EventArgs e)
{
var project = App.FlowProjectData;
if (project == null)
{
return;
}
InitializeCanvas(project.Basic.Canvas.Width, project.Basic.Canvas.Lenght);// 设置画布大小
foreach (var connection in Connections)
{
connection.Refresh();
}
var canvasData = App.FlowProjectData?.Basic.Canvas;
var canvasData = project.Basic.Canvas;
if (canvasData != null)
{
scaleTransform.ScaleX = 1;
scaleTransform.ScaleY = 1;
translateTransform.X = 0;
@@ -453,35 +453,36 @@ namespace Serein.WorkBench
/// <summary>
/// 被监视的对象发生改变(节点执行了一次)
/// 被监视的对象发生改变
/// </summary>
/// <param name="eventArgs"></param>
private void FlowEnvironment_OnMonitorObjectChange(MonitorObjectEventArgs eventArgs)
{
string nodeGuid = eventArgs.NodeGuid;
NodeControlBase nodeControl = GuidToControl(nodeGuid);
ObjectViewer.Dispatcher.BeginInvoke(() => {
if (string.IsNullOrEmpty(ObjectViewer.NodeGuid)) // 如果没有加载过
object monitorKey = MonitorObjectEventArgs.ObjSourceType.NodeFlowData switch
{
MonitorObjectEventArgs.ObjSourceType.NodeFlowData => nodeGuid,
_ => eventArgs.NewData,
};
//NodeControlBase nodeControl = GuidToControl(nodeGuid);
ViewObjectViewer.Dispatcher.BeginInvoke(() => {
if (ViewObjectViewer.MonitorObj is null) // 如果没有加载过对象
{
ObjectViewer.NodeGuid = nodeGuid;
ObjectViewer.LoadObjectInformation(eventArgs.NewData); // 加载节点
//ObjectViewer.LoadObjectInformation(eventArgs.NewData); // 加载节点
}
ViewObjectViewer.LoadObjectInformation(monitorKey, eventArgs.NewData); // 加载对象 ViewObjectViewerControl.MonitorType.Obj
}
else
{
// 加载过,如果显示的对象来源并非同一个节点,则停止监听之前的节点
if (!ObjectViewer.NodeGuid.Equals(nodeGuid))
if (ViewObjectViewer.MonitorKey.Equals(monitorKey)) // 相同对象
{
FlowEnvironment.SetNodeFLowDataMonitorState(ObjectViewer.NodeGuid, false);
ObjectViewer.NodeGuid = nodeGuid;
//ObjectViewer.LoadObjectInformation(eventArgs.NewData); // 加载节点
ViewObjectViewer.RefreshObjectTree(eventArgs.NewData); // 刷新
}
else
{
ObjectViewer.RefreshObjectTree(eventArgs.NewData);
ViewObjectViewer.LoadObjectInformation(monitorKey, eventArgs.NewData); // 加载对象
}
}
});
@@ -733,18 +734,6 @@ namespace Serein.WorkBench
#endregion
contextMenu.Items.Add(CreateMenuItem("查看数据", (s, e) =>
{
var node = nodeControl?.ViewModel?.Node;
if(node is not null)
{
FlowEnvironment.SetNodeFLowDataMonitorState(ObjectViewer.NodeGuid, false); // 通知环境该节点的数据更新后需要传到UI
ObjectViewer.NodeGuid = node.Guid;
FlowEnvironment.SetNodeFLowDataMonitorState(node.Guid, true); // 通知环境该节点的数据更新后需要传到UI
}
}));
contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid)));
contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoteNode(nodeGuid)));
@@ -847,7 +836,7 @@ namespace Serein.WorkBench
//{
// try
// {
// var typeViewerWindow = new ObjectViewerWindow();
// var typeViewerWindow = new ViewObjectViewerWindow();
// typeViewerWindow.LoadObjectInformation(@object);
// typeViewerWindow.Show();
// }
@@ -1076,16 +1065,59 @@ namespace Serein.WorkBench
}
/// <summary>
/// 控件的鼠标左键按下事件,启动拖动操作。
/// 控件的鼠标左键按下事件,启动拖动操作。同时显示当前正在传递的数据。
/// </summary>
private void Block_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
IsControlDragging = true;
startControlDragPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置
((UIElement)sender).CaptureMouse(); // 捕获鼠标
e.Handled = true; // 防止事件传播影响其他控件
if(sender is NodeControlBase nodeControl)
{
ChangeViewerObjOfNode(nodeControl);
if (nodeControl.ViewModel.Node.MethodDetails.IsProtectionParameter) return;
IsControlDragging = true;
startControlDragPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置
((UIElement)sender).CaptureMouse(); // 捕获鼠标
e.Handled = true; // 防止事件传播影响其他控件
}
}
private void ChangeViewerObjOfNode(NodeControlBase nodeControl)
{
// int i = false;
var node = nodeControl?.ViewModel?.Node;
if (node is not null && node.MethodDetails.ReturnType != typeof(void))
{
if (ViewObjectViewer.MonitorObj is null)
{
FlowEnvironment.SetMonitorObjState(node.Guid, true); // 通知环境该节点的数据更新后需要传到UI
// FlowEnvironment.SetMonitorObjState(nodeObj, true); // 通知环境该节点的数据更新后需要传到UI
return;
}
var nodeObj = node.GetFlowData();
if (nodeObj is null)
{
return;
}
//if (nodeObj.Equals(ViewObjectViewer.MonitorObj) == true)
//{
// // 选择同一个控件,不再监视
// ViewObjectViewer.RefreshObjectTree(nodeObj);
// return;
//}
if (node.Guid.Equals(ViewObjectViewer.MonitorKey) == true)
{
ViewObjectViewer.RefreshObjectTree(nodeObj);
return;
}
else
{
FlowEnvironment.SetMonitorObjState(ViewObjectViewer.MonitorKey, false); // 取消对旧节点的监视
FlowEnvironment.SetMonitorObjState(node.Guid, true); // 通知环境该节点的数据更新后需要传到UI
}
}
}
/// <summary>
/// 控件的鼠标移动事件,根据鼠标拖动更新控件的位置。批量移动计算移动逻辑。
/// </summary>
@@ -1691,12 +1723,18 @@ namespace Serein.WorkBench
}
private void SelectedNode()
{
if(selectNodeControls.Count == 0)
if (selectNodeControls.Count == 0)
{
//Console.WriteLine($"没有选择控件");
SelectionRectangle.Visibility = Visibility.Collapsed;
return;
}
if(selectNodeControls.Count == 1)
{
// ChangeViewerObjOfNode(selectNodeControls[0]);
}
//Console.WriteLine($"一共选取了{selectNodeControls.Count}个控件");
foreach (var node in selectNodeControls)
{
@@ -2018,7 +2056,7 @@ namespace Serein.WorkBench
#endregion
#region
#region
private static TControl CreateNodeControl<TControl, TViewModel>(NodeModelBase model)
@@ -2115,6 +2153,12 @@ namespace Serein.WorkBench
#endregion
#region IOC视图管理
void LoadIOCObjectViewer()
{
}
#endregion
/// <summary>
@@ -2165,6 +2209,27 @@ namespace Serein.WorkBench
private void ButtonDebugFlipflopNode_Click(object sender, RoutedEventArgs e)
{
FlowEnvironment?.Exit(); // 在运行平台上点击了退出
}
/// <summary>
/// 从选定的节点开始运行
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void ButtonStartFlowInSelectNode_Click(object sender, RoutedEventArgs e)
{
if(selectNodeControls.Count == 0)
{
Console.WriteLine("请至少选择一个节点");
}
else if (selectNodeControls.Count > 1)
{
Console.WriteLine("请只选择一个节点");
}
else
{
await this.FlowEnvironment.StartFlowInSelectNodeAsync(selectNodeControls[0].ViewModel.Node.Guid);
}
}

View File

@@ -12,9 +12,13 @@
<!--<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />-->
<Converters:InvertableBooleanToVisibilityConverter x:Key="InvertedBoolConverter"/>
</UserControl.Resources>
<Border BorderBrush="#8DE9FD" BorderThickness="1">
<Grid>
<Grid.ToolTip>
<ToolTip Background="LightYellow" Foreground="Black" Content="{Binding MethodDetails.MethodName, UpdateSourceTrigger=PropertyChanged}" />
<ToolTip Background="LightYellow" Foreground="#071042" Content="{Binding MethodDetails.MethodName, UpdateSourceTrigger=PropertyChanged}" />
</Grid.ToolTip>
<Border>
@@ -40,9 +44,7 @@
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Background="#FFCFDF">
<StackPanel Grid.Row="0" Orientation="Horizontal" Background="#8DE9FD">
<CheckBox IsChecked="{Binding DebugSetting.IsEnable, Mode=TwoWay}" VerticalContentAlignment="Center"/>
<CheckBox IsChecked="{Binding MethodDetails.IsProtectionParameter, Mode=TwoWay}" VerticalContentAlignment="Center"/>
<TextBlock Text="{Binding MethodDetails.MethodTips}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
@@ -50,17 +52,17 @@
<themes:MethodDetailsControl Grid.Row="1" MethodDetails="{Binding MethodDetails}"/>
<!-- ParameterProtectionMask 参数保护 -->
<!--取反 Visibility="{Binding DebugSetting.IsEnable, Converter={StaticResource InvertedBoolConverter}, ConverterParameter=Inverted}"-->
<Border Grid.Row="1" x:Name="ParameterProtectionMask" Background="LightBlue" Opacity="0.5" BorderBrush="Black" BorderThickness="0"
<Border Grid.Row="1" x:Name="ParameterProtectionMask" Background="LightBlue" Opacity="0.5" BorderBrush="#0A4651" BorderThickness="0"
Visibility="{Binding MethodDetails.IsProtectionParameter, Converter={StaticResource InvertedBoolConverter},ConverterParameter=Normal}" />
<Grid Grid.Row="2" >
<Grid Grid.Row="2" Background="#D5F0FC" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
<TextBlock Text="result" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Border Grid.Column="0" BorderThickness="1">
<TextBlock Text="result" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<Border Grid.Column="1" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
<Border Grid.Column="1" BorderThickness="1">
<TextBlock Text="{Binding MethodDetails.ReturnType}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Border>
</Grid>
@@ -71,6 +73,7 @@
<!--Visibility="{Binding IsEnable, Converter={StaticResource BoolToVisConverter}, ConverterParameter=False}"-->
</Grid>
</Grid>
</Border>
</local:NodeControlBase>

View File

@@ -27,10 +27,10 @@
<ListBox x:Name="ConditionsListBox" Background="#A8D8EA"/>
</GroupBox>-->
<GroupBox Grid.Row="0" Header="动作" Margin="5">
<ListBox x:Name="ActionsListBox" Background="#FFCFDF"/>
<ListBox x:Name="ActionsListBox" Background="#D0F1F9"/>
</GroupBox>
<GroupBox Grid.Row="1" Header="触发器" Margin="5">
<ListBox x:Name="FlipflopsListBox" Background="#FFFFD2"/>
<ListBox x:Name="FlipflopsListBox" Background="#FACFC1"/>
</GroupBox>
</Grid>

View File

@@ -12,10 +12,13 @@
<vm:TypeToStringConverter x:Key="TypeToStringConverter"/>
<!--<themes:ConditionControl x:Key="ConditionControl"/>-->
</UserControl.Resources>
<Border BorderBrush="#FCB334" BorderThickness="1">
<Grid>
<Grid.ToolTip>
<ToolTip Background="LightYellow" Foreground="Black" Content="{Binding MethodDetails.MethodName, UpdateSourceTrigger=PropertyChanged}" />
<ToolTip Background="LightYellow" Foreground="#071042" Content="{Binding MethodDetails.MethodName, UpdateSourceTrigger=PropertyChanged}" />
</Grid.ToolTip>
<Grid.RowDefinitions>
@@ -23,23 +26,25 @@
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="#FFFFD2" BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding MethodDetails.MethodTips, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Border Background="#FCB334" >
<TextBlock Grid.Row="0" Text="{Binding MethodDetails.MethodTips, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<!--<themes:ExplicitDataControl Grid.Row="1" ExplicitDatas="{Binding ExplicitDatas}" />-->
<themes:MethodDetailsControl Grid.Row="1" MethodDetails="{Binding MethodDetails}" />
<Grid Grid.Row="2" >
<Grid Grid.Row="2" Background="#D5F0FC" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
<Border Grid.Column="0" BorderThickness="1">
<TextBlock Text="result" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Grid.Column="1" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
<Border Grid.Column="1" BorderThickness="1">
<TextBlock Text="{Binding MethodDetails.ReturnType}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Border>
</Grid>
<!--<themes:ConditionControl Grid.Row="2" ></themes:ConditionControl>-->
</Grid>
</Grid>
</Border>
</local:NodeControlBase>

View File

@@ -0,0 +1,25 @@
<UserControl x:Class="Serein.WorkBench.Themes.IOCObjectViewControl"
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="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="已注册待绑定的类型" Margin="5">
<ListBox x:Name="TypeListBox" Background="#E3F6FA"/>
</GroupBox>
<GroupBox Grid.Row="1" Header="完成注入的实例" Margin="5">
<ListBox x:Name="DependenciesListBox" Background="#E3FAE9"/>
</GroupBox>
<!--<GroupBox Grid.Row="3" Header="未完成注入的实例" Margin="5">
<ListBox x:Name="UnfinishedDependenciesListBox" Background="#FFE9D7"/>
</GroupBox>-->
</Grid>
</UserControl>

View File

@@ -0,0 +1,40 @@
using Serein.Library.Api;
using Serein.Library.Utils;
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.Navigation;
using System.Windows.Shapes;
namespace Serein.WorkBench.Themes
{
/// <summary>
/// IOCObjectViewControl.xaml 的交互逻辑
/// </summary>
public partial class IOCObjectViewControl : UserControl
{
private IOCObjectViewMoel IOCObjectViewMoel;
private SereinIOC sereinIOC;
public void SetIOC(SereinIOC sereinIOC)
{
this.sereinIOC = sereinIOC;
}
public IOCObjectViewControl()
{
InitializeComponent();
IOCObjectViewMoel = new IOCObjectViewMoel();
DataContext = IOCObjectViewMoel;
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.WorkBench.Themes
{
internal class IOCObjectViewMoel
{
}
}

View File

@@ -0,0 +1,26 @@
<UserControl x:Class="Serein.WorkBench.Themes.LazyTreeView"
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"
xmlns:converters="clr-namespace:Serein.WorkBench.Tool.Converters"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<converters:TypeToColorConverter x:Key="TypeToColorConverter" />
</UserControl.Resources>
<Grid>
<TreeView Name="treeView"
SelectedItemChanged="treeView_SelectedItemChanged"
ItemsSource="{Binding RootNodes}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SuccessorNodes}">
<TextBlock Text="{Binding DisplayName}" Foreground="{Binding ControlType, Converter={StaticResource TypeToColorConverter}}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</UserControl>

View File

@@ -0,0 +1,58 @@
using Serein.Library.Enums;
using Serein.NodeFlow.Base;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
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;
namespace Serein.WorkBench.Themes
{
/// <summary>
/// LazyTreeView.xaml 的交互逻辑
/// </summary>
public partial class LazyTreeView : UserControl
{
public ObservableCollection<NodeModelBase> RootNodes { get; set; }
public LazyTreeView()
{
InitializeComponent();
RootNodes = new ObservableCollection<NodeModelBase>();
treeView.DataContext = this;
}
private void treeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (e.NewValue is NodeModelBase node)
{
// 在这里设置 Expanded 事件
var treeViewItem = (TreeViewItem)treeView.ItemContainerGenerator.ContainerFromItem(node);
treeViewItem.Expanded += (s, args) => LoadChildren(node, treeViewItem);
}
}
private void LoadChildren(NodeModelBase node, TreeViewItem treeViewItem)
{
// 懒加载逻辑
if (node.SuccessorNodes.Count > 0)
{
treeViewItem.Items.Clear();
foreach (var child in node.SuccessorNodes[ConnectionType.IsSucceed]) // 根据类型加载子项
{
treeViewItem.Items.Add(child);
}
}
}
}
}

View File

@@ -0,0 +1,71 @@
using Serein.NodeFlow.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls.Primitives;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;
using Serein.Library.Enums;
namespace Serein.WorkBench.Themes
{
public class NodeTreeView : TreeView
{
public NodeTreeView()
{
this.ItemContainerGenerator.StatusChanged += OnStatusChanged;
}
private void OnStatusChanged(object sender, EventArgs e)
{
if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
foreach (var item in Items)
{
var treeViewItem = (TreeViewItem)this.ItemContainerGenerator.ContainerFromItem(item);
if (treeViewItem != null)
{
treeViewItem.Expanded += TreeViewItem_Expanded;
ApplyColor(treeViewItem, item as NodeModelBase);
}
}
}
}
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
if (sender is TreeViewItem item && item.DataContext is NodeModelBase node)
{
if (item.Items.Count == 0) // 懒加载
{
foreach (var childNode in node.SuccessorNodes[ConnectionType.Upstream])
{
item.Items.Add(childNode);
}
}
}
}
private void ApplyColor(TreeViewItem item, NodeModelBase node)
{
// 根据 ControlType 设置颜色
switch (node.ControlType)
{
case NodeControlType.Flipflop:
item.Background = Brushes.LightGreen;
break;
case NodeControlType.Action:
item.Background = Brushes.LightCoral;
break;
// 添加更多条件
default:
item.Background = Brushes.Transparent;
break;
}
}
}
}

View File

@@ -1,7 +1,9 @@
using Serein.Library.Api;
using Newtonsoft.Json.Linq;
using Serein.Library.Api;
using Serein.NodeFlow.Base;
using Serein.NodeFlow.Tool.SereinExpression;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
@@ -14,6 +16,7 @@ using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup.Primitives;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
@@ -54,73 +57,83 @@ namespace Serein.WorkBench.Themes
/// </summary>
public partial class ObjectViewerControl : UserControl
{
private object _objectInstance;
public string NodeGuid { get;set; }
public string MonitorExpression { get => ExpressionTextBox.Text.ToString(); }
public IFlowEnvironment FlowEnvironment { get;set; }
public NodeModelBase NodeModel { get;set; }
public ObjectViewerControl()
{
InitializeComponent();
}
private DateTime _lastRefreshTime = DateTime.MinValue; // 上次刷新时间
private TimeSpan _refreshInterval = TimeSpan.FromSeconds(0.1); // 刷新间隔2秒
/// <summary>
/// 监视类型
/// </summary>
public enum MonitorType
{
/// <summary>
/// 作用于对象(对象的引用)的监视
/// </summary>
NodeFlowData,
/// <summary>
/// 作用与节点FLowData的监视
/// </summary>
IOCObj,
}
/// <summary>
/// 运行环境
/// </summary>
public IFlowEnvironment FlowEnvironment { get; set; }
/// <summary>
/// 监视对象的键
/// </summary>
public object MonitorKey { get => monitorKey; }
/// <summary>
/// 正在监视的对象
/// </summary>
public object MonitorObj { get => monitorObj; }
/// <summary>
/// 监视表达式
/// </summary>
public string MonitorExpression { get => ExpressionTextBox.Text.ToString(); }
private object monitorKey;
private object monitorObj;
// 用于存储当前展开的节点路径
private HashSet<string> expandedNodePaths = new HashSet<string>();
/// <summary>
/// 加载对象信息,展示其成员
/// </summary>
/// <param name="obj">要展示的对象</param>
public void LoadObjectInformation(object obj)
public void LoadObjectInformation(object key, object obj)
{
if (obj == null)
return;
// 当前时间
var currentTime = DateTime.Now;
// 如果上次刷新时间和当前时间之间的差值小于设定的间隔,则跳过
if (currentTime - _lastRefreshTime < _refreshInterval)
{
// 跳过过于频繁的刷新调用
return;
}
// 记录这次的刷新时间
_lastRefreshTime = currentTime;
_objectInstance = obj;
RefreshObjectTree(obj);
if (obj == null) return;
monitorKey = key;
monitorObj = obj;
expandedNodePaths.Clear();
LoadTree(obj);
}
///// <summary>
///// 添加表达式
///// </summary>
///// <param name="sender"></param>
///// <param name="e"></param>
//private void AddMonitorExpressionButton_Click(object sender, RoutedEventArgs e)
//{
// OpenInputDialog((exp) =>
// {
// FlowEnvironment.AddInterruptExpression(NodeGuid, exp);
// });
//}
/// <summary>
/// 刷新对象
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void RefreshButton_Click(object sender, RoutedEventArgs e)
{
//RefreshObjectTree(_objectInstance);
FlowEnvironment.SetNodeFLowDataMonitorState(NodeGuid, true);
RefreshObjectTree(monitorObj);
}
/// <summary>
/// 更新表达式
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void UpMonitorExpressionButton_Click(object sender, RoutedEventArgs e)
{
//MonitorExpression = ExpressionTextBox.Text.ToString();
if(FlowEnvironment.AddInterruptExpression(NodeGuid, MonitorExpression))
if (FlowEnvironment.AddInterruptExpression(monitorKey, MonitorExpression)) // 对象预览器尝试添加中断表达式
{
if (string.IsNullOrEmpty(MonitorExpression))
{
@@ -131,98 +144,72 @@ namespace Serein.WorkBench.Themes
UpMonitorExpressionButton.Content = "更新监视表达式";
}
}
}
// 用于存储当前展开的节点路径
private HashSet<string> _expandedNodePaths = new HashSet<string>();
private TreeViewItem? LoadTree(object obj)
{
if (obj is null) return null;
var objectType = obj.GetType();
FlowDataDetails flowDataDetails = new FlowDataDetails
{
Name = objectType.Name,
DataType = objectType,
DataValue = obj,
DataPath = ""
};
var rootNode = new TreeViewItem
{
Header = objectType.Name,
Tag = flowDataDetails,
};
ObjectTreeView.Items.Clear(); // 移除对象树的所有节点
ObjectTreeView.Items.Add(rootNode); // 添加所有节点
rootNode.Expanded += TreeViewItem_Expanded; // 监听展开事件
rootNode.Collapsed += TreeViewItem_Collapsed; // 监听折叠事件
// 这里创建了一个子项,并给这个子项创建了“正在加载”的子项
// 然后移除了原来对象树的所有项,再把这个新创建的子项添加上去
// 绑定了展开/折叠事件后自动展开第一层开始反射obj的成员并判断obj的成员生成什么样的节点
return rootNode;
}
/// <summary>
/// 刷新对象属性树
/// </summary>
public void RefreshObjectTree(object obj)
{
if (obj is null)
return;
// 当前时间
var currentTime = DateTime.Now;
// 如果上次刷新时间和当前时间之间的差值小于设定的间隔,则跳过
if (currentTime - _lastRefreshTime < _refreshInterval)
monitorObj = obj;
var rootNode = LoadTree(obj);
if (rootNode is not null)
{
// 跳过过于频繁的刷新调用
return;
ExpandPreviouslyExpandedNodes(rootNode); // 遍历节点,展开之前记录的节点
}
// 记录这次的刷新时间
_lastRefreshTime = currentTime;
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);
}
// 遍历节点,展开之前记录的节点
ExpandPreviouslyExpandedNodes(rootNode);
}
// 遍历并展开之前记录的节点
private void ExpandPreviouslyExpandedNodes(TreeViewItem node)
/// <summary>
/// 展开父节点,如果路径存在哈希记录,则将其自动展开,并递归展开后的子节点。
/// </summary>
/// <param name="node"></param>
private void ExpandPreviouslyExpandedNodes(TreeViewItem node)
{
if (_expandedNodePaths.Contains(GetNodeFullPath(node)))
if (node == null) return;
if(node.Tag is FlowDataDetails flowDataDetails)
{
node.IsExpanded = true;
if (expandedNodePaths.Contains(flowDataDetails.DataPath))
{
node.IsExpanded = true;
}
}
foreach (TreeViewItem child in node.Items)
{
ExpandPreviouslyExpandedNodes(child);
}
}
/// <summary>
/// 添加父节点
/// </summary>
/// <param name="node"></param>
private void AddPlaceholderNode(TreeViewItem node)
{
node.Items.Add(new TreeViewItem { Header = "Loading..." });
}
/// <summary>
/// 展开子项事件
/// </summary>
@@ -231,19 +218,45 @@ namespace Serein.WorkBench.Themes
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...")
// 判断有没有加载过
//if (item.Items.Count == 1 && item.Items[0] is TreeViewItem placeholder && placeholder.Header.ToString() == "Loading...")
if (item.Items.Count == 0)
{
item.Items.Clear();
if (item.Tag is FlowDataDetails flowDataDetails) // FlowDataDetails flowDataDetails object obj
{
// 记录当前节点的路径
_expandedNodePaths.Add(GetNodeFullPath(item));
var path = flowDataDetails.DataPath;
expandedNodePaths.Add(path);
AddMembersToTreeNode(item, flowDataDetails.DataValue, flowDataDetails.DataType);
}
}
}
/// <summary>
/// 折叠事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TreeViewItem_Collapsed(object sender, RoutedEventArgs e)
{
var item = (TreeViewItem)sender;
if (item.Items.Count > 0)
{
if (item.Tag is FlowDataDetails flowDataDetails)
{
// 记录当前节点的路径
var path = flowDataDetails.DataPath;
if(path != "")
{
expandedNodePaths.Remove(path);
}
}
}
}
/// <summary>
/// 反射对象数据添加子节点
/// </summary>
@@ -252,81 +265,168 @@ namespace Serein.WorkBench.Themes
/// <param name="type"></param>
private 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))
if (member.Name.StartsWith(".") ||
member.Name.StartsWith("get_") ||
member.Name.StartsWith("set_")
)
{
memberNode.ContextMenu = contextMenu; // 设置子项节点的事件
// 跳过构造函数、属性的get/set方法
continue;
}
TreeViewItem memberNode = ConfigureTreeViewItem(obj, member); // 根据对象成员生成节点对象
if (memberNode != null)
{
treeViewNode.Items.Add(memberNode); // 添加到当前节点
// 配置数据路径
FlowDataDetails subFlowDataDetails = (FlowDataDetails)memberNode.Tag;
string superPath = ((FlowDataDetails)treeViewNode.Tag).DataPath;
string subPath = superPath + "." + subFlowDataDetails.Name;
subFlowDataDetails.DataPath = subPath;
// 配置右键菜单
var contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem($"表达式", (s, e) =>
{
ExpressionTextBox.Text = subPath; // 获取表达式
}));
memberNode.ContextMenu = contextMenu;
}
}
}
/// <summary>
/// 配置右键菜单功能
/// 配置节点子项
/// </summary>
/// <param name="obj"></param>
/// <param name="member"></param>
/// <returns></returns>
private TreeViewItem ConfigureTreeViewItem(object obj, MemberInfo member)
private TreeViewItem ConfigureTreeViewItem(object obj, MemberInfo member)
{
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
#region
if (member is PropertyInfo property)
{
string propertyValue = GetPropertyValue(obj, property,out object value);
FlowDataDetails flowDataDetails = new FlowDataDetails
if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType) && property.GetValue(obj) is IEnumerable collection && collection is not null)
{
ItemType = TreeItemType.Property,
DataType = property.PropertyType,
Name = property.Name,
DataValue = value,
DataPath = GetNodeFullPath(memberNode),
};
// 处理集合类型的属性
memberNode.Tag = new FlowDataDetails
{
ItemType = TreeItemType.IEnumerable,
DataType = property.PropertyType,
Name = property.Name,
DataValue = collection,
};
memberNode.Tag = flowDataDetails;
memberNode.Header = $"{property.Name} : {property.PropertyType.Name} = {propertyValue}";
if (!property.PropertyType.IsPrimitive && property.PropertyType != typeof(string))
{
AddPlaceholderNode(memberNode);
memberNode.Expanded += TreeViewItem_Expanded;
int index = 0;
foreach (var item in collection)
{
var itemNode = new TreeViewItem { Header = $"[{index++}] {item}" ?? "null" };
memberNode.Tag = new FlowDataDetails
{
ItemType = TreeItemType.Item,
DataType = item?.GetType(),
Name = property.Name,
DataValue = itemNode,
};
memberNode.Items.Add(itemNode);
}
memberNode.Header = $"{property.Name} : {property.PropertyType.Name} [{index}]";
return memberNode;
}
else
{
string propertyValue = GetPropertyValue(obj, property, out object value);
memberNode.Tag = new FlowDataDetails
{
ItemType = TreeItemType.Property,
DataType = property.PropertyType,
Name = property.Name,
DataValue = value,
}; ;
memberNode.Header = $"{property.Name} : {property.PropertyType.Name} = {propertyValue}";
if (!property.PropertyType.IsPrimitive && property.PropertyType != typeof(string))
{
memberNode.Expanded += TreeViewItem_Expanded;
memberNode.Collapsed += TreeViewItem_Collapsed;
}
return memberNode;
}
}
#endregion
#region
else if (member is FieldInfo field)
{
string fieldValue = GetFieldValue(obj, field, out object value);
FlowDataDetails flowDataDetails = new FlowDataDetails
if (typeof(IEnumerable).IsAssignableFrom(field.FieldType) && field.GetValue(obj) is IEnumerable collection && collection is not null)
{
ItemType = TreeItemType.Field,
DataType = field.FieldType,
Name = field.Name,
DataValue = value,
DataPath = GetNodeFullPath(memberNode),
};
memberNode.Tag = flowDataDetails;
memberNode.Header = $"{field.Name} : {field.FieldType.Name} = {fieldValue}";
if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
// 处理集合类型的字段
memberNode.Tag = new FlowDataDetails
{
ItemType = TreeItemType.IEnumerable,
DataType = field.FieldType,
Name = field.Name,
DataValue = collection,
};
int index = 0;
foreach (var item in collection)
{
var itemNode = new TreeViewItem { Header = $"[{index++}] {item}" ?? "null" };
memberNode.Tag = new FlowDataDetails
{
ItemType = TreeItemType.Item,
DataType = item?.GetType(),
Name = field.Name,
DataValue = itemNode,
};
//collectionNode.Items.Add(itemNode);
memberNode.Items.Add(itemNode);
}
memberNode.Header = $"{field.Name} : {field.FieldType.Name} [{index}]";
return memberNode;
}
else
{
AddPlaceholderNode(memberNode);
memberNode.Expanded += TreeViewItem_Expanded;
string fieldValue = GetFieldValue(obj, field, out object value);
memberNode.Tag = new FlowDataDetails
{
ItemType = TreeItemType.Field,
DataType = field.FieldType,
Name = field.Name,
DataValue = value,
};
memberNode.Header = $"{field.Name} : {field.FieldType.Name} = {fieldValue}";
if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
{
memberNode.Expanded += TreeViewItem_Expanded;
memberNode.Collapsed += TreeViewItem_Collapsed;
}
return memberNode;
}
}
#endregion
#region null
else
{
return null;
}
#endregion
return memberNode;
}
/// <summary>
@@ -335,15 +435,12 @@ namespace Serein.WorkBench.Themes
/// <param name="obj"></param>
/// <param name="property"></param>
/// <returns></returns>
private string GetPropertyValue(object obj, PropertyInfo property,out object value)
private string GetPropertyValue(object obj, PropertyInfo property,out object value)
{
try
{
var properties = obj.GetType().GetProperties();
// 获取实例属性值
value = property.GetValue(obj);
return value?.ToString() ?? "null"; // 返回值或“null”
@@ -355,14 +452,13 @@ namespace Serein.WorkBench.Themes
}
}
/// <summary>
/// 获取字段类型的成员
/// </summary>
/// <param name="obj"></param>
/// <param name="field"></param>
/// <returns></returns>
private string GetFieldValue(object obj, FieldInfo field, out object value)
private string GetFieldValue(object obj, FieldInfo field, out object value)
{
try
{
@@ -376,165 +472,170 @@ namespace Serein.WorkBench.Themes
}
}
/// <summary>
/// 根据成员类别配置右键菜单
/// </summary>
/// <param name="memberNode"></param>
/// <param name="member"></param>
/// <param name="contextMenu"></param>
/// <returns></returns>
private bool ConfigureTreeItemMenu(TreeViewItem memberNode, MemberInfo member, out ContextMenu? contextMenu)
{
bool isChange = false;
if (member is PropertyInfo property)
{
isChange = true;
contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem($"表达式", (s, e) =>
{
string fullPath = GetNodeFullPath(memberNode);
string copyValue = /*"@Get " + */fullPath;
ExpressionTextBox.Text = copyValue;
// Clipboard.SetDataObject(copyValue);
}));
}
else if (member is MethodInfo method)
{
//isChange = true;
contextMenu = new ContextMenu();
}
else if (member is FieldInfo field)
{
isChange = true;
contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem($"表达式", (s, e) =>
{
string fullPath = GetNodeFullPath(memberNode);
string copyValue = /*"@Get " +*/ fullPath;
ExpressionTextBox.Text = copyValue;
// Clipboard.SetDataObject(copyValue);
}));
}
else
{
contextMenu = new ContextMenu();
}
return isChange;
}
/// <summary>
/// 获取当前节点的完整路径,例如 "node1.node2.node3.node4"
/// </summary>
/// <param name="node">目标节点</param>
/// <returns>节点路径</returns>
private string GetNodeFullPath(TreeViewItem node)
{
if (node == null)
return string.Empty;
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 TreeViewItem GetParentTreeViewItem(TreeViewItem node)
{
DependencyObject parent = VisualTreeHelper.GetParent(node);
while (parent != null && !(parent is TreeViewItem))
{
parent = VisualTreeHelper.GetParent(parent);
}
return parent as TreeViewItem;
}
private 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;
// });
// }
//}
}
}
/// <summary>
/// 上次刷新事件
/// </summary>
//private DateTime lastRefreshTime = DateTime.MinValue;
/// <summary>
/// 刷新间隔
/// </summary>
//private readonly TimeSpan refreshInterval = TimeSpan.FromSeconds(0.1);
// 当前时间
//var currentTime = DateTime.Now;
//if (currentTime - lastRefreshTime < refreshInterval)
//{
// return; // 跳过过于频繁的刷新调用
//}
//else
//{
// lastRefreshTime = currentTime;// 记录这次的刷新时间
//}
//
/// <summary>
/// 从当前节点获取至父节点的路径,例如 "node1.node2.node3.node4"
/// </summary>
/// <param name="node">目标节点</param>
/// <returns>节点路径</returns>
//private 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 "";
// }
//}
/// <summary>
/// 获取指定节点的父级节点
/// </summary>
/// <param name="node">目标节点</param>
/// <returns>父节点</returns>
//private TreeViewItem GetParentTreeViewItem(TreeViewItem node)
//{
// DependencyObject parent = VisualTreeHelper.GetParent(node);
// while (parent != null && !(parent is TreeViewItem))
// {
// parent = VisualTreeHelper.GetParent(parent);
// }
// return parent as TreeViewItem;
//}
/// <summary>
/// 根据成员类别配置右键菜单
/// </summary>
/// <param name="memberNode"></param>
/// <param name="member"></param>
/// <param name="contextMenu"></param>
/// <returns></returns>
//private bool ConfigureTreeItemMenu(TreeViewItem memberNode, MemberInfo member, out ContextMenu? contextMenu)
//{
// if (ConfigureTreeItemMenu(memberNode, member, out ContextMenu? contextMenu))
// {
// memberNode.ContextMenu = contextMenu; // 设置子项节点的事件
// }
// bool isChange = false;
// if (member is PropertyInfo property)
// {
// isChange = true;
// contextMenu = new ContextMenu();
// contextMenu.Items.Add(MainWindow.CreateMenuItem($"表达式", (s, e) =>
// {
// string fullPath = GetNodeFullPath(memberNode);
// string copyValue = /*"@Get " + */fullPath;
// ExpressionTextBox.Text = copyValue;
// // Clipboard.SetDataObject(copyValue);
// }));
// }
// else if (member is MethodInfo method)
// {
// //isChange = true;
// contextMenu = new ContextMenu();
// }
// else if (member is FieldInfo field)
// {
// isChange = true;
// contextMenu = new ContextMenu();
// contextMenu.Items.Add(MainWindow.CreateMenuItem($"表达式", (s, e) =>
// {
// string fullPath = GetNodeFullPath(memberNode);
// string copyValue = /*"@Get " +*/ fullPath;
// ExpressionTextBox.Text = copyValue;
// // Clipboard.SetDataObject(copyValue);
// }));
// }
// else
// {
// contextMenu = new ContextMenu();
// }
// return isChange;
//}
///// <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;
// });
// }
//}

View File

@@ -267,7 +267,9 @@ namespace Serein.WorkBench.Themes
{
Property,
Method,
Field
Field,
IEnumerable,
Item,
}

View File

@@ -0,0 +1,23 @@
using Serein.Library.Enums;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
namespace Serein.WorkBench.Tool.Converters
{
public class TypeToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// 根据 ControlType 返回颜色
return value switch
{
NodeControlType.Action => Brushes.Blue,
NodeControlType.Flipflop => Brushes.Green,
_ => Brushes.Black,
};
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
}
}