修改了logwindows输出,避免高频输出时卡死。修改了流程运行上下文,使节点具备终止分支运行的能力。

This commit is contained in:
fengjiayi
2024-10-14 17:29:28 +08:00
parent f76f09da94
commit 4338554384
93 changed files with 4640 additions and 541 deletions

View File

@@ -18,27 +18,30 @@ namespace SereinFlowRemoteManagement
/// <summary>
/// SereinFlow 远程管理模块
/// SereinFlow 远程控制模块
/// </summary>
[DynamicFlow]
[AutoRegister]
[AutoSocketModule(ThemeKey ="theme",DataKey ="data")]
public class FlowRemoteManagement : ISocketHandleModule
public class SereinFlowRemoteControl : ISocketHandleModule
{
#region
public int ServerPort { get; set; } = 7525;
#region
public Guid HandleGuid { get; } = new Guid();
private readonly FlowEnvironment environment;
public FlowRemoteManagement(IFlowEnvironment environment)
private readonly IFlowEnvironment environment;
public SereinFlowRemoteControl(IFlowEnvironment environment)
{
if(environment is FlowEnvironment env)
{
this.environment = env;
}
else
{
throw new Exception();
}
this.environment = environment;
//if (environment is FlowEnvironment env)
//{
// this.environment = env;
//}
//else
//{
// throw new Exception();
//}
}
[NodeAction(NodeType.Init)]
@@ -62,13 +65,76 @@ namespace SereinFlowRemoteManagement
});
});
await Console.Out.WriteLineAsync("启动远程管理模块");
await socketServer.StartAsync("http://*:7525/");
await socketServer.StartAsync($"http://*:{ServerPort}/");
});
SereinProjectData projectData = environment.SaveProject();
}
}
#endregion
#region
#region
/// <summary>
/// 连接到运行环境,获取当前的节点信息
/// </summary>
/// <param name="Send"></param>
/// <returns></returns>
[AutoSocketHandle]
public async Task<object?> ConnectWorkBench(Func<string, Task> Send)
{
await Send("尝试获取");
Dictionary<NodeLibrary, List<MethodDetailsInfo>> LibraryMds = [];
foreach (var mdskv in environment.MethodDetailss)
{
var library = mdskv.Key;
var mds = mdskv.Value;
foreach (var md in mds)
{
if (!LibraryMds.TryGetValue(library, out var t_mds))
{
t_mds = new List<MethodDetailsInfo>();
LibraryMds[library] = t_mds;
}
var mdInfo = md.ToInfo();
mdInfo.LibraryName = library.Assembly.GetName().FullName;
t_mds.Add(mdInfo);
}
}
try
{
var project = await GetProjectInfo();
return new
{
project = project,
envNode = LibraryMds.Values,
};
}
catch (Exception ex)
{
await Send(ex.Message);
return null;
}
}
public void AddNode(string nodeType,string methodName,int x, int y)
{
if(x <= 0 || y <= 0)
{
throw new InvalidOperationException("坐标错误");
}
if (!EnumHelper.TryConvertEnum<NodeControlType>(nodeType, out var connectionType))
{
throw new InvalidOperationException("类型错误");
}
if (this.environment.TryGetMethodDetails(methodName,out var md))
{
this.environment.CreateNode(connectionType, new Position(x, y), md); ;
}
}
/// <summary>
/// 远程更改两个节点的连接关系
@@ -94,7 +160,7 @@ namespace SereinFlowRemoteManagement
}
else
{
environment.RemoteConnect(nodeInfo.FromNodeGuid, nodeInfo.ToNodeGuid, connectionType);
environment.RemoveConnect(nodeInfo.FromNodeGuid, nodeInfo.ToNodeGuid, connectionType);
}
}
@@ -129,49 +195,6 @@ namespace SereinFlowRemoteManagement
}
/// <summary>
/// 连接到运行环境,获取当前的节点信息
/// </summary>
/// <param name="Send"></param>
/// <returns></returns>
[AutoSocketHandle]
public async Task<object?> ConnectWorkBench(Func<string, Task> Send)
{
await Send("尝试获取");
Dictionary<NodeLibrary, List<MethodDetailsInfo>> LibraryMds = [];
foreach (var mdskv in environment.MethodDetailss)
{
var library = mdskv.Key;
var mds = mdskv.Value;
foreach (var md in mds)
{
if(!LibraryMds.TryGetValue(library, out var t_mds))
{
t_mds = new List<MethodDetailsInfo>();
LibraryMds[library] = t_mds;
}
var mdInfo = md.ToInfo();
mdInfo.LibraryName = library.Assembly.GetName().FullName;
t_mds.Add(mdInfo);
}
}
try
{
var project = await GetProjectInfo();
return new
{
project = project,
envNode = LibraryMds.Values,
};
}
catch (Exception ex)
{
await Send(ex.Message);
return null;
}
}
#endregion
}
}

View File

@@ -1,5 +1,7 @@
using Serein.Library.Api;
using Serein.Library.Enums;
using Serein.Library.Utils;
using System.Collections.Concurrent;
namespace Serein.Library.Core.NodeFlow
{
@@ -9,45 +11,66 @@ namespace Serein.Library.Core.NodeFlow
/// </summary>
public class DynamicContext: IDynamicContext
{
public DynamicContext(/*ISereinIOC sereinIoc, */IFlowEnvironment flowEnvironment)
/// <summary>
/// 动态流程上下文
/// </summary>
/// <param name="flowEnvironment"></param>
public DynamicContext(IFlowEnvironment flowEnvironment)
{
//SereinIoc = sereinIoc;
Env = flowEnvironment;
RunState = RunState.Running;
}
// public NodeRunCts NodeRunCts { get; set; }
// public ISereinIOC SereinIoc { get; }
/// <summary>
/// 运行环境
/// </summary>
public IFlowEnvironment Env { get; }
//public Task CreateTimingTask(Action action, int time = 100, int count = -1)
//{
// if (NodeRunCts == null)
// {
// NodeRunCts = Env.IOC.Get<NodeRunCts>();
// }
// // 使用局部变量,避免捕获外部的 `action`
// Action localAction = action;
/// <summary>
/// 运行状态
/// </summary>
public RunState RunState { get; set; } = RunState.NoStart;
// return Task.Run(async () =>
// {
// for (int i = 0; i < count && !NodeRunCts.IsCancellationRequested; i++)
// {
// await Task.Delay(time);
// if (NodeRunCts.IsCancellationRequested) { break; }
// //if (FlowEnvironment.IsGlobalInterrupt)
// //{
// // await FlowEnvironment.GetOrCreateGlobalInterruptAsync();
// //}
// // 确保对局部变量的引用
// localAction?.Invoke();
// }
/// <summary>
/// 每个上下文分别存放节点的当前数据
/// </summary>
private readonly ConcurrentDictionary<string,object?> dictNodeFlowData = new ConcurrentDictionary<string, object?>();
/// <summary>
/// 获取节点当前数据
/// </summary>
/// <param name="nodeGuid"></param>
/// <returns></returns>
public object? GetFlowData(string nodeGuid)
{
if(dictNodeFlowData.TryGetValue(nodeGuid,out var data))
{
return data;
}
{
return null;
}
}
/// <summary>
/// 添加或更新当前节点数据
/// </summary>
/// <param name="nodeGuid">节点Guid</param>
/// <param name="flowData">新的数据</param>
public void AddOrUpdate(string nodeGuid,object? flowData)
{
// this.dictNodeFlowData.TryGetValue(nodeGuid, out var oldFlowData);
this.dictNodeFlowData.AddOrUpdate(nodeGuid, _ => flowData, (_, _) => flowData);
}
/// <summary>
/// 结束流程
/// </summary>
public void EndCurrentBranch()
{
this.dictNodeFlowData?.Clear();
RunState = RunState.Completion;
}
// // 清理引用,避免闭包导致的内存泄漏
// localAction = null;
// });
//}
}
}

View File

@@ -1,6 +1,8 @@
using Serein.Library.Api;
using Serein.Library.Enums;
using Serein.Library.Utils;
using System;
using System.Collections.Concurrent;
using System.Security.Claims;
using System.Threading.Tasks;
@@ -17,12 +19,63 @@ namespace Serein.Library.Framework.NodeFlow
{
// SereinIoc = sereinIoc;
Env = flowEnvironment;
RunState = RunState.Running;
}
/// <summary>
/// 运行环境
/// </summary>
public IFlowEnvironment Env { get; }
/// <summary>
/// 运行状态
/// </summary>
public RunState RunState { get; set; } = RunState.NoStart;
/// <summary>
/// 每个上下文分别存放节点的当前数据
/// </summary>
private readonly ConcurrentDictionary<string, object> dictNodeFlowData = new ConcurrentDictionary<string, object>();
/// <summary>
/// 获取节点当前数据
/// </summary>
/// <param name="nodeGuid"></param>
/// <returns></returns>
public object GetFlowData(string nodeGuid)
{
if (dictNodeFlowData.TryGetValue(nodeGuid, out var data))
{
return data;
}
{
return null;
}
}
/// <summary>
/// 添加或更新当前节点数据
/// </summary>
/// <param name="nodeGuid">节点Guid</param>
/// <param name="flowData">新的数据</param>
public void AddOrUpdate(string nodeGuid, object flowData)
{
// this.dictNodeFlowData.TryGetValue(nodeGuid, out var oldFlowData);
this.dictNodeFlowData[nodeGuid] = flowData;
}
/// <summary>
/// 结束流程
/// </summary>
public void EndCurrentBranch()
{
this.dictNodeFlowData?.Clear();
RunState = RunState.Completion;
}
// public NodeRunCts NodeRunCts { get; set; }
// public ISereinIOC SereinIoc { get; }
public IFlowEnvironment Env { get; }
//public Task CreateTimingTask(Action action, int time = 100, int count = -1)
//{
// if(NodeRunCts == null)

View File

@@ -1,4 +1,5 @@
using Serein.Library.Utils;
using Serein.Library.Enums;
using Serein.Library.Utils;
using System;
using System.Threading.Tasks;
@@ -13,14 +14,35 @@ namespace Serein.Library.Api
/// 运行环境包含IOC容器。
/// </summary>
IFlowEnvironment Env { get; }
RunState RunState { get; }
/// <summary>
/// 获取节点的数据(当前节点需要获取上一节点数据时,需要从 运行时上一节点 的Guid 通过这个方法进行获取
/// </summary>
/// <param name="nodeGuid"></param>
/// <returns></returns>
object GetFlowData(string nodeGuid);
/// <summary>
/// 添加或更新当前节点的数据
/// </summary>
/// <param name="nodeGuid"></param>
/// <param name="flowData"></param>
void AddOrUpdate(string nodeGuid, object flowData);
/// <summary>
/// 用以提前结束分支运行
/// </summary>
void EndCurrentBranch();
/*/// <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);
}
// Task CreateTimingTask(Action callback, int time = 100, int count = -1);*/
}
}

View File

@@ -379,7 +379,8 @@ namespace Serein.Library.Api
{
#region
/// <summary>
/// IOC容器
/// <para>单例模式IOC容器内部维护了一个实例字典默认使用类型的FullName作为Key如果以“接口-实现类”的方式注册那么将使用接口类型的FullName作为Key。</para>
/// <para>当某个类型注册绑定成功后,将不会因为其它地方尝试注册相同类型的行为导致类型被重新创建。</para>
/// </summary>
ISereinIOC IOC { get; }
@@ -387,12 +388,26 @@ namespace Serein.Library.Api
/// 环境名称
/// </summary>
string EnvName { get; }
/// <summary>
/// 是否全局中断
/// </summary>
bool IsGlobalInterrupt { get; }
/// <summary>
/// DLL中NodeAction特性的方法描述的所有原始副本
/// </summary>
Dictionary<NodeLibrary, List<MethodDetails>> MethodDetailss { get; }
/// <summary>
/// 流程运行状态
/// </summary>
RunState FlowState { get; set; }
/// <summary>
/// 全局触发器运行状态
/// </summary>
RunState FlipFlopState { get; set; }
#endregion
@@ -545,15 +560,22 @@ namespace Serein.Library.Api
/// <param name="fromNodeGuid">起始节点</param>
/// <param name="toNodeGuid">目标节点</param>
/// <param name="connectionType">连接类型</param>
void RemoteConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType);
void RemoveConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType);
/// <summary>
/// 移除节点/区域/基础控件
/// </summary>
/// <param name="nodeGuid">待移除的节点Guid</param>
void RemoteNode(string nodeGuid);
void RemoveNode(string nodeGuid);
// 启动触发器
/// <summary>
/// 激活未启动的全局触发器
/// </summary>
/// <param name="nodeGuid"></param>
void ActivateFlipflopNode(string nodeGuid);
/// <summary>
/// 终结一个全局触发器,在它触发后将不会再次监听消息(表现为已经启动的触发器至少会再次处理一次消息,后面版本再修正这个非预期行为)
/// </summary>
/// <param name="nodeGuid"></param>
void TerminateFlipflopNode(string nodeGuid);

View File

@@ -178,7 +178,7 @@ namespace Serein.Library.Entity
public Position Position { get; set; }
/// <summary>
/// 是否选中
/// 是否选中(暂时无效)
/// </summary>
public bool IsSelect { get; set; }
}
@@ -188,8 +188,17 @@ namespace Serein.Library.Entity
/// </summary>
public class Parameterdata
{
/// <summary>
/// 参数类型true时使用自定义的入参false时由运行环境自动传参
/// </summary>
public bool State { get; set; }
/// <summary>
/// 自定义入参
/// </summary>
public string Value { get; set; }
/// <summary>
/// 表达式相关节点的表达式内容
/// </summary>
public string Expression { get; set; }
}
@@ -200,6 +209,9 @@ namespace Serein.Library.Entity
/// </summary>
public class Position
{
/// <summary>
/// 构造一个坐标
/// </summary>
public Position(double x, double y)
{
this.X = x; this.Y = y;

27
Library/Enums/RunState.cs Normal file
View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Library.Enums
{
/// <summary>
/// 流程运行状态
/// </summary>
public enum RunState
{
/// <summary>
/// 初始化值,等待开始。只有初始化时才会存在该值,后续每次重新开始都是从 Completion 变成 Running
/// </summary>
NoStart,
/// <summary>
/// 正在运行
/// </summary>
Running,
/// <summary>
/// 运行完成
/// </summary>
Completion,
}
}

View File

@@ -34,6 +34,12 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
/// </summary>
public event Action<Exception, Action<object>> OnExceptionTracking;
/// <summary>
/// 添加消息处理与异常处理
/// </summary>
/// <param name="themeKeyName"></param>
/// <param name="dataKeyName"></param>
/// <returns></returns>
private WebSocketHandleModule AddMyHandleModule(string themeKeyName, string dataKeyName)
{
var key = (themeKeyName, dataKeyName);
@@ -45,7 +51,11 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
return myHandleModule;
}
public void RemoteModule(ISocketHandleModule socketControlBase)
/// <summary>
/// 移除某个模块的WebSocket消息处理
/// </summary>
/// <param name="socketControlBase"></param>
public void RemoveModule(ISocketHandleModule socketControlBase)
{
var type = socketControlBase.GetType();
var moduleAttribute = type.GetCustomAttribute<AutoSocketModuleAttribute>();
@@ -66,7 +76,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
/// <summary>
/// 添加
/// 添加消息处理以及异常处理
/// </summary>
/// <param name="socketControlBase"></param>
/// <param name="onExceptionTracking"></param>

View File

@@ -50,6 +50,12 @@ namespace Serein.Library.Utils
}
}
/// <summary>
/// 根据方法信息创建动态调用的委托,返回方法类型,以及传出一个委托
/// </summary>
/// <param name="methodInfo"></param>
/// <param name="delegate"></param>
/// <returns></returns>
public static EmitMethodType CreateDynamicMethod( MethodInfo methodInfo,out Delegate @delegate)
{
bool IsTask = IsGenericTask(methodInfo.ReturnType, out var taskGenericsType);

View File

@@ -6,7 +6,6 @@ using Serein.Library.Api;
using Serein.Library.Attributes;
using Serein.Library.Enums;
using Serein.Library.Ex;
using Serein.Library.Framework.NodeFlow;
using Serein.Library.Network.WebSocketCommunication;
using Serein.Library.NodeFlow.Tool;
using Serein.Library.Web;
@@ -80,7 +79,7 @@ namespace Net462DllTest.Web
});
context.Env.IOC.Run<WebSocketServer>((socketServer) =>
{
socketServer.MsgHandleHelper.RemoteModule(this);
socketServer.MsgHandleHelper.RemoveModule(this);
socketServer?.Stop(); // 关闭 Web 服务
});
MyPlc.Close();
@@ -88,6 +87,8 @@ namespace Net462DllTest.Web
}
#endregion
[AutoSocketHandle]
public async Task BatchReadVar(Func<string, Task> SendMsg, Func<object, Task> SendObj)
{

View File

@@ -94,6 +94,36 @@ namespace Serein.NodeFlow.Base
#region
/// <summary>
/// 是否应该退出执行
/// </summary>
/// <param name="context"></param>
/// <param name="flowCts"></param>
/// <returns></returns>
public static bool IsBradk(IDynamicContext context, CancellationTokenSource? flowCts)
{
// 上下文不再执行
if(context.RunState == RunState.Completion)
{
return true;
}
// 不存在全局触发器时,流程运行状态被设置为完成,退出执行,用于打断无限循环分支。
if (flowCts is null && context.Env.FlowState == RunState.Completion)
{
return true;
}
// 如果存在全局触发器,且触发器的执行任务已经被取消时,退出执行。
if (flowCts is not null)
{
if (flowCts.IsCancellationRequested)
return true;
}
return false;
}
/// <summary>
/// 开始执行
/// </summary>
@@ -107,24 +137,18 @@ namespace Serein.NodeFlow.Base
bool hasFlipflow = flowCts != null;
while (stack.Count > 0) // 循环中直到栈为空才会退出循环
{
if (hasFlipflow && flowCts is not null)
{
if (flowCts.IsCancellationRequested)
break;
}
await Task.Delay(0);
// 从栈中弹出一个节点作为当前节点进行处理
var currentNode = stack.Pop();
#region
// 筛选出上游分支
var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream].Where(
node => node.DebugSetting.IsEnable
).ToArray();
// 执行上游分支
foreach (var upstreamNode in upstreamNodes)
var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream].ToArray();
for (int index = 0; index < upstreamNodes.Length; index++)
{
if (upstreamNode.DebugSetting.IsEnable)
NodeModelBase? upstreamNode = upstreamNodes[index];
if (upstreamNode is not null && upstreamNode.DebugSetting.IsEnable)
{
if (upstreamNode.DebugSetting.InterruptClass != InterruptClass.None) // 执行触发前
{
@@ -142,14 +166,11 @@ namespace Serein.NodeFlow.Base
}
}
}
if (IsBradk(context, flowCts)) break; // 退出执行
// 上游分支执行完成,才执行当前节点
object? newFlowData = await currentNode.ExecutingAsync(context);
if (hasFlipflow && (flowCts is null || flowCts.IsCancellationRequested || currentNode.NextOrientation == ConnectionType.None))
{
// 不再执行
break;
}
if (IsBradk(context, flowCts)) break; // 退出执行
await RefreshFlowDataAndExpInterrupt(context, currentNode, newFlowData); // 执行当前节点后刷新数据
#endregion
@@ -169,6 +190,7 @@ namespace Serein.NodeFlow.Base
stack.Push(nextNodes[i]);
}
}
#endregion
}
@@ -342,7 +364,10 @@ namespace Serein.NodeFlow.Base
/// <summary>
/// 更新节点数据,并检查监视表达式是否生效
/// </summary>
/// <param name="newData"></param>
/// <param name="context">上下文</param>
/// <param name="nodeModel">节点Moel</param>
/// <param name="newData">新的数据</param>
/// <returns></returns>
public static async Task RefreshFlowDataAndExpInterrupt(IDynamicContext context, NodeModelBase nodeModel, object? newData = null)
{
string guid = nodeModel.Guid;
@@ -351,6 +376,7 @@ namespace Serein.NodeFlow.Base
await MonitorObjExpInterrupt(context, nodeModel, newData, 0); // 首先监视对象
await MonitorObjExpInterrupt(context, nodeModel, newData, 1); // 然后监视节点
nodeModel.FlowData = newData; // 替换数据
context.AddOrUpdate(guid, nodeModel); // 上下文中更新数据
}
}

View File

@@ -150,6 +150,14 @@ namespace Serein.NodeFlow
#endregion
#region
/// <summary>
/// 如果没有全局触发器,且没有循环分支,流程执行完成后自动为 Completion 。
/// </summary>
public RunState FlowState { get; set; } = RunState.NoStart;
/// <summary>
/// 如果全局触发器还在运行,则为 Running 。
/// </summary>
public RunState FlipFlopState { get; set; } = RunState.NoStart;
/// <summary>
/// 环境名称
@@ -166,8 +174,18 @@ namespace Serein.NodeFlow
/// </summary>
public ChannelFlowInterrupt ChannelFlowInterrupt { get; set; }
/// <summary>
/// <para>单例模式IOC容器内部维护了一个实例字典默认使用类型的FullName作为Key如果以“接口-实现类”的方式注册那么将使用接口类型的FullName作为Key。</para>
/// <para>当某个类型注册绑定成功后,将不会因为其它地方尝试注册相同类型的行为导致类型被重新创建。</para>
/// </summary>
public ISereinIOC IOC { get => this; }
/// <summary>
/// 描述所有DLL中NodeAction特性的方法的原始副本
/// </summary>
public Dictionary<NodeLibrary, List<MethodDetails>> MethodDetailss { get; } = [];
#endregion
#region
@@ -185,10 +203,7 @@ namespace Serein.NodeFlow
/// </summary>
public List<NodeLibrary> NodeLibrarys { get; } = [];
/// <summary>
/// 描述所有DLL中NodeAction特性的方法的原始副本
/// </summary>
public Dictionary<NodeLibrary, List<MethodDetails>> MethodDetailss { get; } = [];
/// <summary>
/// 环境加载的节点集合
@@ -283,7 +298,7 @@ namespace Serein.NodeFlow
await flowStarter.RunAsync(this, nodes, AutoRegisterTypes, initMethods, loadMethods, exitMethods);
if (flowStarter?.FlipFlopState == RunState.NoStart)
if (this.FlipFlopState == RunState.Completion)
{
this.Exit(); // 未运行触发器时,才会调用结束方法
}
@@ -296,7 +311,7 @@ namespace Serein.NodeFlow
{
return;
}
if (flowStarter.FlowState == RunState.Running || flowStarter.FlipFlopState == RunState.Running)
if (this.FlowState == RunState.Running || this.FlipFlopState == RunState.Running)
{
NodeModelBase? nodeModel = GuidToModel(startNodeGuid);
if (nodeModel is null || nodeModel is SingleFlipflopNode)
@@ -580,7 +595,7 @@ namespace Serein.NodeFlow
/// </summary>
/// <param name="nodeGuid"></param>
/// <exception cref="NotImplementedException"></exception>
public void RemoteNode(string nodeGuid)
public void RemoveNode(string nodeGuid)
{
var remoteNode = GuidToModel(nodeGuid);
if (remoteNode is null) return;
@@ -653,7 +668,7 @@ namespace Serein.NodeFlow
/// <param name="toNodeGuid">目标节点Guid</param>
/// <param name="connectionType">连接关系</param>
/// <exception cref="NotImplementedException"></exception>
public void RemoteConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
public void RemoveConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
{
// 获取起始节点与目标节点
var fromNode = GuidToModel(fromNodeGuid);
@@ -853,7 +868,7 @@ namespace Serein.NodeFlow
if (nodeModel is null) return;
if (flowStarter is not null && nodeModel is SingleFlipflopNode flipflopNode) // 子节点为触发器
{
if (flowStarter.FlowState != RunState.Completion
if (this.FlowState != RunState.Completion
&& flipflopNode.NotExitPreviousNode()) // 正在运行,且该触发器没有上游节点
{
_ = flowStarter.RunGlobalFlipflopAsync(this, flipflopNode);// 被父节点移除连接关系的子节点若为触发器,且无上级节点,则当前流程正在运行,则加载到运行环境中

View File

@@ -17,48 +17,28 @@ using static Serein.Library.Utils.ChannelFlowInterrupt;
namespace Serein.NodeFlow
{
/// <summary>
/// 流程启动器
/// </summary>
/// <param name="serviceContainer"></param>
/// <param name="methodDetails"></param>
public class FlowStarter
{
/// <summary>
/// 全局触发器CTS
/// </summary>
public const string FlipFlopCtsName = "<>.FlowFlipFlopCts";
/// <summary>
/// 流程运行CTS
/// </summary>
public const string FlowRungCtsName = "<>.FlowRungCtsName";
public FlowStarter()
{
}
/// <summary>
/// 流程运行状态
/// </summary>
public enum RunState
{
/// <summary>
/// 等待开始
/// </summary>
NoStart,
/// <summary>
/// 正在运行
/// </summary>
Running,
/// <summary>
/// 运行完成
/// </summary>
Completion,
}
/// <summary>
/// 起点流程运行状态
/// </summary>
public RunState FlowState { get; private set; } = RunState.NoStart;
/// <summary>
/// 全局触发器运行状态
/// </summary>
public RunState FlipFlopState { get; private set; } = RunState.NoStart;
/// <summary>
/// 控制触发器
@@ -115,11 +95,11 @@ namespace Serein.NodeFlow
List<MethodDetails> loadingMethods,
List<MethodDetails> exitMethods)
{
FlowState = RunState.Running; // 开始运行
env.FlowState = RunState.Running; // 开始运行
NodeModelBase? startNode = nodes.FirstOrDefault(node => node.IsStart);
if (startNode is null) {
FlowState = RunState.Completion; // 不存在起点,退出流程
env.FlowState = RunState.Completion; // 不存在起点,退出流程
return;
}
@@ -281,8 +261,8 @@ namespace Serein.NodeFlow
_flipFlopCts?.Cancel();
_flipFlopCts?.Dispose();
}
FlowState = RunState.Completion;
FlipFlopState = RunState.Completion;
env.FlowState = RunState.Completion;
env.FlipFlopState = RunState.Completion;
};
#endregion
@@ -294,7 +274,7 @@ namespace Serein.NodeFlow
if (flipflopNodes.Count > 0)
{
FlipFlopState = RunState.Running;
env.FlipFlopState = RunState.Running;
// 如果存在需要启动的触发器,则开始启动
_flipFlopCts = new CancellationTokenSource();
env.IOC.CustomRegisterInstance(FlipFlopCtsName, _flipFlopCts,false);
@@ -308,7 +288,7 @@ namespace Serein.NodeFlow
}
await startNode.StartFlowAsync(Context); // 开始运行时从起始节点开始运行
// 等待结束
if(FlipFlopState == RunState.Running && _flipFlopCts is not null)
if(env.FlipFlopState == RunState.Running && _flipFlopCts is not null)
{
while (!_flipFlopCts.IsCancellationRequested)
{
@@ -322,7 +302,7 @@ namespace Serein.NodeFlow
}
finally
{
FlowState = RunState.Completion;
env.FlowState = RunState.Completion;
}
#endregion
}

View File

@@ -37,7 +37,7 @@ namespace Serein.NodeFlow.Model
break;
}
}
return Task.FromResult( PreviousNode?.GetFlowData());
return Task.FromResult(PreviousNode?.GetFlowData()); // 条件区域透传上一节点的数据
}

View File

@@ -32,35 +32,33 @@ namespace Serein.NodeFlow.Model
public override Task<object?> ExecutingAsync(IDynamicContext context)
{
// 接收上一节点参数or自定义参数内容
object? result;
if (IsCustomData)
object? parameter;
object? result = PreviousNode?.GetFlowData(); // 条件节点透传上一节点的数据
if (IsCustomData) // 是否使用自定义参数
{
result = CustomData;
// 表达式获取上一节点数据
var getObjExp = CustomData?.ToString();
if (!string.IsNullOrEmpty(getObjExp) && getObjExp.Length >= 4 && getObjExp[..4].Equals("@get", StringComparison.CurrentCultureIgnoreCase))
{
parameter = result;
if (parameter is not null)
{
parameter = SerinExpressionEvaluator.Evaluate(getObjExp, parameter, out _);
}
}
else
{
parameter = CustomData;
}
}
else
{
result = PreviousNode?.GetFlowData();
parameter = result;
}
try
{
var getObjExp = CustomData?.ToString();
if (IsCustomData && !string.IsNullOrEmpty(getObjExp) && getObjExp.Length >= 4)
{
var ExpOpOption = getObjExp[..4];
if(ExpOpOption.ToLower() == "@get")
{
result = PreviousNode?.GetFlowData();
if (result is not null)
{
result = SerinExpressionEvaluator.Evaluate(getObjExp, result, out _);
}
}
}
var isPass = SereinConditionParser.To(result, Expression);
var isPass = SereinConditionParser.To(parameter, Expression);
NextOrientation = isPass ? ConnectionType.IsSucceed : ConnectionType.IsFail;
}
catch (Exception ex)

View File

@@ -21,7 +21,7 @@ namespace Serein.NodeFlow.Model
//public override async Task<object?> Executing(IDynamicContext context)
public override Task<object?> ExecutingAsync(IDynamicContext context)
{
var data = PreviousNode?.GetFlowData();
var data = PreviousNode?.GetFlowData(); // 表达式节点使用上一节点数据
try
{

View File

@@ -1,189 +0,0 @@
using Serein.Library.Api;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow
{
/* /// <summary>
/// 输出文件
/// </summary>
public class SereinOutputFileData
{
/// <summary>
/// 基础
/// </summary>
public Basic basic { get; set; }
/// <summary>
/// 依赖的DLL
/// </summary>
public Library[] library { get; set; }
/// <summary>
/// 起始节点GUID
/// </summary>
public string startNode { get; set; }
/// <summary>
/// 节点信息集合
/// </summary>
public NodeInfo[] nodes { get; set; }
/// <summary>
/// 区域集合
/// </summary>
public Region[] regions { get; set; }
}
/// <summary>
/// 基础
/// </summary>
public class Basic
{
/// <summary>
/// 画布
/// </summary>
public FlowCanvas canvas { get; set; }
/// <summary>
/// 版本
/// </summary>
public string versions { get; set; }
// 预览位置
// 缩放比例
}
/// <summary>
/// 画布
/// </summary>
public class FlowCanvas
{
/// <summary>
/// 宽度
/// </summary>
public float width { get; set; }
/// <summary>
/// 高度
/// </summary>
public float lenght { get; set; }
}
/// <summary>
/// DLL
/// </summary>
public class Library
{
/// <summary>
/// DLL名称
/// </summary>
public string name { get; set; }
/// <summary>
/// 路径
/// </summary>
public string path { get; set; }
/// <summary>
/// 提示
/// </summary>
public string tips { get; set; }
}
/// <summary>
/// 节点
/// </summary>
public class NodeInfo
{
/// <summary>
/// GUID
/// </summary>
public string guid { get; set; }
/// <summary>
/// 名称
/// </summary>
public string name { get; set; }
/// <summary>
/// 显示标签
/// </summary>
public string label { get; set; }
/// <summary>
/// 类型
/// </summary>
public string type { get; set; }
/// <summary>
/// 于画布中的位置
/// </summary>
public Position position { get; set; }
/// <summary>
/// 真分支节点GUID
/// </summary>
public string[] trueNodes { get; set; }
/// <summary>
/// 假分支节点
/// </summary>
public string[] falseNodes { get; set; }
public string[] upstreamNodes { get; set; }
public Parameterdata[] parameterData { get; set; }
}
public class Parameterdata
{
public bool state { get; set; }
public string value { get; set; }
public string expression { get; set; }
}
/// <summary>
/// 节点于画布中的位置
/// </summary>
public class Position
{
public float x { get; set; }
public float y { get; set; }
}
/// <summary>
/// 区域
/// </summary>
public class Region
{
public string guid { get; set; }
public NodeInfo[] childNodes { get; set; }
}*/
}

View File

@@ -530,13 +530,9 @@ namespace Serein.NodeFlow.Tool.SereinExpression
{
">" => ValueTypeConditionResolver<T>.Operator.GreaterThan,
"<" => ValueTypeConditionResolver<T>.Operator.LessThan,
"=" => ValueTypeConditionResolver<T>.Operator.Equal,
"==" => ValueTypeConditionResolver<T>.Operator.Equal,
">=" => ValueTypeConditionResolver<T>.Operator.GreaterThanOrEqual,
"" => ValueTypeConditionResolver<T>.Operator.GreaterThanOrEqual,
"<=" => ValueTypeConditionResolver<T>.Operator.LessThanOrEqual,
"≤" => ValueTypeConditionResolver<T>.Operator.LessThanOrEqual,
"equals" => ValueTypeConditionResolver<T>.Operator.Equal,
">=" or "≥" => ValueTypeConditionResolver<T>.Operator.GreaterThanOrEqual,
"<=" or "≤" => ValueTypeConditionResolver<T>.Operator.LessThanOrEqual,
"in" => ValueTypeConditionResolver<T>.Operator.InRange,
"!in" => ValueTypeConditionResolver<T>.Operator.OutOfRange,
_ => throw new ArgumentException($"Invalid operator {operatorStr} for value type.")
@@ -553,10 +549,7 @@ namespace Serein.NodeFlow.Tool.SereinExpression
{
return operatorStr switch
{
"is" => BoolConditionResolver.Operator.Is,
"==" => BoolConditionResolver.Operator.Is,
"equals" => BoolConditionResolver.Operator.Is,
//"isFalse" => BoolConditionNode.Operator.IsFalse,
"is" or "==" or "equals" => BoolConditionResolver.Operator.Is,
_ => throw new ArgumentException($"Invalid operator {operatorStr} for bool type.")
};
}
@@ -569,21 +562,16 @@ namespace Serein.NodeFlow.Tool.SereinExpression
/// <exception cref="ArgumentException"></exception>
private static StringConditionResolver.Operator ParseStringOperator(string operatorStr)
{
return operatorStr switch
return operatorStr.ToLower() switch
{
"c" => StringConditionResolver.Operator.Contains,
"nc" => StringConditionResolver.Operator.DoesNotContain,
"sw" => StringConditionResolver.Operator.StartsWith,
"ew" => StringConditionResolver.Operator.EndsWith,
"c" or "contains" => StringConditionResolver.Operator.Contains,
"nc" or "doesnotcontain" => StringConditionResolver.Operator.DoesNotContain,
"sw" or "startswith" => StringConditionResolver.Operator.StartsWith,
"ew" or "endswith" => StringConditionResolver.Operator.EndsWith,
"==" or "equals" => StringConditionResolver.Operator.Equal,
"!=" or "notequals" => StringConditionResolver.Operator.NotEqual,
"contains" => StringConditionResolver.Operator.Contains,
"doesNotContain" => StringConditionResolver.Operator.DoesNotContain,
"equals" => StringConditionResolver.Operator.Equal,
"==" => StringConditionResolver.Operator.Equal,
"notEquals" => StringConditionResolver.Operator.NotEqual,
"!=" => StringConditionResolver.Operator.NotEqual,
"startsWith" => StringConditionResolver.Operator.StartsWith,
"endsWith" => StringConditionResolver.Operator.EndsWith,
_ => throw new ArgumentException($"Invalid operator {operatorStr} for string type.")
};
}

View File

@@ -20,9 +20,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Library", "Library\S
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Net462DllTest", "Net462DllTest\Net462DllTest.csproj", "{E40EE629-1A38-4011-88E3-9AD036869987}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.FlowRemoteManagement", "Serein.FlowRemoteManagement\Serein.FlowRemoteManagement.csproj", "{3E568C47-74C6-4C28-9D43-C9BA29008DB7}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Extend.RemoteControl", "Extend.FlowRemoteManagement\Serein.Extend.RemoteControl.csproj", "{3E568C47-74C6-4C28-9D43-C9BA29008DB7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serein.FlowStartTool", "Serein.FlowStartTool\Serein.FlowStartTool.csproj", "{6FB346F6-B83D-49BC-AB4E-291AE6289BBE}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.WorkBench.Remote", "WorkBench.Remote\Serein.WorkBench.Remote.csproj", "{D550688A-4EAB-4872-8243-66D39FE3817D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.WorkBench.ControlLibrary.Core", "WorkBench.ControlLibrary.Core\Serein.WorkBench.ControlLibrary.Core.csproj", "{789E3885-3D64-461B-BEF1-DC965E7CEF57}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.FlowStartTool", "FlowStartTool\Serein.FlowStartTool.csproj", "{38D0FA92-5139-4616-A41E-8186AA4C1532}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -58,10 +62,18 @@ Global
{3E568C47-74C6-4C28-9D43-C9BA29008DB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E568C47-74C6-4C28-9D43-C9BA29008DB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E568C47-74C6-4C28-9D43-C9BA29008DB7}.Release|Any CPU.Build.0 = Release|Any CPU
{6FB346F6-B83D-49BC-AB4E-291AE6289BBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FB346F6-B83D-49BC-AB4E-291AE6289BBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FB346F6-B83D-49BC-AB4E-291AE6289BBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FB346F6-B83D-49BC-AB4E-291AE6289BBE}.Release|Any CPU.Build.0 = Release|Any CPU
{D550688A-4EAB-4872-8243-66D39FE3817D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D550688A-4EAB-4872-8243-66D39FE3817D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D550688A-4EAB-4872-8243-66D39FE3817D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D550688A-4EAB-4872-8243-66D39FE3817D}.Release|Any CPU.Build.0 = Release|Any CPU
{789E3885-3D64-461B-BEF1-DC965E7CEF57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{789E3885-3D64-461B-BEF1-DC965E7CEF57}.Debug|Any CPU.Build.0 = Debug|Any CPU
{789E3885-3D64-461B-BEF1-DC965E7CEF57}.Release|Any CPU.ActiveCfg = Release|Any CPU
{789E3885-3D64-461B-BEF1-DC965E7CEF57}.Release|Any CPU.Build.0 = Release|Any CPU
{38D0FA92-5139-4616-A41E-8186AA4C1532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{38D0FA92-5139-4616-A41E-8186AA4C1532}.Debug|Any CPU.Build.0 = Debug|Any CPU
{38D0FA92-5139-4616-A41E-8186AA4C1532}.Release|Any CPU.ActiveCfg = Release|Any CPU
{38D0FA92-5139-4616-A41E-8186AA4C1532}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@@ -0,0 +1,50 @@
using System.Text;
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.ControlLibrary.Core
{
/// <summary>
/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
///
/// Step 1a) Using this custom control in a XAML file that exists in the current project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:Serein.WorkBench.ControlLibrary.Core"
///
///
/// Step 1b) Using this custom control in a XAML file that exists in a different project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:Serein.WorkBench.ControlLibrary.Core;assembly=Serein.WorkBench.ControlLibrary.Core"
///
/// You will also need to add a project reference from the project where the XAML file lives
/// to this project and Rebuild to avoid compilation errors:
///
/// Right click on the target project in the Solution Explorer and
/// "Add Reference"->"Projects"->[Select this project]
///
///
/// Step 2)
/// Go ahead and use your control in the XAML file.
///
/// <MyNamespace:CustomControl1/>
///
/// </summary>
public class CustomControl1 : Control
{
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
}
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,9 @@
<Application x:Class="Serein.RemoteWorkBench.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Serein.RemoteWorkBench"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,14 @@
using System.Configuration;
using System.Data;
using System.Windows;
namespace Serein.RemoteWorkBench
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

@@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@@ -0,0 +1,12 @@
<Window x:Class="Serein.RemoteWorkBench.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Serein.RemoteWorkBench"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
</Grid>
</Window>

View File

@@ -0,0 +1,24 @@
using System.Text;
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.RemoteWorkBench
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,127 @@
using Serein.Library.Entity;
using Serein.NodeFlow.Base;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Serein.WorkBench.Node.ViewModel
{
public abstract class NodeControlViewModelBase : INotifyPropertyChanged
{
public NodeControlViewModelBase(NodeModelBase node)
{
Node = node;
MethodDetails = Node.MethodDetails;
}
/// <summary>
/// 对应的节点实体类
/// </summary>
internal NodeModelBase Node { get; }
private bool isSelect;
/// <summary>
/// 表示节点控件是否被选中
/// </summary>
internal bool IsSelect
{
get => isSelect;
set
{
isSelect = value;
OnPropertyChanged();
}
}
public NodeDebugSetting DebugSetting
{
get => Node.DebugSetting;
set
{
if (value != null)
{
Node.DebugSetting = value;
OnPropertyChanged(/*nameof(DebugSetting)*/);
}
}
}
public MethodDetails MethodDetails
{
get => Node.MethodDetails;
set
{
if(value != null)
{
Node.MethodDetails = value;
OnPropertyChanged(/*nameof(MethodDetails)*/);
}
}
}
private bool isInterrupt;
public bool IsInterrupt
{
get => isInterrupt;
set
{
isInterrupt = value;
OnPropertyChanged(/*nameof(IsInterrupt)*/);
}
}
//public bool IsInterrupt
//{
// get => Node.DebugSetting.IsInterrupt;
// set
// {
// if (value)
// {
// Node.Interrupt();
// }
// else
// {
// Node.CancelInterrupt();
// }
// OnPropertyChanged(nameof(IsInterrupt));
// }
//}
//public bool IsProtectionParameter
//{
// get => MethodDetails.IsProtectionParameter;
// set
// {
// MethodDetails.IsProtectionParameter = value;
// OnPropertyChanged(nameof(IsInterrupt));
// }
//}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void Selected()
{
IsSelect = true;
}
public void CancelSelect()
{
IsSelect = false;
}
}
}

View File

@@ -0,0 +1,79 @@
<local:NodeControlBase x:Class="Serein.WorkBench.Node.View.ActionNodeControl"
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.Node.View"
xmlns:vm="clr-namespace:Serein.WorkBench.Node.ViewModel"
xmlns:Converters="clr-namespace:Serein.WorkBench.Tool.Converters"
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
MaxWidth="300">
<UserControl.Resources>
<!--<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />-->
<Converters:InvertableBooleanToVisibilityConverter x:Key="InvertedBoolConverter"/>
</UserControl.Resources>
<Border BorderBrush="#8DE9FD" BorderThickness="1">
<Grid>
<Grid.ToolTip>
<ToolTip Background="LightYellow" Foreground="#071042" Content="{Binding MethodDetails.MethodName, UpdateSourceTrigger=PropertyChanged}" />
</Grid.ToolTip>
<Border>
<Border.Style>
<Style TargetType="Border">
<!-- 默认无边框 -->
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsInterrupt}" Value="True">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="Background" Value="#80000000" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<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"/>
</StackPanel>
<themes:MethodDetailsControl Grid.Row="1" MethodDetails="{Binding MethodDetails}"/>
<!-- ParameterProtectionMask 参数保护 -->
<!--取反 Visibility="{Binding DebugSetting.IsEnable, Converter={StaticResource InvertedBoolConverter}, ConverterParameter=Inverted}"-->
<Border Grid.Row="1" x:Name="ParameterProtectionMask" Background="LightBlue" Opacity="0.5" BorderBrush="#0A4651" BorderThickness="0"
Visibility="{Binding MethodDetails.IsProtectionParameter, Converter={StaticResource InvertedBoolConverter},ConverterParameter=Normal}" />
<Grid Grid.Row="2" Background="#D5F0FC" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderThickness="1">
<TextBlock Text="result" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<Border Grid.Column="1" BorderThickness="1">
<TextBlock Text="{Binding MethodDetails.ReturnType}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Border>
</Grid>
</Grid>
</Border>
<!--Visibility="{Binding IsEnable, Converter={StaticResource BoolToVisConverter}, ConverterParameter=False}"-->
</Grid>
</Border>
</local:NodeControlBase>

View File

@@ -0,0 +1,19 @@
using Serein.NodeFlow.Model;
using Serein.WorkBench.Node.ViewModel;
using System.Runtime.CompilerServices;
using System.Windows.Controls;
namespace Serein.WorkBench.Node.View
{
/// <summary>
/// ActionNode.xaml 的交互逻辑
/// </summary>
public partial class ActionNodeControl : NodeControlBase
{
public ActionNodeControl(ActionNodeControlViewModel viewModel):base(viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,21 @@
<local:NodeControlBase x:Class="Serein.WorkBench.Node.View.ActionRegionControl"
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.Node.View"
MaxWidth="300">
<Grid>
<!--<Border BorderBrush="Black" BorderThickness="1" Padding="10">
<StackPanel>
<Grid Margin="2,2,2,5">
<TextBlock Text="动作区域" FontWeight="Bold" HorizontalAlignment="Left" FontSize="14" Margin="0,1,0,0"/>
<Button Content="编辑" FontWeight="Bold" HorizontalAlignment="Right"/>
</Grid>
<ListBox x:Name="ActionsListBox" AllowDrop="True" Drop="ActionsListBox_Drop" />
</StackPanel>
</Border>-->
</Grid>
</local:NodeControlBase>

View File

@@ -0,0 +1,130 @@
using Serein.NodeFlow;
using Serein.NodeFlow.Model;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace Serein.WorkBench.Node.View
{
/// <summary>
/// ActionRegion.xaml 的交互逻辑
/// </summary>
public partial class ActionRegionControl : NodeControlBase
{
private Point _dragStartPoint;
//private new readonly CompositeActionNode Node;
//public override NodeControlViewModel ViewModel { get ; set ; }
public ActionRegionControl() : base(null)
{
InitializeComponent();
}
//public ActionRegionControl(CompositeActionNode node)
//{
// InitializeComponent();
// //ViewModel = new NodeControlViewModel(node);
// DataContext = ViewModel;
// base.Name = "动作组合节点";
//}
public void AddAction(NodeControlBase node, bool isTask = false)
{
/*TextBlock actionText = new TextBlock
{
Text = node.MethodDetails.MethodName + (isTask ? " (Task)" : ""),
Margin = new Thickness(10, 2, 0, 0),
Tag = node.MethodDetails,
};*/
/// Node?.AddNode((SingleActionNode)node.ViewModel.Node);
// ActionsListBox.Items.Add(node);
}
/* public async Task ExecuteActions(DynamicContext context)
{
foreach (TextBlock item in ActionsListBox.Items)
{
dynamic tag = item.Tag;
IAction action = tag.Action;
bool isTask = tag.IsTask;
if (isTask)
{
await Task.Run(() => action.Execute(Node.MethodDetails, context));
}
else
{
action.Execute(Node.MethodDetails, context);
}
}
}*/
private void ActionsListBox_Drop(object sender, DragEventArgs e)
{
/*if (e.Data.GetDataPresent("Type"))
{
Type droppedType = e.Data.GetData("Type") as Type;
if (droppedType != null && typeof(ICondition).IsAssignableFrom(droppedType) && droppedType.IsClass)
{
// 创建一个新的 TextBlock 并设置其属性
TextBlock conditionText = new TextBlock
{
Text = droppedType.Name,
Margin = new Thickness(10, 2, 0, 0),
Tag = droppedType
};
// 为 TextBlock 添加鼠标左键按下事件处理程序
// conditionText.MouseLeftButtonDown += TypeText_MouseLeftButtonDown;
// 为 TextBlock 添加鼠标移动事件处理程序
// conditionText.MouseMove += TypeText_MouseMove;
// 将 TextBlock 添加到 ActionsListBox 中
ActionsListBox.Items.Add(conditionText);
}
}*/
e.Handled = true;
}
// 用于拖动的鼠标事件处理程序
private void TypeText_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_dragStartPoint = e.GetPosition(null);
}
private void TypeText_MouseMove(object sender, MouseEventArgs e)
{
Point mousePos = e.GetPosition(null);
Vector diff = _dragStartPoint - mousePos;
if (e.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
{
if (sender is TextBlock typeText)
{
MoveNodeData moveNodeData = new MoveNodeData
{
NodeControlType = Library.Enums.NodeControlType.ConditionRegion
};
// 创建一个 DataObject 用于拖拽操作,并设置拖拽效果
DataObject dragData = new DataObject(MouseNodeType.CreateDllNodeInCanvas, moveNodeData);
DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move);
//var dragData = new DataObject(MouseNodeType.CreateNodeInCanvas, typeText.Tag);
//DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move);
}
}
}
}
}

View File

@@ -0,0 +1,72 @@
<local:NodeControlBase x:Class="Serein.WorkBench.Node.View.ConditionNodeControl"
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.Node.View"
xmlns:vm="clr-namespace:Serein.WorkBench.Node.ViewModel"
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
MaxWidth="300">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</UserControl.Resources>
<Grid>
<Grid.ToolTip>
<ToolTip Background="LightYellow" Foreground="Black" Content="{Binding MethodDetails.MethodTips, UpdateSourceTrigger=PropertyChanged}" />
</Grid.ToolTip>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="#A8D8EA" BorderBrush="#A8D8EA" BorderThickness="1" HorizontalAlignment="Stretch">
<TextBlock Text="条件节点" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Grid Grid.Row="1" Background="#F1FFDF" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0" IsChecked="{Binding IsCustomData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> <!--Converter={StaticResource BoolToVis}-->
<TextBox Grid.Column="1" MinWidth="50" Text="{Binding CustomData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Stretch" VerticalAlignment="Center">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsCustomData}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBlock Grid.Column="1" MinWidth="50" Text="上一节点数据" HorizontalAlignment="Stretch" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsCustomData}" Value="False">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
<TextBox Grid.Row="2" Background="#f1F66F" MinWidth="100" Text="{Binding Expression, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
<!--<themes:MethodDetailsControl Grid.Row="1" MethodDetails="{Binding MethodDetails}" />
<Border Grid.Row="2" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
<TextBlock Text="{Binding MethodDetails.MethodTips, Converter={StaticResource TypeToStringConverter}, StringFormat=return:{0}, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>-->
</Grid>
</local:NodeControlBase>

View File

@@ -0,0 +1,26 @@
using Serein.NodeFlow.Model;
using Serein.WorkBench.Node.ViewModel;
namespace Serein.WorkBench.Node.View
{
/// <summary>
/// ConditionNode.xaml 的交互逻辑
/// </summary>
public partial class ConditionNodeControl : NodeControlBase
{
public ConditionNodeControl() : base()
{
// 窗体初始化需要
ViewModel = new ConditionNodeControlViewModel (new SingleConditionNode());
DataContext = ViewModel;
InitializeComponent();
}
public ConditionNodeControl(ConditionNodeControlViewModel viewModel):base(viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,19 @@
<local:NodeControlBase x:Class="Serein.WorkBench.Node.View.ConditionRegionControl"
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.Node.View"
MaxWidth="300">
<Grid>
<Border BorderBrush="Black" BorderThickness="1" Padding="10">
<StackPanel>
<DockPanel Margin="2,2,2,5">
<TextBlock Text="条件区域" FontWeight="Bold" HorizontalAlignment="Left" FontSize="14" Margin="0,1,0,0"/>
<Button Content="编辑" FontWeight="Bold" HorizontalAlignment="Right"/>
</DockPanel>
<ListBox x:Name="ConditionsListBox" AllowDrop="True" Drop="ConditionsListBox_Drop"/>
</StackPanel>
</Border>
</Grid>
</local:NodeControlBase>

View File

@@ -0,0 +1,95 @@
using Serein.NodeFlow.Model;
using Serein.WorkBench.Node.ViewModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace Serein.WorkBench.Node.View
{
/// <summary>
/// ConditionRegion.xaml 的交互逻辑
/// </summary>
public partial class ConditionRegionControl : NodeControlBase
{
public ConditionRegionControl() : base()
{
InitializeComponent();
}
public ConditionRegionControl(ConditionRegionNodeControlViewModel viewModel) : base(viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
/// <summary>
/// 添加条件控件
/// </summary>
/// <param name="condition"></param>
public void AddCondition(NodeControlBase node)
{
((CompositeConditionNode)ViewModel.Node).AddNode((SingleConditionNode)node.ViewModel.Node);
this.Width += node.Width;
this.Height += node.Height;
ConditionsListBox.Items.Add(node);
}
private void ConditionsListBox_Drop(object sender, DragEventArgs e)
{
e.Handled = true;
}
// Mouse event handlers for dragging
//private void TypeText_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
//{
// _dragStartPoint = e.GetPosition(null);
//}
//private void TypeText_MouseMove(object sender, MouseEventArgs e)
//{
// Point mousePos = e.GetPosition(null);
// Vector diff = _dragStartPoint - mousePos;
// if (e.LeftButton == MouseButtonState.Pressed &&
// (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
// Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
// {
// if (sender is TextBlock typeText)
// {
// var dragData = new DataObject(MouseNodeType.RegionType, typeText.Tag);
// DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move);
// }
// }
//}
/*private void TypeText_MouseMove(object sender, MouseEventArgs e)
{
Point mousePos = e.GetPosition(null);
Vector diff = _dragStartPoint - mousePos;
if (e.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
{
TextBlock typeText = sender as TextBlock;
if (typeText != null)
{
DataObject dragData = new DataObject("Type", typeText.Tag);
DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move);
}
}
}*/
}
}

View File

@@ -0,0 +1,40 @@
<UserControl x:Class="Serein.WorkBench.Node.View.DllControl"
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.Node.View"
MaxWidth="300"
>
<DockPanel>
<StackPanel DockPanel.Dock="Top" >
<TextBlock Text="{Binding Path=Header, RelativeSource={RelativeSource AncestorType=UserControl}}"
FontWeight="Bold" FontSize="14" Margin="5" Background="#dbe2ef"/>
</StackPanel>
<DockPanel>
<Grid>
<Grid.RowDefinitions>
<!--<RowDefinition Height="*"/>-->
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<!--<ColumnDefinition Width="*" />-->
</Grid.ColumnDefinitions>
<!--<GroupBox Grid.Row="0" Header="条件" Margin="5">
<ListBox x:Name="ConditionsListBox" Background="#A8D8EA"/>
</GroupBox>-->
<GroupBox Grid.Row="0" Header="动作" Margin="5">
<ListBox x:Name="ActionsListBox" Background="#D0F1F9"/>
</GroupBox>
<GroupBox Grid.Row="1" Header="触发器" Margin="5">
<ListBox x:Name="FlipflopsListBox" Background="#FACFC1"/>
</GroupBox>
</Grid>
</DockPanel>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,160 @@
using Serein.Library.Api;
using Serein.Library.Entity;
using Serein.Library.Enums;
using Serein.NodeFlow;
using System.Reflection;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Input;
namespace Serein.WorkBench.Node.View
{
/// <summary>
/// UserControl1.xaml 的交互逻辑
/// </summary>
public partial class DllControl : UserControl
{
private readonly NodeLibrary nodeLibrary;
public DllControl()
{
Header = "DLL文件"; // 设置初始值
InitializeComponent();
}
public DllControl(NodeLibrary nodeLibrary)
{
this.nodeLibrary = nodeLibrary;
Header = "DLL name : " + nodeLibrary.Assembly.GetName().Name;
InitializeComponent();
}
/// <summary>
/// Header 依赖属性,用于绑定标题
/// </summary>
public string Header
{
get { return (string)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(string), typeof(DllControl), new PropertyMetadata(string.Empty));
/// <summary>
/// 向动作面板添加类型的文本块
/// </summary>
/// <param name="type">要添加的类型</param>
public void AddAction(MethodDetails md)
{
AddTypeToListBox(md, ActionsListBox);
}
/// <summary>
/// 向触发器面板添加类型的文本块
/// </summary>
/// <param name="type">要添加的类型</param>
public void AddFlipflop(MethodDetails md)
{
AddTypeToListBox(md, FlipflopsListBox);
}
/// <summary>
/// 向指定面板添加类型的文本块
/// </summary>
/// <param name="type">要添加的类型</param>
/// <param name="panel">要添加到的面板</param>
private void AddTypeToListBox(MethodDetails md, ListBox listBox)
{
// 创建一个新的 TextBlock 并设置其属性
TextBlock typeText = new TextBlock
{
Text = $"{md.MethodTips}",
Margin = new Thickness(10, 2, 0, 0),
Tag = md
};
// 为 TextBlock 添加鼠标左键按下事件处理程序
typeText.MouseLeftButtonDown += TypeText_MouseLeftButtonDown;
// 为 TextBlock 添加鼠标移动事件处理程序
typeText.MouseMove += TypeText_MouseMove;
// 将 TextBlock 添加到指定的面板
listBox.Items.Add(typeText);
}
/// <summary>
/// 存储拖拽开始时的鼠标位置
/// </summary>
private Point _dragStartPoint;
/// <summary>
/// 处理 TextBlock 的鼠标左键按下事件
/// </summary>
/// <param name="sender">事件源</param>
/// <param name="e">事件参数</param>
private void TypeText_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// 记录鼠标按下时的位置
_dragStartPoint = e.GetPosition(null);
}
/// <summary>
/// 处理 TextBlock 的鼠标移动事件
/// </summary>
/// <param name="sender">事件源</param>
/// <param name="e">事件参数</param>
private void TypeText_MouseMove(object sender, MouseEventArgs e)
{
// 获取当前鼠标位置
Point mousePos = e.GetPosition(null);
// 计算鼠标移动的距离
Vector diff = _dragStartPoint - mousePos;
// 判断是否符合拖拽的最小距离要求
if (e.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
{
// 获取触发事件的 TextBlock
if (sender is TextBlock typeText && typeText.Tag is MethodDetails md)
{
MoveNodeData moveNodeData = new MoveNodeData
{
NodeControlType = md.MethodDynamicType switch
{
NodeType.Action => NodeControlType.Action,
NodeType.Flipflop => NodeControlType.Flipflop,
_ => NodeControlType.None,
},
MethodDetails = md,
};
if(moveNodeData.NodeControlType == NodeControlType.None)
{
return;
}
// 创建一个 DataObject 用于拖拽操作,并设置拖拽效果
DataObject dragData = new DataObject(MouseNodeType.CreateDllNodeInCanvas, moveNodeData);
DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move);
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
<local:NodeControlBase x:Class="Serein.WorkBench.Node.View.ExpOpNodeControl"
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.Node.View"
MaxWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--<TextBlock Grid.Row="0" Text=""></TextBlock>-->
<StackPanel Grid.Row="0" Orientation="Vertical" Background="LightSteelBlue">
<TextBlock Grid.Row="2" Text="表达式"></TextBlock>
<TextBox Text="{Binding Expression, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch"></TextBox>
</StackPanel>
</Grid>
</local:NodeControlBase>

View File

@@ -0,0 +1,24 @@
using Serein.NodeFlow.Model;
using Serein.WorkBench.Node.ViewModel;
namespace Serein.WorkBench.Node.View
{
/// <summary>
/// ExprOpNodeControl.xaml 的交互逻辑
/// </summary>
public partial class ExpOpNodeControl : NodeControlBase
{
public ExpOpNodeControl() : base()
{
// 窗体初始化需要
ViewModel = new ExpOpNodeViewModel(new SingleExpOpNode());
DataContext = ViewModel;
InitializeComponent();
}
public ExpOpNodeControl(ExpOpNodeViewModel viewModel) :base(viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,62 @@
<local:NodeControlBase x:Class="Serein.WorkBench.Node.View.FlipflopNodeControl"
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:Converters="clr-namespace:Serein.WorkBench.Tool.Converters"
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
xmlns:vm="clr-namespace:Serein.WorkBench.Node.ViewModel"
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
MaxWidth="300">
<UserControl.Resources>
<vm:TypeToStringConverter x:Key="TypeToStringConverter"/>
<!--<themes:ConditionControl x:Key="ConditionControl"/>-->
<Converters:InvertableBooleanToVisibilityConverter x:Key="InvertedBoolConverter"/>
</UserControl.Resources>
<Border BorderBrush="#FCB334" BorderThickness="1">
<Grid>
<Grid.ToolTip>
<ToolTip Background="LightYellow" Foreground="#071042" Content="{Binding MethodDetails.MethodName, UpdateSourceTrigger=PropertyChanged}" />
</Grid.ToolTip>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Background="#FCB334">
<CheckBox IsChecked="{Binding DebugSetting.IsEnable, Mode=TwoWay}" VerticalContentAlignment="Center"/>
<CheckBox IsChecked="{Binding MethodDetails.IsProtectionParameter, Mode=TwoWay}" VerticalContentAlignment="Center"/>
<TextBlock Text="{Binding MethodDetails.MethodTips, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
<themes:MethodDetailsControl Grid.Row="1" MethodDetails="{Binding MethodDetails}" />
<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}" />
<!--<Border Grid.Row="0" Background="#FCB334" >
</Border>-->
<!--<themes:ExplicitDataControl Grid.Row="1" ExplicitDatas="{Binding ExplicitDatas}" />-->
<Grid Grid.Row="2" Background="#D5F0FC" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderThickness="1">
<TextBlock Text="result" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<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>
</Border>
</local:NodeControlBase>

View File

@@ -0,0 +1,17 @@
using Serein.NodeFlow.Model;
using Serein.WorkBench.Node.ViewModel;
namespace Serein.WorkBench.Node.View
{
/// <summary>
/// StateNode.xaml 的交互逻辑
/// </summary>
public partial class FlipflopNodeControl : NodeControlBase
{
public FlipflopNodeControl(FlipflopNodeControlViewModel viewModel) : base(viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,61 @@
using Serein.Library.Api;
using Serein.Library.Entity;
using Serein.NodeFlow.Base;
using Serein.WorkBench.Node.ViewModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Controls;
using System.Windows.Media;
namespace Serein.WorkBench.Node.View
{
/// <summary>
/// 节点控件基类(控件)
/// </summary>
public abstract class NodeControlBase : UserControl, IDynamicFlowNode
{
public NodeControlViewModelBase ViewModel { get; set; }
protected NodeControlBase()
{
this.Background = Brushes.Transparent;
}
protected NodeControlBase(NodeControlViewModelBase viewModelBase)
{
ViewModel = viewModelBase;
this.Background = Brushes.Transparent;
}
}
public class FLowNodeObObservableCollection<T> : ObservableCollection<T>
{
public void AddRange(IEnumerable<T> items)
{
foreach (var item in items)
{
this.Items.Add(item);
}
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
}
}
}

View File

@@ -0,0 +1,15 @@
using Serein.NodeFlow.Model;
using Serein.WorkBench.Node.View;
namespace Serein.WorkBench.Node.ViewModel
{
public class ActionNodeControlViewModel : NodeControlViewModelBase
{
private readonly SingleActionNode node;
public ActionNodeControlViewModel(SingleActionNode node):base(node)
{
this.node = node;
}
}
}

View File

@@ -0,0 +1,44 @@
using Serein.NodeFlow.Model;
using Serein.WorkBench.Node.View;
namespace Serein.WorkBench.Node.ViewModel
{
public class ConditionNodeControlViewModel : NodeControlViewModelBase
{
private readonly SingleConditionNode singleConditionNode;
/// <summary>
/// 是否为自定义参数
/// </summary>
public bool IsCustomData
{
get => singleConditionNode.IsCustomData;
set { singleConditionNode.IsCustomData= value; OnPropertyChanged(); }
}
/// <summary>
/// 自定义参数值
/// </summary>
public object? CustomData
{
get => singleConditionNode.CustomData;
set { singleConditionNode.CustomData = value ; OnPropertyChanged(); }
}
/// <summary>
/// 表达式
/// </summary>
public string Expression
{
get => singleConditionNode.Expression;
set { singleConditionNode.Expression = value; OnPropertyChanged(); }
}
public ConditionNodeControlViewModel(SingleConditionNode node) : base(node)
{
this.singleConditionNode = node;
//IsCustomData = false;
//CustomData = "";
//Expression = "PASS";
}
}
}

View File

@@ -0,0 +1,18 @@
using Serein.NodeFlow.Model;
using Serein.WorkBench.Node.View;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.WorkBench.Node.ViewModel
{
public class ConditionRegionNodeControlViewModel : NodeControlViewModelBase
{
public ConditionRegionNodeControlViewModel(CompositeConditionNode node):base(node)
{
}
}
}

View File

@@ -0,0 +1,26 @@
using Serein.NodeFlow.Model;
using Serein.WorkBench.Node.View;
namespace Serein.WorkBench.Node.ViewModel
{
public class ExpOpNodeViewModel: NodeControlViewModelBase
{
public readonly SingleExpOpNode node;
public string Expression
{
get => node.Expression;
set
{
node.Expression = value;
OnPropertyChanged();
}
}
public ExpOpNodeViewModel(SingleExpOpNode node) : base(node)
{
this.node = node;
}
}
}

View File

@@ -0,0 +1,14 @@
using Serein.NodeFlow.Model;
using Serein.WorkBench.Node.View;
namespace Serein.WorkBench.Node.ViewModel
{
public class FlipflopNodeControlViewModel : NodeControlViewModelBase
{
private readonly SingleFlipflopNode node;
public FlipflopNodeControlViewModel(SingleFlipflopNode node) : base(node)
{
this.node = node;
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace Serein.WorkBench.Node.ViewModel
{
public class TypeToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Type type)
{
return type.ToString();
}
return string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,53 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Library.Core\Serein.Library.Core.csproj" />
<ProjectReference Include="..\Library\Serein.Library.csproj" />
<ProjectReference Include="..\NodeFlow\Serein.NodeFlow.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Node\View\ActionRegionControl.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Node\View\ConditionRegionControl.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Node\View\DllControlControl.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Node\View\ExpOpNodeControl.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Themes\InputDialog.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Themes\IOCObjectViewControl.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Themes\MethodDetailsControl.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Themes\NodeTreeItemViewControl.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Themes\NodeTreeViewControl.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Themes\ObjectViewerControl.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Themes\TypeViewerWindow.xaml.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,16 @@
<UserControl x:Class="DynamicDemo.Themes.Condition.BoolConditionControl"
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:DynamicDemo.Themes.Condition"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<ComboBox x:Name="ConditionComboBox"
SelectedValue="{Binding Condition, Mode=TwoWay}">
<ComboBoxItem Content="Is True" Tag="IsTrue" />
<ComboBoxItem Content="Is False" Tag="IsFalse" />
</ComboBox>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
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 DynamicDemo.Themes.Condition
{
/// <summary>
/// BoolConditionControl.xaml 的交互逻辑
/// </summary>
public partial class BoolConditionControl : UserControl
{
public BoolConditionControl()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,21 @@
<UserControl x:Class="DynamicDemo.Themes.Condition.IntConditionControl"
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:DynamicDemo.Themes.Condition"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<ComboBox x:Name="ConditionComboBox"
SelectedValue="{Binding Condition, Mode=TwoWay}">
<ComboBoxItem Content="Greater Than" Tag="GreaterThan" />
<ComboBoxItem Content="Less Than" Tag="LessThan" />
<ComboBoxItem Content="Equal To" Tag="EqualTo" />
<ComboBoxItem Content="Between" Tag="Between" />
<ComboBoxItem Content="Not Between" Tag="NotBetween" />
<ComboBoxItem Content="Not In Range" Tag="NotInRange" />
</ComboBox>
<TextBox x:Name="ValueTextBox" Text="{Binding Value, Mode=TwoWay}" />
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
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 DynamicDemo.Themes.Condition
{
/// <summary>
/// IntConditionControl.xaml 的交互逻辑
/// </summary>
public partial class IntConditionControl : UserControl
{
public IntConditionControl()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DynamicDemo.Themes.Condition
{
//public class IntConditionNode : ConditionNode
//{
// public int Value { get; set; }
// public int MinValue { get; set; }
// public int MaxValue { get; set; }
// public List<int> ExcludeValues { get; set; }
// public override bool Evaluate(object value)
// {
// if (value is int intValue)
// {
// switch (Condition)
// {
// case ConditionType.GreaterThan:
// return intValue > Value;
// case ConditionType.LessThan:
// return intValue < Value;
// case ConditionType.EqualTo:
// return intValue == Value;
// case ConditionType.Between:
// return intValue >= MinValue && intValue <= MaxValue;
// case ConditionType.NotBetween:
// return intValue < MinValue || intValue > MaxValue;
// case ConditionType.NotInRange:
// return !ExcludeValues.Contains(intValue);
// default:
// return false;
// }
// }
// return false;
// }
//}
//public class BoolConditionNode : ConditionNode
//{
// public override bool Evaluate(object value)
// {
// if (value is bool boolValue)
// {
// switch (Condition)
// {
// case ConditionType.IsTrue:
// return boolValue;
// case ConditionType.IsFalse:
// return !boolValue;
// default:
// return false;
// }
// }
// return false;
// }
//}
//public class StringConditionNode : ConditionNode
//{
// public string Substring { get; set; }
// public override bool Evaluate(object value)
// {
// if (value is string stringValue)
// {
// switch (Condition)
// {
// case ConditionType.Contains:
// return stringValue.Contains(Substring);
// case ConditionType.DoesNotContain:
// return !stringValue.Contains(Substring);
// case ConditionType.IsNotEmpty:
// return !string.IsNullOrEmpty(stringValue);
// default:
// return false;
// }
// }
// return false;
// }
//}
}

View File

@@ -0,0 +1,18 @@
<UserControl x:Class="DynamicDemo.Themes.Condition.StringConditionControl"
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:DynamicDemo.Themes.Condition"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<ComboBox x:Name="ConditionComboBox"
SelectedValue="{Binding Condition, Mode=TwoWay}">
<ComboBoxItem Content="Contains" Tag="Contains" />
<ComboBoxItem Content="Does Not Contain" Tag="DoesNotContain" />
<ComboBoxItem Content="Is Not Empty" Tag="IsNotEmpty" />
</ComboBox>
<TextBox x:Name="SubstringTextBox" Text="{Binding Substring, Mode=TwoWay}" />
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
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 DynamicDemo.Themes.Condition
{
/// <summary>
/// StringConditionControl.xaml 的交互逻辑
/// </summary>
public partial class StringConditionControl : UserControl
{
public StringConditionControl()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,35 @@
<UserControl x:Class="DynamicDemo.Themes.ConditionControl"
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:DynamicDemo.Themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--<ComboBox x:Name="ConditionTypeComboBox" SelectionChanged="ConditionTypeComboBox_SelectionChanged">
<ComboBoxItem Content="GreaterThan" Tag="{x:Static local:ConditionType.GreaterThan}"/>
<ComboBoxItem Content="LessThan" Tag="{x:Static local:ConditionType.LessThan}"/>
<ComboBoxItem Content="EqualTo" Tag="{x:Static local:ConditionType.EqualTo}"/>
<ComboBoxItem Content="InRange" Tag="{x:Static local:ConditionType.InRange}"/>
<ComboBoxItem Content="NotInRange" Tag="{x:Static local:ConditionType.NotInRange}"/>
<ComboBoxItem Content="NotInSpecificRange" Tag="{x:Static local:ConditionType.NotInSpecificRange}"/>
<ComboBoxItem Content="IsTrue" Tag="{x:Static local:ConditionType.IsTrue}"/>
<ComboBoxItem Content="IsFalse" Tag="{x:Static local:ConditionType.IsFalse}"/>
<ComboBoxItem Content="Contains" Tag="{x:Static local:ConditionType.Contains}"/>
<ComboBoxItem Content="DoesNotContain" Tag="{x:Static local:ConditionType.DoesNotContain}"/>
<ComboBoxItem Content="IsNotEmpty" Tag="{x:Static local:ConditionType.IsNotEmpty}"/>
</ComboBox>
<TextBox x:Name="ValueTextBox" Visibility="Collapsed"/>
<TextBox x:Name="Value2TextBox" Visibility="Collapsed"/>-->
<StackPanel Grid.Row="0" x:Name="ConditionsPanel" Orientation="Vertical" Height="400"/>
<Button Grid.Row="1" Content="Add Condition" Click="OnAddConditionClicked" />
<!-- 其他控件 -->
</Grid>
</UserControl>

View File

@@ -0,0 +1,85 @@
using DynamicDemo.Themes.Condition;
using System.Windows;
using System.Windows.Controls;
namespace DynamicDemo.Themes
{
/// <summary>
/// ConditionControl.xaml 的交互逻辑
/// </summary>
public partial class ConditionControl : UserControl
{
public ConditionControl()
{
InitializeComponent();
}
//private void ConditionTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
//{
// var selectedType = (ConditionType)((ComboBoxItem)ConditionTypeComboBox.SelectedItem).Tag;
// UpdateInputVisibility(selectedType);
//}
//private void UpdateInputVisibility(ConditionType type)
//{
// ValueTextBox.Visibility = Visibility.Collapsed;
// Value2TextBox.Visibility = Visibility.Collapsed;
// switch (type)
// {
// case ConditionType.GreaterThan:
// case ConditionType.LessThan:
// case ConditionType.EqualTo:
// case ConditionType.Contains:
// case ConditionType.DoesNotContain:
// ValueTextBox.Visibility = Visibility.Visible;
// break;
// case ConditionType.InRange:
// case ConditionType.NotInRange:
// ValueTextBox.Visibility = Visibility.Visible;
// Value2TextBox.Visibility = Visibility.Visible;
// break;
// case ConditionType.IsTrue:
// case ConditionType.IsFalse:
// case ConditionType.IsNotEmpty:
// // No additional input needed
// break;
// case ConditionType.NotInSpecificRange:
// // Handle specific range input, possibly with a different control
// break;
// }
//}
private void OnAddConditionClicked(object sender, RoutedEventArgs e)
{
// 示例添加一个IntConditionNode
var intConditionNode = new IntConditionNode { Condition = ConditionType.GreaterThan, Value = 10 };
AddConditionNode(intConditionNode);
}
public void AddConditionNode(ConditionNode node)
{
UserControl control = null;
if (node is IntConditionNode)
{
control = new IntConditionControl { DataContext = node };
}
else if (node is BoolConditionNode)
{
control = new BoolConditionControl { DataContext = node };
}
else if (node is StringConditionNode)
{
control = new StringConditionControl { DataContext = node };
}
if (control != null)
{
ConditionsPanel.Children.Add(control);
}
}
}
}

View File

@@ -0,0 +1,99 @@
namespace DynamicDemo.Themes;
public enum ConditionType
{
GreaterThan,
LessThan,
EqualTo,
Between,
NotBetween,
NotInRange,
IsTrue,
IsFalse,
Contains,
DoesNotContain,
IsNotEmpty
}
public abstract class ConditionNode
{
public ConditionType Condition { get; set; }
public abstract bool Evaluate(object value);
}
public class IntConditionNode : ConditionNode
{
public int Value { get; set; }
public int MinValue { get; set; }
public int MaxValue { get; set; }
public List<int> ExcludeValues { get; set; }
public override bool Evaluate(object value)
{
if (value is int intValue)
{
switch (Condition)
{
case ConditionType.GreaterThan:
return intValue > Value;
case ConditionType.LessThan:
return intValue < Value;
case ConditionType.EqualTo:
return intValue == Value;
case ConditionType.Between:
return intValue >= MinValue && intValue <= MaxValue;
case ConditionType.NotBetween:
return intValue < MinValue || intValue > MaxValue;
case ConditionType.NotInRange:
return !ExcludeValues.Contains(intValue);
default:
return false;
}
}
return false;
}
}
public class BoolConditionNode : ConditionNode
{
public override bool Evaluate(object value)
{
if (value is bool boolValue)
{
switch (Condition)
{
case ConditionType.IsTrue:
return boolValue;
case ConditionType.IsFalse:
return !boolValue;
default:
return false;
}
}
return false;
}
}
public class StringConditionNode : ConditionNode
{
public string Substring { get; set; }
public override bool Evaluate(object value)
{
if (value is string stringValue)
{
switch (Condition)
{
case ConditionType.Contains:
return stringValue.Contains(Substring);
case ConditionType.DoesNotContain:
return !stringValue.Contains(Substring);
case ConditionType.IsNotEmpty:
return !string.IsNullOrEmpty(stringValue);
default:
return false;
}
}
return false;
}
}

View File

@@ -0,0 +1,28 @@
<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="1" Header="实例视图" Margin="5">
<ListBox x:Name="DependenciesListBox" Background="#E3FAE9"/>
</GroupBox>
<!--<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,120 @@
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.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;
using System.Xml.Linq;
namespace Serein.WorkBench.Themes
{
/// <summary>
/// IOCObjectViewControl.xaml 的交互逻辑
/// </summary>
public partial class IOCObjectViewControl : UserControl
{
public Action<string,object> SelectObj { get; set; }
public IOCObjectViewControl()
{
InitializeComponent();
}
private class IOCObj
{
public string Key { get; set; }
public object Instance { get; set; }
}
/// <summary>
/// 运行环境
/// </summary>
public IFlowEnvironment FlowEnvironment { get; set; }
/// <summary>
/// 添加一个实例
/// </summary>
/// <param name="key"></param>
/// <param name="instance"></param>
public void AddDependenciesInstance(string key,object instance)
{
IOCObj iOCObj = new IOCObj
{
Key = key,
Instance = instance,
};
TextBlock textBlock = new TextBlock();
textBlock.Text = key;
textBlock.Tag = iOCObj;
textBlock.MouseDown += (s, e) =>
{
if(s is TextBlock block && block.Tag is IOCObj iocObj)
{
SelectObj?.Invoke(iocObj.Key, iocObj.Instance);
//FlowEnvironment.SetMonitorObjState(iocObj.Instance, true); // 通知环境该节点的数据更新后需要传到UI
}
};
DependenciesListBox.Items.Add(textBlock);
SortLisbox(DependenciesListBox);
}
/// <summary>
/// 刷新一个实例
/// </summary>
/// <param name="key"></param>
/// <param name="instance"></param>
public void RefreshDependenciesInstance(string key, object instance)
{
foreach (var item in DependenciesListBox.Items)
{
if (item is TextBlock block && block.Tag is IOCObj iocObj && iocObj.Key.Equals(key))
{
iocObj.Instance = instance;
}
}
}
public void ClearObjItem()
{
DependenciesListBox.Items.Clear();
}
private static void SortLisbox(ListBox listBox)
{
var sortedItems = listBox.Items.Cast<TextBlock>().OrderBy(x => x.Text).ToList();
listBox.Items.Clear();
foreach (var item in sortedItems)
{
listBox.Items.Add(item);
}
}
public void RemoveDependenciesInstance(string key)
{
object? itemControl = null;
foreach (var item in DependenciesListBox.Items)
{
if (item is TextBlock block && block.Tag is IOCObj iocObj && iocObj.Key.Equals(key))
{
itemControl = item;
}
}
if (itemControl is not null)
{
DependenciesListBox.Items.Remove(itemControl);
}
}
}
}

View File

@@ -0,0 +1,16 @@
<Window x:Class="Serein.WorkBench.Themes.InputDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Serein.WorkBench.Themes"
mc:Ignorable="d"
Title="InputDialog" Height="450" Width="800">
<StackPanel Margin="10">
<TextBox x:Name="InputTextBox" Width="200" Margin="0,0,0,10" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="确认" Click="ConfirmButton_Click" Margin="5" />
<Button Content="取消" Click="CancelButton_Click" Margin="5" />
</StackPanel>
</StackPanel>
</Window>

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Serein.WorkBench.Themes
{
/// <summary>
/// InputDialog.xaml 的交互逻辑
/// </summary>
public partial class InputDialog : Window
{
public string InputValue { get; private set; }
public InputDialog()
{
InitializeComponent();
}
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
InputValue = InputTextBox.Text;
DialogResult = true; // 设置返回结果为 true
Close(); // 关闭窗口
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false; // 设置返回结果为 false
Close(); // 关闭窗口
}
}
}

View File

@@ -0,0 +1,115 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Serein.WorkBench.Themes"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ResourceDictionary.MergedDictionaries>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="{x:Type local:MethodDetailsControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MethodDetailsControl}">
<ItemsControl ItemsSource="{Binding MethodDetails.ParameterDetailss, RelativeSource={RelativeSource TemplatedParent}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsExplicitData}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid Background="#E3FDFD">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<TextBlock Grid.Column="3" MinWidth="50" Text="无须指定参数"/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsExplicitData}" Value="true" />
<Condition Binding="{Binding ExplicitTypeName}" Value="Select" />
</MultiDataTrigger.Conditions>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid Background="#E3FDFD">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<ComboBox Grid.Column="3"
MinWidth="50"
ItemsSource="{Binding Items}"
SelectedItem="{Binding DataValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsExplicitData}" Value="true" />
<Condition Binding="{Binding ExplicitTypeName}" Value="Value" />
<!--<Condition Binding="{Binding ExplicitTypeName}" Value="{x:Type sys:String}" />
<Condition Binding="{Binding ExplicitTypeName}" Value="{x:Type sys:Double}" />-->
</MultiDataTrigger.Conditions>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid Background="#E3FDFD">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<TextBox Grid.Column="3" MinWidth="50" Text="{Binding DataValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,64 @@
using Serein.Library.Entity;
using Serein.NodeFlow;
using System.Collections;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace Serein.WorkBench.Themes
{
public class MultiConditionConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length == 2 && values[0] is Type valueType && values[1] is bool isEnabled)
{
if (isEnabled)
{
if (valueType == typeof(string) || valueType == typeof(int) || valueType == typeof(double))
{
return "TextBoxTemplate";
}
else if (typeof(IEnumerable).IsAssignableFrom(valueType))
{
return "ComboBoxTemplate";
}
}
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public partial class MethodDetailsControl : UserControl//,ItemsControl
{
static MethodDetailsControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MethodDetailsControl), new FrameworkPropertyMetadata(typeof(MethodDetailsControl)));
}
public MethodDetails MethodDetails
{
get { return (MethodDetails)GetValue(MethodDetailsProperty); }
set { SetValue(MethodDetailsProperty, value); }
}
public static readonly DependencyProperty MethodDetailsProperty = DependencyProperty.Register("MethodDetails", typeof(MethodDetails),
typeof(MethodDetailsControl), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertyChange)));
static void OnPropertyChange(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var MethodDetails = (MethodDetails)args.NewValue;
//MethodDetails.ExplicitDatas[0].
}
}
}

View File

@@ -0,0 +1,4 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</ResourceDictionary>

View File

@@ -0,0 +1,59 @@
<UserControl x:Class="Serein.WorkBench.Themes.NodeTreeItemViewControl"
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="400" d:DesignWidth="200">
<UserControl.Resources>
<Style x:Key="CustomTreeViewItemStyle" TargetType="TreeViewItem">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" x:Name="UpstreamTreeGuid" Margin="0,0,0,0" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" Width="1" x:Name="UpstreamTreeRectangle" Grid.Row="0" Fill="#4A82E4" Margin="4,1,4,1" IsHitTestVisible="False"/>
<TreeView Grid.Column="1" x:Name="UpstreamTreeNodes" BorderThickness="0" ItemContainerStyle="{StaticResource CustomTreeViewItemStyle}"/>
</Grid>
<Grid Grid.Row="1" x:Name="IsSucceedTreeGuid" Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" Width="1" x:Name="IsSucceedRectangle" Grid.Row="0" Fill="#04FC10" Margin="4,1,4,1" IsHitTestVisible="False"/>
<TreeView Grid.Column="1" x:Name="IsSucceedTreeNodes" BorderThickness="0" ItemContainerStyle="{StaticResource CustomTreeViewItemStyle}"/>
</Grid>
<Grid Grid.Row="2" x:Name="IsFailTreeGuid" Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" Width="1" x:Name="IsFailRectangle" Grid.Row="0" Fill="#F18905" Margin="4,1,4,1" IsHitTestVisible="False"/>
<TreeView Grid.Column="1" x:Name="IsFailTreeNodes" BorderThickness="0" ItemContainerStyle="{StaticResource CustomTreeViewItemStyle}"/>
</Grid>
<Grid Grid.Row="3" x:Name="IsErrorTreeGuid" Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" Width="1" x:Name="IsErrorRectangle" Grid.Row="0" Fill="#FE1343" Margin="4,1,4,1" IsHitTestVisible="False"/>
<TreeView Grid.Column="1" x:Name="IsErrorTreeNodes" BorderThickness="0" ItemContainerStyle="{StaticResource CustomTreeViewItemStyle}"/>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,289 @@
using Serein.Library.Api;
using Serein.Library.Enums;
using Serein.NodeFlow;
using Serein.NodeFlow.Base;
using System;
using System.Collections.Generic;
using System.Drawing;
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;
using System.Xml.Linq;
using static Serein.WorkBench.Themes.TypeViewerWindow;
namespace Serein.WorkBench.Themes
{
/// <summary>
/// NodeTreeVIewControl.xaml 的交互逻辑
/// </summary>
public partial class NodeTreeItemViewControl : UserControl
{
public NodeTreeItemViewControl()
{
InitializeComponent();
foreach (var ct in NodeStaticConfig.ConnectionTypes)
{
var guid = ToGridView(this, ct);
guid.Visibility = Visibility.Collapsed;
}
}
/// <summary>
/// 保存的节点数据
/// </summary>
private NodeModelBase nodeModel;
private IFlowEnvironment flowEnvironment { get; set; }
private class NodeTreeModel
{
public NodeModelBase RootNode { get; set; }
public Dictionary<ConnectionType, List<NodeModelBase>> ChildNodes { get; set; }
}
public void InitAndLoadTree(IFlowEnvironment flowEnvironment, NodeModelBase nodeModel)
{
this.flowEnvironment = flowEnvironment;
this.nodeModel = nodeModel;
RefreshTree();
}
public TreeViewItem RefreshTree()
{
NodeModelBase rootNodeModel = this.nodeModel;
NodeTreeModel nodeTreeModel = new NodeTreeModel
{
RootNode = rootNodeModel,
ChildNodes = new Dictionary<ConnectionType, List<NodeModelBase>>()
{
{ConnectionType.Upstream, []},
{ConnectionType.IsSucceed, [rootNodeModel]},
{ConnectionType.IsFail, []},
{ConnectionType.IsError, []},
}
};
string? itemName = rootNodeModel.MethodDetails?.MethodTips;
if (string.IsNullOrEmpty(itemName))
{
itemName = rootNodeModel.ControlType.ToString();
}
var rootNode = new TreeViewItem
{
Header = itemName,
Tag = nodeTreeModel,
};
LoadNodeItem(this, nodeTreeModel);
rootNode.Expanded += TreeViewItem_Expanded; // 监听展开事件
rootNode.IsExpanded = true;
return rootNode;
}
/// <summary>
/// 展开子项事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
if (sender is TreeViewItem item && item.Tag is NodeTreeModel nodeTreeModel)
{
item.Items.Clear();
NodeTreeItemViewControl? nodeTreeItemViewControl = LoadTNoderee(nodeTreeModel);
if (nodeTreeItemViewControl is not null)
{
LoadNodeItem(nodeTreeItemViewControl, nodeTreeModel);
item.Items.Add(nodeTreeItemViewControl);
}
item.IsSelected = false;
}
e.Handled = true;
}
/// <summary>
/// 加载面板
/// </summary>
/// <param name="nodeTreeItemViewControl"></param>
/// <param name="nodeTreeModel"></param>
private void LoadNodeItem(NodeTreeItemViewControl nodeTreeItemViewControl, NodeTreeModel nodeTreeModel)
{
foreach (var ct in NodeStaticConfig.ConnectionTypes)
{
var treeViewer = ToTreeView(nodeTreeItemViewControl, ct);
var guid = ToGridView(nodeTreeItemViewControl, ct);
treeViewer.Items.Clear(); // 移除对象树的所有节点
var list = nodeTreeModel.ChildNodes[ct];
if (list.Count > 0)
{
foreach (var child in list)
{
NodeTreeModel tmpNodeTreeModel = new NodeTreeModel
{
RootNode = child,
ChildNodes = child.SuccessorNodes,
};
string? itemName = child?.MethodDetails?.MethodTips;
if (string.IsNullOrEmpty(itemName))
{
itemName = child?.ControlType.ToString();
}
TreeViewItem treeViewItem = new TreeViewItem
{
Header = itemName,
Tag = tmpNodeTreeModel
};
treeViewItem.Expanded += TreeViewItem_Expanded;
var contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem("从此节点执行", (s, e) =>
{
flowEnvironment.StartFlowInSelectNodeAsync(tmpNodeTreeModel.RootNode.Guid);
}));
contextMenu.Items.Add(MainWindow.CreateMenuItem("定位", (s, e) => flowEnvironment.NodeLocated(tmpNodeTreeModel.RootNode.Guid)));
treeViewItem.ContextMenu = contextMenu;
treeViewItem.Margin = new Thickness(-20, 0, 0, 0);
treeViewer.Items.Add(treeViewItem);
}
guid.Visibility = Visibility.Visible;
}
else
{
guid.Visibility = Visibility.Collapsed;
}
}
}
/// <summary>
/// 加载节点子项
/// </summary>
/// <param name="nodeTreeModel"></param>
/// <returns></returns>
private NodeTreeItemViewControl? LoadTNoderee(NodeTreeModel nodeTreeModel)
{
NodeTreeItemViewControl nodeTreeItemViewControl = null;
foreach (var connectionType in NodeStaticConfig.ConnectionTypes)
{
var childNodeModels = nodeTreeModel.ChildNodes[connectionType];
if (childNodeModels.Count > 0)
{
nodeTreeItemViewControl ??= new NodeTreeItemViewControl();
}
else
{
continue;
}
TreeView treeView = ToTreeView(nodeTreeItemViewControl, connectionType);
foreach (var childNodeModel in childNodeModels)
{
NodeTreeModel tempNodeTreeModel = new NodeTreeModel
{
RootNode = childNodeModel,
ChildNodes = childNodeModel.SuccessorNodes,
};
string? itemName = childNodeModel?.MethodDetails?.MethodTips;
if (string.IsNullOrEmpty(itemName))
{
itemName = childNodeModel?.ControlType.ToString();
}
TreeViewItem treeViewItem = new TreeViewItem
{
Header = itemName,
Tag = tempNodeTreeModel
};
treeViewItem.Margin = new Thickness(-20, 0, 0, 0);
treeViewItem.Visibility = Visibility.Visible;
treeView.Items.Add(treeViewItem);
}
}
if (nodeTreeItemViewControl is not null)
{
foreach (var connectionType in NodeStaticConfig.ConnectionTypes)
{
var childNodeModels = nodeTreeModel.ChildNodes[connectionType];
if (childNodeModels.Count > 0)
{
nodeTreeItemViewControl ??= new NodeTreeItemViewControl();
}
else
{
continue;
}
}
}
return nodeTreeItemViewControl;
}
/// <summary>
/// 折叠事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TreeViewItem_Collapsed(object sender, RoutedEventArgs e)
{
if (sender is TreeViewItem item && item.Items.Count > 0)
{
item.Items.Clear();
}
}
public static TreeView ToTreeView(NodeTreeItemViewControl item, ConnectionType connectionType)
{
return connectionType switch
{
ConnectionType.Upstream => item.UpstreamTreeNodes,
ConnectionType.IsError => item.IsErrorTreeNodes,
ConnectionType.IsFail => item.IsFailTreeNodes,
ConnectionType.IsSucceed => item.IsSucceedTreeNodes,
_ => throw new Exception("LoadNodeItem Error ConnectionType is " + connectionType)
};
}
public static Grid ToGridView(NodeTreeItemViewControl item, ConnectionType connectionType)
{
return connectionType switch
{
ConnectionType.Upstream => item.UpstreamTreeGuid,
ConnectionType.IsError => item.IsErrorTreeGuid,
ConnectionType.IsFail => item.IsFailTreeGuid,
ConnectionType.IsSucceed => item.IsSucceedTreeGuid,
_ => throw new Exception("LoadNodeItem Error ConnectionType is " + connectionType)
};
}
//public static System.Windows.Shapes.Rectangle ToRectangle(NodeTreeItemViewControl item, ConnectionType connectionType)
//{
// return connectionType switch
// {
// ConnectionType.Upstream => item.UpstreamTreeRectangle,
// ConnectionType.IsError => item.IsErrorRectangle,
// ConnectionType.IsFail => item.IsFailRectangle,
// ConnectionType.IsSucceed => item.IsSucceedRectangle,
// _ => throw new Exception("LoadNodeItem Error ConnectionType is " + connectionType)
// };
//}
}
}

View File

@@ -0,0 +1,47 @@
<UserControl x:Class="Serein.WorkBench.Themes.NodeTreeViewControl"
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">
<UserControl.Resources>
<Style x:Key="ListItemNullFocusContainerStyle" TargetType="ListBoxItem">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" Background="Transparent" SnapsToDevicePixels="True">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<!--<RowDefinition Height="*"/>-->
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<TextBlock Text="起始节点"/>
<ScrollViewer >
<local:NodeTreeItemViewControl x:Name="StartNodeViewer" Margin="4,4,4,4"/>
</ScrollViewer >
</StackPanel>
<StackPanel Grid.Row="1">
<TextBlock Text="全局触发器"/>
<ListBox x:Name="GlobalFlipflopNodeListbox" BorderThickness="0" ItemContainerStyle="{StaticResource ListItemNullFocusContainerStyle}"></ListBox>
</StackPanel>
<!--<StackPanel Grid.Row="2">
<TextBlock Text="无业游民"/>
<ListBox x:Name="UnreachableNodeListbox" BorderThickness="0" ItemContainerStyle="{StaticResource ListItemNullFocusContainerStyle}"></ListBox>
</StackPanel>-->
</Grid>
</UserControl>

View File

@@ -0,0 +1,98 @@
using Serein.Library.Api;
using Serein.NodeFlow.Base;
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>
/// NodeTreeViewControl.xaml 的交互逻辑
/// </summary>
public partial class NodeTreeViewControl : UserControl
{
public NodeTreeViewControl()
{
InitializeComponent();
}
private string startNodeGuid = string.Empty;
private Dictionary<string, NodeTreeItemViewControl> globalFlipflopNodes = [];
private Dictionary<string, NodeTreeItemViewControl> unemployedNodes = [];
public void LoadNodeTreeOfStartNode(IFlowEnvironment flowEnvironment, NodeModelBase nodeModel)
{
startNodeGuid = nodeModel.Guid;
StartNodeViewer.InitAndLoadTree(flowEnvironment, nodeModel);
}
#region
public void AddGlobalFlipFlop(IFlowEnvironment flowEnvironment, NodeModelBase nodeModel)
{
if (!globalFlipflopNodes.ContainsKey(nodeModel.Guid))
{
NodeTreeItemViewControl flipflopTreeViewer = new NodeTreeItemViewControl();
flipflopTreeViewer.InitAndLoadTree(flowEnvironment, nodeModel);
globalFlipflopNodes.Add(nodeModel.Guid, flipflopTreeViewer);
GlobalFlipflopNodeListbox.Items.Add(flipflopTreeViewer);
}
}
public void RefreshGlobalFlipFlop(NodeModelBase nodeModel)
{
if (globalFlipflopNodes.TryGetValue(nodeModel.Guid, out var viewer))
{
viewer.RefreshTree();
}
}
public void RemoteGlobalFlipFlop(NodeModelBase nodeModel)
{
if (globalFlipflopNodes.TryGetValue(nodeModel.Guid, out var viewer))
{
globalFlipflopNodes.Remove(nodeModel.Guid);
GlobalFlipflopNodeListbox.Items.Remove(viewer);
}
}
#endregion
#region
public void AddUnemployed(IFlowEnvironment flowEnvironment, NodeModelBase nodeModel)
{
if (!unemployedNodes.ContainsKey(nodeModel.Guid))
{
NodeTreeItemViewControl flipflopTreeViewer = new NodeTreeItemViewControl();
flipflopTreeViewer.InitAndLoadTree(flowEnvironment, nodeModel);
unemployedNodes.Add(nodeModel.Guid, flipflopTreeViewer);
GlobalFlipflopNodeListbox.Items.Add(flipflopTreeViewer);
}
}
public void RefreshUnemployed(NodeModelBase nodeModel)
{
if (unemployedNodes.TryGetValue(nodeModel.Guid, out var viewer))
{
viewer.RefreshTree();
}
}
public void RemoteUnemployed(NodeModelBase nodeModel)
{
if (unemployedNodes.TryGetValue(nodeModel.Guid, out var viewer))
{
unemployedNodes.Remove(nodeModel.Guid);
GlobalFlipflopNodeListbox.Items.Remove(viewer);
}
}
#endregion
}
}

View File

@@ -0,0 +1,31 @@
<UserControl x:Class="Serein.WorkBench.Themes.ObjectViewerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Serein.WorkBench.Themes"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="400">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<!-- 按钮 -->
<RowDefinition Height="*" />
<!-- 树视图 -->
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0" >
<!---->
<!--<Button Grid.Row="0" HorizontalAlignment="Left" Margin="14,2,4,2" Content="监视" Width="100" Height="20" Name="TimerRefreshButton"/>-->
<!--<Button Grid.Row="0" HorizontalAlignment="Left" Margin="14,2,4,2" Content="添加监视表达式" Width="100" Height="20" Name="AddMonitorExpressionButton" Click="AddMonitorExpressionButton_Click"/>-->
<Button Grid.Row="0" HorizontalAlignment="Left" Margin="4,2,4,2" Content="刷新" Width="40" Height="20" Name="RefreshButton" Click="RefreshButton_Click"/>
<Button Grid.Row="0" HorizontalAlignment="Left" Margin="4,2,4,2" Content="添加监视表达式" Width="80" Height="20" Name="UpMonitorExpressionButton" Click="UpMonitorExpressionButton_Click"/>
<TextBox x:Name="ExpressionTextBox" Margin="4,2,4,2" Width="300"/>
</StackPanel>
<!-- 刷新按钮 -->
<!-- 树视图,用于显示对象属性 -->
<TreeView FontSize="14" x:Name="ObjectTreeView" Grid.Row="1" />
</Grid>
</UserControl>

View File

@@ -0,0 +1,671 @@
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;
using System.Reflection;
using System.Security.Cryptography;
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.Markup.Primitives;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml.Linq;
using static Serein.WorkBench.Themes.TypeViewerWindow;
namespace Serein.WorkBench.Themes
{
public class FlowDataDetails
{
/// <summary>
/// 属性名称
/// </summary>
public string? Name { get; set; }
/// <summary>
/// 属性类型
/// </summary>
public TreeItemType ItemType { get; set; }
/// <summary>
/// 数据类型
/// </summary>
public Type? DataType { get; set; }
/// <summary>
/// 数据
/// </summary>
public object? DataValue { get; set; }
/// <summary>
/// 数据路径
/// </summary>
public string DataPath { get; set; } = string.Empty;
}
/// <summary>
/// ObjectViewerControl.xaml 的交互逻辑
/// </summary>
public partial class ObjectViewerControl : UserControl
{
public ObjectViewerControl()
{
InitializeComponent();
}
/// <summary>
/// 监视类型
/// </summary>
public enum MonitorType
{
/// <summary>
/// 作用于对象(对象的引用)的监视
/// </summary>
NodeFlowData,
/// <summary>
/// 作用与节点FLowData的监视
/// </summary>
IOCObj,
}
/// <summary>
/// 运行环境
/// </summary>
public IFlowEnvironment? FlowEnvironment { get; set; }
/// <summary>
/// 监视对象的键
/// </summary>
public string? MonitorKey { get => monitorKey; }
/// <summary>
/// 正在监视的对象
/// </summary>
public object? MonitorObj { get => monitorObj; }
/// <summary>
/// 监视表达式
/// </summary>
public string? MonitorExpression { get => ExpressionTextBox.Text.ToString(); }
private string? monitorKey;
private object? monitorObj;
// 用于存储当前展开的节点路径
private HashSet<string> expandedNodePaths = new HashSet<string>();
/// <summary>
/// 加载对象信息,展示其成员
/// </summary>
/// <param name="obj">要展示的对象</param>
public void LoadObjectInformation(string key, object 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 RefreshButton_Click(object sender, RoutedEventArgs e)
{
RefreshObjectTree(monitorObj);
}
/// <summary>
/// 更新表达式
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void UpMonitorExpressionButton_Click(object sender, RoutedEventArgs e)
{
if (FlowEnvironment is not null && FlowEnvironment.AddInterruptExpression(monitorKey, MonitorExpression)) // 对象预览器尝试添加中断表达式
{
if (string.IsNullOrEmpty(MonitorExpression))
{
ExpressionTextBox.Text = "表达式已清空";
}
else
{
UpMonitorExpressionButton.Content = "更新监视表达式";
}
}
}
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的成员生成什么样的节点
rootNode.IsExpanded = true;
return rootNode;
}
/// <summary>
/// 刷新对象属性树
/// </summary>
public void RefreshObjectTree(object? obj)
{
monitorObj = obj;
var rootNode = LoadTree(obj);
if (rootNode is not null)
{
ExpandPreviouslyExpandedNodes(rootNode); // 遍历节点,展开之前记录的节点
}
}
/// <summary>
/// 展开父节点,如果路径存在哈希记录,则将其自动展开,并递归展开后的子节点。
/// </summary>
/// <param name="node"></param>
private void ExpandPreviouslyExpandedNodes(TreeViewItem node)
{
if (node == null) return;
if(node.Tag is FlowDataDetails flowDataDetails)
{
if (expandedNodePaths.Contains(flowDataDetails.DataPath))
{
node.IsExpanded = true;
}
}
foreach (TreeViewItem child in node.Items)
{
ExpandPreviouslyExpandedNodes(child);
}
}
/// <summary>
/// 展开子项事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
if (sender is TreeViewItem item)
{
if (item.Tag is FlowDataDetails flowDataDetails) // FlowDataDetails flowDataDetails object obj
{
if (flowDataDetails.ItemType != TreeItemType.Item && item.Items.Count != 0)
{
return;
}
if(flowDataDetails.DataValue is null || flowDataDetails.DataType is null)
{
return;
}
// 记录当前节点的路径
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)
{
if (sender is TreeViewItem item && item.Items.Count > 0)
{
if (item.Tag is FlowDataDetails flowDataDetails)
{
// 记录当前节点的路径
var path = flowDataDetails.DataPath;
if(path != "")
{
expandedNodePaths.Remove(path);
}
}
}
}
/// <summary>
/// 反射对象数据添加子节点
/// </summary>
/// <param name="treeViewNode"></param>
/// <param name="obj"></param>
/// <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)
{
if (member.Name.StartsWith(".") ||
member.Name.StartsWith("get_") ||
member.Name.StartsWith("set_")
)
{
// 跳过构造函数、属性的get/set方法
continue;
}
TreeViewItem? memberNode = ConfigureTreeViewItem(obj, member); // 根据对象成员生成节点对象
if (memberNode is not 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)
{
if (obj == null)
{
return null;
}
#region
if (member is PropertyInfo property)
{
#region (
if (property.PropertyType != typeof(string) && typeof(IEnumerable).IsAssignableFrom(property.PropertyType) && property.GetValue(obj) is IEnumerable collection && collection is not null)
{
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
// 处理集合类型的属性
memberNode.Tag = new FlowDataDetails
{
ItemType = TreeItemType.IEnumerable,
DataType = property.PropertyType,
Name = property.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 = property.Name,
DataValue = itemNode,
};
memberNode.Items.Add(itemNode);
}
memberNode.Header = $"{property.Name} : {property.PropertyType.Name} [{index}]";
if (!property.PropertyType.IsPrimitive && property.PropertyType != typeof(string))
{
memberNode.Expanded += TreeViewItem_Expanded;
memberNode.Collapsed += TreeViewItem_Collapsed;
}
return memberNode;
}
#endregion
#region
else
{
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
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
}
#endregion
#region
else if (member is FieldInfo field)
{
#region (
if (field.FieldType != typeof(string) && typeof(IEnumerable).IsAssignableFrom(field.FieldType) && field.GetValue(obj) is IEnumerable collection && collection is not null)
{
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
// 处理集合类型的字段
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}]";
if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
{
memberNode.Expanded += TreeViewItem_Expanded;
memberNode.Collapsed += TreeViewItem_Collapsed;
}
return memberNode;
}
#endregion
#region
else
{
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
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
}
#endregion
#region null
else
{
return null;
}
#endregion
}
/// <summary>
/// 获取属性类型的成员
/// </summary>
/// <param name="obj"></param>
/// <param name="property"></param>
/// <returns></returns>
private string GetPropertyValue(object obj, PropertyInfo property,out object? value)
{
try
{
if(obj is null)
{
value = null;
return "Error";
}
var properties = obj.GetType().GetProperties();
// 获取实例属性值
value = property.GetValue(obj);
return value?.ToString() ?? "null"; // 返回值或“null”
}
catch
{
value = null;
return "Error";
}
}
/// <summary>
/// 获取字段类型的成员
/// </summary>
/// <param name="obj"></param>
/// <param name="field"></param>
/// <returns></returns>
private string GetFieldValue(object obj, FieldInfo field, out object? value)
{
try
{
value = field.GetValue(obj);
return value?.ToString() ?? "null";
}
catch
{
value = null;
return "Error";
}
}
}
}
/// <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

@@ -0,0 +1,8 @@
<UserControl x:Class="RealTimeObjectViewer.ObjectViewerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Serein.WorkBench.Themes">
<Grid>
<TreeView x:Name="ObjectTreeView" />
</Grid>
</UserControl>

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Serein.WorkBench.Themes
{
/// <summary>
/// ObjectViewerWindow.xaml 的交互逻辑
/// </summary>
public partial class ObjectViewerControl : UserControl
{
public ObjectViewerControl()
{
InitializeComponent();
}
private object _objectInstance;
private Action _closeCallback;
public void LoadObjectInformation(object obj,Action closeCallback)
{
if (obj == null || closeCallback == null)
return;
_closeCallback = closeCallback;
_objectInstance = obj;
var objectType = obj.GetType();
var rootNode = new TreeViewItem { Header = objectType.Name, Tag = obj };
// 添加占位符节点
AddPlaceholderNode(rootNode);
ObjectTreeView.Items.Clear();
ObjectTreeView.Items.Add(rootNode);
// 监听展开事件
rootNode.Expanded += TreeViewItem_Expanded;
}
private void AddPlaceholderNode(TreeViewItem node)
{
node.Items.Add(new TreeViewItem { Header = "Loading..." });
}
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
var item = (TreeViewItem)sender;
if (item.Items.Count == 1 && item.Items[0] is TreeViewItem placeholder && placeholder.Header.ToString() == "Loading...")
{
item.Items.Clear();
if (item.Tag is object obj)
{
var objectType = obj.GetType();
AddMembersToTreeNode(item, obj, objectType);
}
}
}
private void AddMembersToTreeNode(TreeViewItem node, object obj, Type type)
{
// 获取属性和字段
var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (var member in members)
{
TreeViewItem memberNode = ConfigureTreeViewItem(obj, member);
node.Items.Add(memberNode);
}
}
private TreeViewItem ConfigureTreeViewItem(object obj, MemberInfo member)
{
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
if (member is PropertyInfo property)
{
string propertyValue = GetPropertyValue(obj, property);
memberNode.Header = $"{property.Name} : {property.PropertyType.Name} = {propertyValue}";
if (!property.PropertyType.IsPrimitive && property.PropertyType != typeof(string))
{
AddPlaceholderNode(memberNode);
memberNode.Expanded += TreeViewItem_Expanded;
}
}
else if (member is FieldInfo field)
{
string fieldValue = GetFieldValue(obj, field);
memberNode.Header = $"{field.Name} : {field.FieldType.Name} = {fieldValue}";
if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
{
AddPlaceholderNode(memberNode);
memberNode.Expanded += TreeViewItem_Expanded;
}
}
return memberNode;
}
private string GetPropertyValue(object obj, PropertyInfo property)
{
try
{
var value = property.GetValue(obj);
return value?.ToString() ?? "null";
}
catch
{
return "Error";
}
}
private string GetFieldValue(object obj, FieldInfo field)
{
try
{
var value = field.GetValue(obj);
return value?.ToString() ?? "null";
}
catch
{
return "Error";
}
}
private void Window_Closed(object sender, EventArgs e)
{
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
_closeCallback?.Invoke();
}
}
}

View File

@@ -0,0 +1,16 @@
<Window x:Class="Serein.WorkBench.Themes.TypeViewerWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Serein.WorkBench.Themes"
mc:Ignorable="d"
Topmost="True"
Title="TypeViewerWindow" Height="300" Width="300">
<Grid>
<Grid>
<TreeView x:Name="TypeTreeView"/>
</Grid>
</Grid>
</Window>

View File

@@ -0,0 +1,279 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Serein.WorkBench.Themes
{
/// <summary>
/// TypeViewerWindow.xaml 的交互逻辑
/// </summary>
public partial class TypeViewerWindow : Window
{
public TypeViewerWindow()
{
InitializeComponent();
}
public Type Type { get; set; }
public void LoadTypeInformation()
{
if (Type == null)
return;
NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
{
Name = Type.Name,
DataType = Type,
};
var rootNode = new TreeViewItem { Header = Type.Name, Tag = typeNodeDetails };
AddPlaceholderNode(rootNode); // 添加占位符节点
TypeTreeView.Items.Clear();
TypeTreeView.Items.Add(rootNode);
rootNode.Expanded += TreeViewItem_Expanded; // 监听节点展开事件
}
/// <summary>
/// 添加占位符节点
/// </summary>
private void AddPlaceholderNode(TreeViewItem node)
{
node.Items.Add(new TreeViewItem { Header = "Loading..." });
}
/// <summary>
/// 节点展开事件,延迟加载子节点
/// </summary>
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
var item = (TreeViewItem)sender;
// 如果已经加载过子节点,则不再重复加载
if (item.Items.Count == 1 && item.Items[0] is TreeViewItem placeholder && placeholder.Header.ToString() == "Loading...")
{
item.Items.Clear();
if (item.Tag is NodeFlowDataObjectDetails typeNodeDetails)
{
AddMembersToTreeNode(item, typeNodeDetails.DataType);
}
}
}
/// <summary>
/// 添加属性节点
/// </summary>
private void AddMembersToTreeNode(TreeViewItem node, Type type)
{
var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
foreach (var member in members)
{
TreeViewItem memberNode = ConfigureTreeViewItem(member); // 生成类型节点的子项
if (ConfigureTreeItemMenu(memberNode,member, out ContextMenu? contextMenu))
{
memberNode.ContextMenu = contextMenu; // 设置子项节点的事件
}
node.Items.Add(memberNode); // 添加到父节点中
}
}
/// <summary>
/// 生成类型节点的子项
/// </summary>
/// <param name="member"></param>
/// <returns></returns>
private TreeViewItem ConfigureTreeViewItem(MemberInfo member)
{
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
if (member is PropertyInfo property)
{
NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
{
ItemType = TreeItemType.Property,
DataType = property.PropertyType,
Name = property.Name,
DataValue = property,
};
memberNode.Tag = typeNodeDetails;
var propertyType = typeNodeDetails.DataType;
memberNode.Header = $"{member.Name} : {propertyType.Name}";
if (!propertyType.IsPrimitive && propertyType != typeof(string))
{
// 延迟加载类型的子属性,添加占位符节点
AddPlaceholderNode(memberNode);
memberNode.Expanded += TreeViewItem_Expanded; // 监听展开事件
}
}
else if (member is MethodInfo method)
{
NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
{
ItemType = TreeItemType.Method,
DataType = typeof(MethodInfo),
Name = method.Name,
DataValue = null,
};
memberNode.Tag = typeNodeDetails;
var parameters = method.GetParameters();
var paramStr = string.Join(", ", parameters.Select(p => $"{p.ParameterType.Name} {p.Name}"));
memberNode.Header = $"{member.Name}({paramStr})";
}
else if (member is FieldInfo field)
{
NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
{
ItemType = TreeItemType.Field,
DataType = field.FieldType,
Name = field.Name,
DataValue = field,
};
memberNode.Tag = typeNodeDetails;
memberNode.Header = $"{member.Name} : {field.FieldType.Name}";
}
return memberNode;
}
/// <summary>
/// 设置子项节点的事件
/// </summary>
/// <param name="member"></param>
/// <returns></returns>
private bool ConfigureTreeItemMenu(TreeViewItem memberNode, MemberInfo member,out ContextMenu? contextMenu)
{
bool isChange = false;
if (member is PropertyInfo property)
{
isChange = true;
contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem($"取值表达式", (s, e) =>
{
string fullPath = GetNodeFullPath(memberNode);
string copyValue = "@Get " + fullPath;
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;
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;
NodeFlowDataObjectDetails typeNodeDetails = (NodeFlowDataObjectDetails)node.Tag;
var parent = GetParentTreeViewItem(node);
if (parent != null)
{
// 递归获取父节点的路径,并拼接当前节点的 Header
return $"{GetNodeFullPath(parent)}.{typeNodeDetails.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 not TreeViewItem)
{
parent = VisualTreeHelper.GetParent(parent);
}
return parent as TreeViewItem;
}
public class NodeFlowDataObjectDetails
{
/// <summary>
/// 属性名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 属性类型
/// </summary>
public TreeItemType ItemType { get; set; }
/// <summary>
/// 数据类型
/// </summary>
public Type DataType { get; set; }
/// <summary>
/// 数据(调试用?)
/// </summary>
public object DataValue { get; set; }
/// <summary>
/// 数据路径
/// </summary>
public string DataPath { get; set; }
}
public enum TreeItemType
{
Property,
Method,
Field,
IEnumerable,
Item,
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows;
namespace Serein.WorkBench.Tool.Converters
{
/// <summary>
/// 根据bool类型控制可见性
/// </summary>
[ValueConversion(typeof(bool), typeof(Visibility))]
public class InvertableBooleanToVisibilityConverter : IValueConverter
{
enum Parameters
{
Normal, Inverted
}
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var boolValue = (bool)value;
var direction = (Parameters)Enum.Parse(typeof(Parameters), (string)parameter);
if (direction == Parameters.Inverted)
return !boolValue ? Visibility.Visible : Visibility.Collapsed;
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
public object? ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return null;
}
}
}

View File

@@ -5,8 +5,11 @@ using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace Serein.WorkBench
namespace Serein.WorkBench.Tool.Converters
{
/// <summary>
/// 画布拉动范围距离计算器
/// </summary>
public class RightThumbPositionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
@@ -21,7 +24,9 @@ namespace Serein.WorkBench
throw new NotImplementedException();
}
}
/// <summary>
/// 画布拉动范围距离计算器
/// </summary>
public class BottomThumbPositionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
@@ -36,13 +41,15 @@ namespace Serein.WorkBench
throw new NotImplementedException();
}
}
/// <summary>
/// 画布拉动范围距离计算器
/// </summary>
public class VerticalCenterThumbPositionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is double height)
return (height / 2) - 5; // Centering Thumb vertically
return height / 2 - 5; // Centering Thumb vertically
return 0;
}
@@ -51,13 +58,15 @@ namespace Serein.WorkBench
throw new NotImplementedException();
}
}
/// <summary>
/// 画布拉动范围距离计算器
/// </summary>
public class HorizontalCenterThumbPositionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is double width)
return (width / 2) - 5; // Centering Thumb horizontally
return width / 2 - 5; // Centering Thumb horizontally
return 0;
}

View File

@@ -0,0 +1,26 @@
using Serein.Library.Enums;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
namespace Serein.WorkBench.Tool.Converters
{
/// <summary>
/// 根据控件类型切换颜色
/// </summary>
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();
}
}

View File

@@ -0,0 +1,85 @@
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using System.Threading.Channels;
namespace Serein.WorkBench.tool
{
/// <summary>
/// 可以捕获类库输出的打印输出
/// </summary>
public class LogTextWriter : TextWriter
{
private readonly Action<string> logAction; // 更新日志UI的委托
private readonly StringWriter stringWriter = new(); // 缓存日志内容
private readonly Channel<string> logChannel = Channel.CreateUnbounded<string>(); // 日志管道
private readonly Action clearTextBoxAction; // 清空日志UI的委托
private int writeCount = 0; // 写入计数器
private const int maxWrites = 500; // 写入最大计数
public LogTextWriter(Action<string> logAction, Action clearTextBoxAction)
{
this.logAction = logAction;
this.clearTextBoxAction = clearTextBoxAction;
// 异步启动日志处理任务,不阻塞主线程
Task.Run(ProcessLogQueueAsync);
}
public override Encoding Encoding => Encoding.UTF8;
public override void Write(char value)
{
stringWriter.Write(value);
if (value == '\n')
{
EnqueueLog();
}
}
public override void Write(string? value)
{
if (string.IsNullOrWhiteSpace(value)) return;
stringWriter.Write(value);
if (value.Contains('\n'))
{
EnqueueLog();
}
}
public override void WriteLine(string? value)
{
if (string.IsNullOrWhiteSpace(value)) return;
stringWriter.WriteLine(value);
EnqueueLog();
}
// 将日志加入通道
private void EnqueueLog()
{
var log = stringWriter.ToString();
stringWriter.GetStringBuilder().Clear();
if (!logChannel.Writer.TryWrite(log))
{
// 如果写入失败(不太可能),则直接丢弃日志或处理
}
}
// 异步处理日志队列
private async Task ProcessLogQueueAsync()
{
await foreach (var log in logChannel.Reader.ReadAllAsync()) // 异步读取日志通道
{
logAction?.Invoke(log); // 执行日志写入到UI的委托
writeCount++;
if (writeCount >= maxWrites)
{
clearTextBoxAction?.Invoke(); // 清空文本框
writeCount = 0; // 重置计数器
}
}
}
}
}

View File

@@ -12,32 +12,109 @@ namespace Serein.WorkBench
/// </summary>
public partial class App : Application
{
//public class TestObject
//{
// public NestedObject Data { get; set; }
// public class NestedObject
// {
// public int Code { get; set; }
// public int Code2 { get; set; }
// public string Tips { get; set; }
// }
// public string ToUpper(string input)
// {
// return input.ToUpper();
// }
//}
public static SereinProjectData? FlowProjectData { get; set; }
public static string FileDataPath { get; set; } = "";
public App()
{
// TestExp();
}
protected override void OnExit(ExitEventArgs e)
{
base.OnExit(e);
// 强制关闭所有窗口
foreach (Window window in Windows)
{
window.Close();
}
}
private void Application_Startup(object sender, StartupEventArgs e)
{
// 检查是否传入了参数
if (e.Args.Length == 1)
{
// 获取文件路径
string filePath = e.Args[0];
// 检查文件是否存在
if (!System.IO.File.Exists(filePath))
{
MessageBox.Show($"文件未找到:{filePath}");
Shutdown(); // 关闭应用程序
return;
}
try
{
// 读取文件内容
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
FlowProjectData = JsonConvert.DeserializeObject<SereinProjectData>(content);
FileDataPath = System.IO.Path.GetDirectoryName(filePath) ?? "";
}
catch (Exception ex)
{
MessageBox.Show($"读取文件时发生错误:{ex.Message}");
Shutdown(); // 关闭应用程序
}
}
#if DEBUG
else if(1== 1)
{
//string filePath = @"F:\临时\project\new project.dnf";
string filePath;
//filePath = @"F:\临时\project\tmp\project.dnf";
//filePath = @"D:\Project\C#\TestNetFramework\Net45DllTest\Net45DllTest\bin\Debug\project.dnf";
//filePath = @"D:\Project\C#\DynamicControl\SereinFlow\Net462DllTest\bin\Debug\project.dnf";
//filePath = @"D:\Project\C#\DynamicControl\SereinFlow\.Output\Debug\net8.0-windows7.0\project.dnf";
filePath = @"F:\临时\project\linux\project.dnf";
//string filePath = @"D:\Project\C#\DynamicControl\SereinFlow\.Output\Debug\net8.0-windows7.0\U9 project.dnf";
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
App.FlowProjectData = JsonConvert.DeserializeObject<SereinProjectData>(content);
App.FileDataPath =System.IO.Path.GetDirectoryName(filePath)!; // filePath;//
}
#endif
#if false //测试 操作表达式,条件表达式
}
#if DEBUG && false
public class TestObject
{
public NestedObject Data { get; set; }
public class NestedObject
{
public int Code { get; set; }
public int Code2 { get; set; }
public string Tips { get; set; }
}
public string ToUpper(string input)
{
return input.ToUpper();
}
}
//测试 操作表达式,条件表达式
private void TestExp()
{
#region
string expression = "";
@@ -103,76 +180,9 @@ namespace Serein.WorkBench
Debug.WriteLine($"{str} {expression} -> " + pass);
#endregion
}
#endif
}
protected override void OnExit(ExitEventArgs e)
{
base.OnExit/**/(e);
// 强制关闭所有窗口
foreach (Window window in Windows)
{
window.Close();
}
}
/// <summary>
/// 成功加载的工程文件
/// </summary>
public static SereinProjectData? FlowProjectData { get; set; }
public static string FileDataPath { get; set; } = "";
private void Application_Startup(object sender, StartupEventArgs e)
{
// 检查是否传入了参数
if (e.Args.Length == 1)
{
// 获取文件路径
string filePath = e.Args[0];
// 检查文件是否存在
if (!System.IO.File.Exists(filePath))
{
MessageBox.Show($"文件未找到:{filePath}");
Shutdown(); // 关闭应用程序
return;
}
try
{
// 读取文件内容
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
FlowProjectData = JsonConvert.DeserializeObject<SereinProjectData>(content);
FileDataPath = System.IO.Path.GetDirectoryName(filePath) ?? "";
}
catch (Exception ex)
{
MessageBox.Show($"读取文件时发生错误:{ex.Message}");
Shutdown(); // 关闭应用程序
}
}
#if DEBUG
else if(1== 1)
{
//string filePath = @"F:\临时\project\new project.dnf";
string filePath;
//filePath = @"F:\临时\project\tmp\project.dnf";
//filePath = @"D:\Project\C#\TestNetFramework\Net45DllTest\Net45DllTest\bin\Debug\project.dnf";
//filePath = @"D:\Project\C#\DynamicControl\SereinFlow\Net462DllTest\bin\Debug\project.dnf";
//filePath = @"D:\Project\C#\DynamicControl\SereinFlow\.Output\Debug\net8.0-windows7.0\project.dnf";
filePath = @"F:\临时\project\linux\project.dnf";
//string filePath = @"D:\Project\C#\DynamicControl\SereinFlow\.Output\Debug\net8.0-windows7.0\U9 project.dnf";
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
App.FlowProjectData = JsonConvert.DeserializeObject<SereinProjectData>(content);
App.FileDataPath =System.IO.Path.GetDirectoryName(filePath)!; // filePath;//
}
#endif
}
}

View File

@@ -6,18 +6,24 @@ namespace Serein.WorkBench
/// DebugWindow.xaml 的交互逻辑
/// </summary>
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
/// <summary>
/// LogWindow.xaml 的交互逻辑
/// </summary>
public partial class LogWindow : Window
{
private StringBuilder logBuffer = new StringBuilder();
private int logUpdateInterval = 100; // 批量更新的时间间隔(毫秒)
private int logUpdateInterval = 500; // 批量更新的时间间隔(毫秒)
private Timer logUpdateTimer;
private const int MaxLines = 1000; // 最大显示的行数
private bool autoScroll = true; // 自动滚动标识
private int flushThreshold = 1000; // 设置日志刷新阈值
private const int maxFlushSize = 10000; // 每次最大刷新字符数
public LogWindow()
{
@@ -39,7 +45,16 @@ namespace Serein.WorkBench
{
lock (logBuffer)
{
logBuffer.Append(text); // 将日志添加到缓冲区中
logBuffer.Append(text);
// 异步写入日志到文件
// Task.Run(() => File.AppendAllText("log.txt", text));
// 如果日志达到阈值,立即刷新
if (logBuffer.Length > flushThreshold)
{
FlushLog();
}
}
}
@@ -50,17 +65,27 @@ namespace Serein.WorkBench
{
if (logBuffer.Length == 0) return;
Dispatcher.Invoke(() =>
Dispatcher.InvokeAsync(() =>
{
lock (logBuffer)
{
LogTextBox.AppendText(logBuffer.ToString());
logBuffer.Clear(); // 清空缓冲区
// 仅追加部分日志,避免一次更新过多内容
string logContent = logBuffer.Length > maxFlushSize
? logBuffer.ToString(0, maxFlushSize)
: logBuffer.ToString();
logBuffer.Remove(0, logContent.Length); // 清空已更新的部分
LogTextBox.AppendText(logContent);
}
TrimLog(); // 检查并修剪日志长度
ScrollToEndIfNeeded(); // 根据条件滚动到末尾
});
// 不必每次都修剪日志当行数超过限制20%时再修剪
if (LogTextBox.LineCount > MaxLines * 1.2)
{
TrimLog();
}
ScrollToEndIfNeeded(); // 根据是否需要自动滚动来决定
}, System.Windows.Threading.DispatcherPriority.Background);
}
/// <summary>
@@ -71,7 +96,8 @@ namespace Serein.WorkBench
if (LogTextBox.LineCount > MaxLines)
{
// 删除最早的多余行
LogTextBox.Text = LogTextBox.Text.Substring(LogTextBox.GetCharacterIndexFromLineIndex(LogTextBox.LineCount - MaxLines));
LogTextBox.Text = LogTextBox.Text.Substring(
LogTextBox.GetCharacterIndexFromLineIndex(LogTextBox.LineCount - MaxLines));
}
}
@@ -83,7 +109,7 @@ namespace Serein.WorkBench
if (e.ExtentHeightChange == 0) // 用户手动滚动时
{
// 判断是否滚动到底部
// autoScroll = LogTextBox.VerticalOffset == LogTextBox.ScrollableHeight;
//autoScroll = LogTextBox.VerticalOffset == LogTextBox.ScrollableHeight;
}
}
@@ -122,9 +148,6 @@ namespace Serein.WorkBench
/// </summary>
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
//logUpdateTimer?.Stop();
//logUpdateTimer?.Close();
//logUpdateTimer?.Dispose();
logBuffer?.Clear();
Clear();
e.Cancel = true; // 取消关闭操作

View File

@@ -2,6 +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:tool="clr-namespace:Serein.WorkBench.Tool.Converters"
xmlns:nodeView="clr-namespace:Serein.WorkBench.Node.View"
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
Title="Dynamic Node Flow" Height="900" Width="1400"
@@ -13,10 +14,10 @@
Closing="Window_Closing">
<Window.Resources>
<local:RightThumbPositionConverter x:Key="RightThumbPositionConverter" />
<local:BottomThumbPositionConverter x:Key="BottomThumbPositionConverter" />
<local:VerticalCenterThumbPositionConverter x:Key="VerticalCenterThumbPositionConverter" />
<local:HorizontalCenterThumbPositionConverter x:Key="HorizontalCenterThumbPositionConverter" />
<tool:RightThumbPositionConverter x:Key="RightThumbPositionConverter" />
<tool:BottomThumbPositionConverter x:Key="BottomThumbPositionConverter" />
<tool:VerticalCenterThumbPositionConverter x:Key="VerticalCenterThumbPositionConverter" />
<tool:HorizontalCenterThumbPositionConverter x:Key="HorizontalCenterThumbPositionConverter" />
</Window.Resources>
<Window.InputBindings>

View File

@@ -939,7 +939,7 @@ namespace Serein.WorkBench
contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid)));
contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoteNode(nodeGuid)));
contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoveNode(nodeGuid)));
contextMenu.Items.Add(CreateMenuItem("添加 真分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsSucceed)));
contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsFail)));
@@ -1016,7 +1016,7 @@ namespace Serein.WorkBench
// 获取起始节点与终止节点,消除映射关系
var fromNodeGuid = connectionToRemove.Start.ViewModel.Node.Guid;
var toNodeGuid = connectionToRemove.End.ViewModel.Node.Guid;
FlowEnvironment.RemoteConnect(fromNodeGuid, toNodeGuid, connection.Type);
FlowEnvironment.RemoveConnect(fromNodeGuid, toNodeGuid, connection.Type);
}
/// <summary>
@@ -1392,7 +1392,7 @@ namespace Serein.WorkBench
if (node is not null && node.MethodDetails?.ReturnType != typeof(void))
{
var key = node.Guid;
var instance = node.GetFlowData();
var instance = node.GetFlowData(); // 对象预览树视图获取(后期更改)
if(instance is not null)
{
ViewObjectViewer.LoadObjectInformation(key, instance);
@@ -1864,7 +1864,7 @@ namespace Serein.WorkBench
var guid = node?.ViewModel?.Node?.Guid;
if (!string.IsNullOrEmpty(guid))
{
FlowEnvironment.RemoteNode(guid);
FlowEnvironment.RemoveNode(guid);
}
}
}
@@ -2371,9 +2371,9 @@ namespace Serein.WorkBench
{
logWindow?.Show();
await FlowEnvironment.StartAsync(); // 快
// await FlowEnvironment.StartAsync(); // 快
//await Task.Run(FlowEnvironment.StartAsync); // 上下文多次切换的场景中慢了1/10,定时器精度丢失
await Task.Run(FlowEnvironment.StartAsync); // 上下文多次切换的场景中慢了1/10,定时器精度丢失
//await Task.Factory.StartNew(FlowEnvironment.StartAsync); // 慢了1/5,定时器精度丢失
}

View File

@@ -54,19 +54,23 @@ namespace Serein.WorkBench.Themes
Key = key,
Instance = instance,
};
TextBlock textBlock = new TextBlock();
textBlock.Text = key;
textBlock.Tag = iOCObj;
textBlock.MouseDown += (s, e) =>
Application.Current.Dispatcher.Invoke(() =>
{
if(s is TextBlock block && block.Tag is IOCObj iocObj)
TextBlock textBlock = new TextBlock();
textBlock.Text = key;
textBlock.Tag = iOCObj;
textBlock.MouseDown += (s, e) =>
{
SelectObj?.Invoke(iocObj.Key, iocObj.Instance);
//FlowEnvironment.SetMonitorObjState(iocObj.Instance, true); // 通知环境该节点的数据更新后需要传到UI
}
};
DependenciesListBox.Items.Add(textBlock);
SortLisbox(DependenciesListBox);
if (s is TextBlock block && block.Tag is IOCObj iocObj)
{
SelectObj?.Invoke(iocObj.Key, iocObj.Instance);
//FlowEnvironment.SetMonitorObjState(iocObj.Instance, true); // 通知环境该节点的数据更新后需要传到UI
}
};
DependenciesListBox.Items.Add(textBlock);
SortLisbox(DependenciesListBox);
});
}
/// <summary>

View File

@@ -9,6 +9,9 @@ using System.Windows;
namespace Serein.WorkBench.Tool.Converters
{
/// <summary>
/// 根据bool类型控制可见性
/// </summary>
[ValueConversion(typeof(bool), typeof(Visibility))]
public class InvertableBooleanToVisibilityConverter : IValueConverter
{

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace Serein.WorkBench.Tool.Converters
{
/// <summary>
/// 画布拉动范围距离计算器
/// </summary>
public class RightThumbPositionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is double width)
return width - 10; // Adjust for Thumb width
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// 画布拉动范围距离计算器
/// </summary>
public class BottomThumbPositionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is double height)
return height - 10; // Adjust for Thumb height
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// 画布拉动范围距离计算器
/// </summary>
public class VerticalCenterThumbPositionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is double height)
return height / 2 - 5; // Centering Thumb vertically
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// 画布拉动范围距离计算器
/// </summary>
public class HorizontalCenterThumbPositionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is double width)
return width / 2 - 5; // Centering Thumb horizontally
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -5,6 +5,9 @@ using System.Windows.Media;
namespace Serein.WorkBench.Tool.Converters
{
/// <summary>
/// 根据控件类型切换颜色
/// </summary>
public class TypeToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)