From 4338554384b0be5c663d23891f618cf35664f10b Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Mon, 14 Oct 2024 17:29:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86logwindows=E8=BE=93?= =?UTF-8?q?=E5=87=BA=EF=BC=8C=E9=81=BF=E5=85=8D=E9=AB=98=E9=A2=91=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E6=97=B6=E5=8D=A1=E6=AD=BB=E3=80=82=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E4=BA=86=E6=B5=81=E7=A8=8B=E8=BF=90=E8=A1=8C=E4=B8=8A=E4=B8=8B?= =?UTF-8?q?=E6=96=87=EF=BC=8C=E4=BD=BF=E8=8A=82=E7=82=B9=E5=85=B7=E5=A4=87?= =?UTF-8?q?=E7=BB=88=E6=AD=A2=E5=88=86=E6=94=AF=E8=BF=90=E8=A1=8C=E7=9A=84?= =?UTF-8?q?=E8=83=BD=E5=8A=9B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/ConnectionInfoData.cs | 0 .../Serein.Extend.RemoteControl.csproj | 0 .../SereinFlowRemoteControl.cs | 143 ++-- .../Program.cs | 0 .../Serein.FlowStartTool.csproj | 0 Library.Core/NodeFlow/DynamicContext.cs | 87 ++- Library.Framework/NodeFlow/DynamicContext.cs | 57 +- Library/Api/IDynamicContext.cs | 30 +- Library/Api/IFlowEnvironment.cs | 32 +- Library/Entity/SereinProjectData.cs | 14 +- Library/Enums/RunState.cs | 27 + .../Handle/WebSocketMsgHandleHelper.cs | 14 +- Library/Utils/EmitHelper.cs | 6 + Net462DllTest/Web/PlcSocketService.cs | 5 +- NodeFlow/Base/NodeModelBaseFunc.cs | 62 +- NodeFlow/FlowEnvironment.cs | 33 +- NodeFlow/FlowStarter.cs | 52 +- NodeFlow/Model/CompositeConditionNode.cs | 2 +- NodeFlow/Model/SingleConditionNode.cs | 42 +- NodeFlow/Model/SingleExpOpNode.cs | 2 +- NodeFlow/SereinOutputFileData.cs | 189 ----- .../SereinExpression/SereinConditionParser.cs | 34 +- SereinFlow.sln | 24 +- WorkBench.ControlLibrary.Core/AssemblyInfo.cs | 10 + .../CustomControl1.cs | 50 ++ ...erein.WorkBench.ControlLibrary.Core.csproj | 10 + WorkBench.Remote/App.xaml | 9 + WorkBench.Remote/App.xaml.cs | 14 + WorkBench.Remote/AssemblyInfo.cs | 10 + WorkBench.Remote/MainWindow.xaml | 12 + WorkBench.Remote/MainWindow.xaml.cs | 24 + .../Node/NodeControlViewModelBase.cs | 127 ++++ .../Node/View/ActionNodeControl.xaml | 79 +++ .../Node/View/ActionNodeControl.xaml.cs | 19 + .../Node/View/ActionRegionControl.xaml | 21 + .../Node/View/ActionRegionControl.xaml.cs | 130 ++++ .../Node/View/ConditionNodeControl.xaml | 72 ++ .../Node/View/ConditionNodeControl.xaml.cs | 26 + .../Node/View/ConditionRegionControl.xaml | 19 + .../Node/View/ConditionRegionControl.xaml.cs | 95 +++ .../Node/View/DllControlControl.xaml | 40 ++ .../Node/View/DllControlControl.xaml.cs | 160 +++++ .../Node/View/ExpOpNodeControl.xaml | 19 + .../Node/View/ExpOpNodeControl.xaml.cs | 24 + .../Node/View/FlipflopNodeControl.xaml | 62 ++ .../Node/View/FlipflopNodeControl.xaml.cs | 17 + WorkBench.Remote/Node/View/NodeControlBase.cs | 61 ++ .../ViewModel/ActionNodeControlViewModel.cs | 15 + .../ConditionNodeControlViewModel.cs | 44 ++ .../ConditionRegionNodeControlViewModel.cs | 18 + .../Node/ViewModel/ExpOpNodeViewModel.cs | 26 + .../ViewModel/FlipflopNodeControlViewModel.cs | 14 + .../Node/ViewModel/TypeToStringConverter.cs | 27 + .../Serein.WorkBench.Remote.csproj | 53 ++ .../Condition/BoolConditionControl.xaml | 16 + .../Condition/BoolConditionControl.xaml.cs | 28 + .../Themes/Condition/IntConditionControl.xaml | 21 + .../Condition/IntConditionControl.xaml.cs | 28 + WorkBench.Remote/Themes/Condition/Model.cs | 88 +++ .../Condition/StringConditionControl.xaml | 18 + .../Condition/StringConditionControl.xaml.cs | 28 + WorkBench.Remote/Themes/ConditionControl.xaml | 35 + .../Themes/ConditionControl.xaml.cs | 85 +++ .../Themes/ConditionControlModel.cs | 99 +++ .../Themes/IOCObjectViewControl.xaml | 28 + .../Themes/IOCObjectViewControl.xaml.cs | 120 ++++ WorkBench.Remote/Themes/InputDialog.xaml | 16 + WorkBench.Remote/Themes/InputDialog.xaml.cs | 42 ++ .../Themes/MethodDetailsControl.xaml | 115 +++ .../Themes/MethodDetailsControl.xaml.cs | 64 ++ .../Themes/MultiConditionConverter.xaml | 4 + .../Themes/NodeTreeItemViewControl.xaml | 59 ++ .../Themes/NodeTreeItemViewControl.xaml.cs | 289 ++++++++ .../Themes/NodeTreeViewControl.xaml | 47 ++ .../Themes/NodeTreeViewControl.xaml.cs | 98 +++ .../Themes/ObjectViewerControl.xaml | 31 + .../Themes/ObjectViewerControl.xaml.cs | 671 ++++++++++++++++++ .../Themes/ObjectViewerControl1.xaml | 8 + .../Themes/ObjectViewerControl1.xaml.cs | 146 ++++ WorkBench.Remote/Themes/TypeViewerWindow.xaml | 16 + .../Themes/TypeViewerWindow.xaml.cs | 279 ++++++++ .../InvertableBooleanToVisibilityConverter.cs | 41 ++ .../Tool/Converters/ThumbPositionConverter.cs | 21 +- .../Tool/Converters/TypeToColorConverter.cs | 26 + WorkBench.Remote/Tool/LogTextWriter.cs | 85 +++ WorkBench/App.xaml.cs | 186 ++--- WorkBench/LogWindow.xaml.cs | 49 +- WorkBench/MainWindow.xaml | 9 +- WorkBench/MainWindow.xaml.cs | 12 +- WorkBench/Themes/IOCObjectViewControl.xaml.cs | 26 +- .../InvertableBooleanToVisibilityConverter.cs | 3 + .../Tool/Converters/ThumbPositionConverter.cs | 79 +++ .../Tool/Converters/TypeToColorConverter.cs | 3 + 93 files changed, 4640 insertions(+), 541 deletions(-) rename {Serein.FlowRemoteManagement => Extend.FlowRemoteManagement}/Model/ConnectionInfoData.cs (100%) rename Serein.FlowRemoteManagement/Serein.FlowRemoteManagement.csproj => Extend.FlowRemoteManagement/Serein.Extend.RemoteControl.csproj (100%) rename Serein.FlowRemoteManagement/FlowRemoteManagement.cs => Extend.FlowRemoteManagement/SereinFlowRemoteControl.cs (77%) rename {Serein.FlowStartTool => FlowStartTool}/Program.cs (100%) rename {Serein.FlowStartTool => FlowStartTool}/Serein.FlowStartTool.csproj (100%) create mode 100644 Library/Enums/RunState.cs delete mode 100644 NodeFlow/SereinOutputFileData.cs create mode 100644 WorkBench.ControlLibrary.Core/AssemblyInfo.cs create mode 100644 WorkBench.ControlLibrary.Core/CustomControl1.cs create mode 100644 WorkBench.ControlLibrary.Core/Serein.WorkBench.ControlLibrary.Core.csproj create mode 100644 WorkBench.Remote/App.xaml create mode 100644 WorkBench.Remote/App.xaml.cs create mode 100644 WorkBench.Remote/AssemblyInfo.cs create mode 100644 WorkBench.Remote/MainWindow.xaml create mode 100644 WorkBench.Remote/MainWindow.xaml.cs create mode 100644 WorkBench.Remote/Node/NodeControlViewModelBase.cs create mode 100644 WorkBench.Remote/Node/View/ActionNodeControl.xaml create mode 100644 WorkBench.Remote/Node/View/ActionNodeControl.xaml.cs create mode 100644 WorkBench.Remote/Node/View/ActionRegionControl.xaml create mode 100644 WorkBench.Remote/Node/View/ActionRegionControl.xaml.cs create mode 100644 WorkBench.Remote/Node/View/ConditionNodeControl.xaml create mode 100644 WorkBench.Remote/Node/View/ConditionNodeControl.xaml.cs create mode 100644 WorkBench.Remote/Node/View/ConditionRegionControl.xaml create mode 100644 WorkBench.Remote/Node/View/ConditionRegionControl.xaml.cs create mode 100644 WorkBench.Remote/Node/View/DllControlControl.xaml create mode 100644 WorkBench.Remote/Node/View/DllControlControl.xaml.cs create mode 100644 WorkBench.Remote/Node/View/ExpOpNodeControl.xaml create mode 100644 WorkBench.Remote/Node/View/ExpOpNodeControl.xaml.cs create mode 100644 WorkBench.Remote/Node/View/FlipflopNodeControl.xaml create mode 100644 WorkBench.Remote/Node/View/FlipflopNodeControl.xaml.cs create mode 100644 WorkBench.Remote/Node/View/NodeControlBase.cs create mode 100644 WorkBench.Remote/Node/ViewModel/ActionNodeControlViewModel.cs create mode 100644 WorkBench.Remote/Node/ViewModel/ConditionNodeControlViewModel.cs create mode 100644 WorkBench.Remote/Node/ViewModel/ConditionRegionNodeControlViewModel.cs create mode 100644 WorkBench.Remote/Node/ViewModel/ExpOpNodeViewModel.cs create mode 100644 WorkBench.Remote/Node/ViewModel/FlipflopNodeControlViewModel.cs create mode 100644 WorkBench.Remote/Node/ViewModel/TypeToStringConverter.cs create mode 100644 WorkBench.Remote/Serein.WorkBench.Remote.csproj create mode 100644 WorkBench.Remote/Themes/Condition/BoolConditionControl.xaml create mode 100644 WorkBench.Remote/Themes/Condition/BoolConditionControl.xaml.cs create mode 100644 WorkBench.Remote/Themes/Condition/IntConditionControl.xaml create mode 100644 WorkBench.Remote/Themes/Condition/IntConditionControl.xaml.cs create mode 100644 WorkBench.Remote/Themes/Condition/Model.cs create mode 100644 WorkBench.Remote/Themes/Condition/StringConditionControl.xaml create mode 100644 WorkBench.Remote/Themes/Condition/StringConditionControl.xaml.cs create mode 100644 WorkBench.Remote/Themes/ConditionControl.xaml create mode 100644 WorkBench.Remote/Themes/ConditionControl.xaml.cs create mode 100644 WorkBench.Remote/Themes/ConditionControlModel.cs create mode 100644 WorkBench.Remote/Themes/IOCObjectViewControl.xaml create mode 100644 WorkBench.Remote/Themes/IOCObjectViewControl.xaml.cs create mode 100644 WorkBench.Remote/Themes/InputDialog.xaml create mode 100644 WorkBench.Remote/Themes/InputDialog.xaml.cs create mode 100644 WorkBench.Remote/Themes/MethodDetailsControl.xaml create mode 100644 WorkBench.Remote/Themes/MethodDetailsControl.xaml.cs create mode 100644 WorkBench.Remote/Themes/MultiConditionConverter.xaml create mode 100644 WorkBench.Remote/Themes/NodeTreeItemViewControl.xaml create mode 100644 WorkBench.Remote/Themes/NodeTreeItemViewControl.xaml.cs create mode 100644 WorkBench.Remote/Themes/NodeTreeViewControl.xaml create mode 100644 WorkBench.Remote/Themes/NodeTreeViewControl.xaml.cs create mode 100644 WorkBench.Remote/Themes/ObjectViewerControl.xaml create mode 100644 WorkBench.Remote/Themes/ObjectViewerControl.xaml.cs create mode 100644 WorkBench.Remote/Themes/ObjectViewerControl1.xaml create mode 100644 WorkBench.Remote/Themes/ObjectViewerControl1.xaml.cs create mode 100644 WorkBench.Remote/Themes/TypeViewerWindow.xaml create mode 100644 WorkBench.Remote/Themes/TypeViewerWindow.xaml.cs create mode 100644 WorkBench.Remote/Tool/Converters/InvertableBooleanToVisibilityConverter.cs rename WorkBench/RightThumbPositionConverter.cs => WorkBench.Remote/Tool/Converters/ThumbPositionConverter.cs (81%) create mode 100644 WorkBench.Remote/Tool/Converters/TypeToColorConverter.cs create mode 100644 WorkBench.Remote/Tool/LogTextWriter.cs create mode 100644 WorkBench/Tool/Converters/ThumbPositionConverter.cs diff --git a/Serein.FlowRemoteManagement/Model/ConnectionInfoData.cs b/Extend.FlowRemoteManagement/Model/ConnectionInfoData.cs similarity index 100% rename from Serein.FlowRemoteManagement/Model/ConnectionInfoData.cs rename to Extend.FlowRemoteManagement/Model/ConnectionInfoData.cs diff --git a/Serein.FlowRemoteManagement/Serein.FlowRemoteManagement.csproj b/Extend.FlowRemoteManagement/Serein.Extend.RemoteControl.csproj similarity index 100% rename from Serein.FlowRemoteManagement/Serein.FlowRemoteManagement.csproj rename to Extend.FlowRemoteManagement/Serein.Extend.RemoteControl.csproj diff --git a/Serein.FlowRemoteManagement/FlowRemoteManagement.cs b/Extend.FlowRemoteManagement/SereinFlowRemoteControl.cs similarity index 77% rename from Serein.FlowRemoteManagement/FlowRemoteManagement.cs rename to Extend.FlowRemoteManagement/SereinFlowRemoteControl.cs index 0668f58..cde6264 100644 --- a/Serein.FlowRemoteManagement/FlowRemoteManagement.cs +++ b/Extend.FlowRemoteManagement/SereinFlowRemoteControl.cs @@ -18,27 +18,30 @@ namespace SereinFlowRemoteManagement /// - /// SereinFlow 远程管理模块 + /// SereinFlow 远程控制模块 /// [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 流程运行接口 + + /// + /// 连接到运行环境,获取当前的节点信息 + /// + /// + /// + [AutoSocketHandle] + public async Task ConnectWorkBench(Func Send) + { + await Send("尝试获取"); + + Dictionary> 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(); + 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(nodeType, out var connectionType)) + { + throw new InvalidOperationException("类型错误"); + } + + if (this.environment.TryGetMethodDetails(methodName,out var md)) + { + this.environment.CreateNode(connectionType, new Position(x, y), md); ; + } + + + } /// /// 远程更改两个节点的连接关系 @@ -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 } - /// - /// 连接到运行环境,获取当前的节点信息 - /// - /// - /// - [AutoSocketHandle] - public async Task ConnectWorkBench(Func Send) - { - await Send("尝试获取"); - - Dictionary> 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(); - 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 } } diff --git a/Serein.FlowStartTool/Program.cs b/FlowStartTool/Program.cs similarity index 100% rename from Serein.FlowStartTool/Program.cs rename to FlowStartTool/Program.cs diff --git a/Serein.FlowStartTool/Serein.FlowStartTool.csproj b/FlowStartTool/Serein.FlowStartTool.csproj similarity index 100% rename from Serein.FlowStartTool/Serein.FlowStartTool.csproj rename to FlowStartTool/Serein.FlowStartTool.csproj diff --git a/Library.Core/NodeFlow/DynamicContext.cs b/Library.Core/NodeFlow/DynamicContext.cs index b39bc16..e555791 100644 --- a/Library.Core/NodeFlow/DynamicContext.cs +++ b/Library.Core/NodeFlow/DynamicContext.cs @@ -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 /// public class DynamicContext: IDynamicContext { - public DynamicContext(/*ISereinIOC sereinIoc, */IFlowEnvironment flowEnvironment) + /// + /// 动态流程上下文 + /// + /// + public DynamicContext(IFlowEnvironment flowEnvironment) { - //SereinIoc = sereinIoc; Env = flowEnvironment; - + RunState = RunState.Running; } - // 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) - // { - // NodeRunCts = Env.IOC.Get(); - // } - // // 使用局部变量,避免捕获外部的 `action` - // Action localAction = action; + /// + /// 运行状态 + /// + 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(); - // } + /// + /// 每个上下文分别存放节点的当前数据 + /// + private readonly ConcurrentDictionary dictNodeFlowData = new ConcurrentDictionary(); + + /// + /// 获取节点当前数据 + /// + /// + /// + public object? GetFlowData(string nodeGuid) + { + if(dictNodeFlowData.TryGetValue(nodeGuid,out var data)) + { + return data; + } + { + return null; + } + } + + /// + /// 添加或更新当前节点数据 + /// + /// 节点Guid + /// 新的数据 + public void AddOrUpdate(string nodeGuid,object? flowData) + { + // this.dictNodeFlowData.TryGetValue(nodeGuid, out var oldFlowData); + this.dictNodeFlowData.AddOrUpdate(nodeGuid, _ => flowData, (_, _) => flowData); + } + + /// + /// 结束流程 + /// + public void EndCurrentBranch() + { + this.dictNodeFlowData?.Clear(); + RunState = RunState.Completion; + } - // // 清理引用,避免闭包导致的内存泄漏 - // localAction = null; - // }); - //} } - - } diff --git a/Library.Framework/NodeFlow/DynamicContext.cs b/Library.Framework/NodeFlow/DynamicContext.cs index 45eb638..2e3d0f4 100644 --- a/Library.Framework/NodeFlow/DynamicContext.cs +++ b/Library.Framework/NodeFlow/DynamicContext.cs @@ -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; + } + + + + /// + /// 运行环境 + /// + public IFlowEnvironment Env { get; } + + /// + /// 运行状态 + /// + public RunState RunState { get; set; } = RunState.NoStart; + /// + /// 每个上下文分别存放节点的当前数据 + /// + private readonly ConcurrentDictionary dictNodeFlowData = new ConcurrentDictionary(); + + /// + /// 获取节点当前数据 + /// + /// + /// + public object GetFlowData(string nodeGuid) + { + if (dictNodeFlowData.TryGetValue(nodeGuid, out var data)) + { + return data; + } + { + return null; + } + } + + /// + /// 添加或更新当前节点数据 + /// + /// 节点Guid + /// 新的数据 + public void AddOrUpdate(string nodeGuid, object flowData) + { + // this.dictNodeFlowData.TryGetValue(nodeGuid, out var oldFlowData); + this.dictNodeFlowData[nodeGuid] = flowData; + } + + /// + /// 结束流程 + /// + 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) diff --git a/Library/Api/IDynamicContext.cs b/Library/Api/IDynamicContext.cs index 31d5b74..baf0d74 100644 --- a/Library/Api/IDynamicContext.cs +++ b/Library/Api/IDynamicContext.cs @@ -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容器。 /// IFlowEnvironment Env { get; } - + + RunState RunState { get; } + /// + /// 获取节点的数据(当前节点需要获取上一节点数据时,需要从 运行时上一节点 的Guid 通过这个方法进行获取 + /// + /// + /// + object GetFlowData(string nodeGuid); + + /// + /// 添加或更新当前节点的数据 + /// + /// + /// + void AddOrUpdate(string nodeGuid, object flowData); + + /// + /// 用以提前结束分支运行 + /// + void EndCurrentBranch(); + + /*/// /// 定时循环触发 /// /// /// /// /// - // Task CreateTimingTask(Action callback, int time = 100, int count = -1); - } + // Task CreateTimingTask(Action callback, int time = 100, int count = -1);*/ + } } diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 9b4b5e0..3128408 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -379,7 +379,8 @@ namespace Serein.Library.Api { #region 属性 /// - /// IOC容器 + /// 单例模式IOC容器,内部维护了一个实例字典,默认使用类型的FullName作为Key,如果以“接口-实现类”的方式注册,那么将使用接口类型的FullName作为Key。 + /// 当某个类型注册绑定成功后,将不会因为其它地方尝试注册相同类型的行为导致类型被重新创建。 /// ISereinIOC IOC { get; } @@ -387,12 +388,26 @@ namespace Serein.Library.Api /// 环境名称 /// string EnvName { get; } + /// /// 是否全局中断 /// bool IsGlobalInterrupt { get; } - + /// + /// DLL中NodeAction特性的方法描述的所有原始副本 + /// + Dictionary> MethodDetailss { get; } + + + /// + /// 流程运行状态 + /// + RunState FlowState { get; set; } + /// + /// 全局触发器运行状态 + /// + RunState FlipFlopState { get; set; } #endregion @@ -545,15 +560,22 @@ namespace Serein.Library.Api /// 起始节点 /// 目标节点 /// 连接类型 - void RemoteConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType); + void RemoveConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType); /// /// 移除节点/区域/基础控件 /// /// 待移除的节点Guid - void RemoteNode(string nodeGuid); + void RemoveNode(string nodeGuid); - // 启动触发器 + /// + /// 激活未启动的全局触发器 + /// + /// void ActivateFlipflopNode(string nodeGuid); + /// + /// 终结一个全局触发器,在它触发后将不会再次监听消息(表现为已经启动的触发器至少会再次处理一次消息,后面版本再修正这个非预期行为) + /// + /// void TerminateFlipflopNode(string nodeGuid); diff --git a/Library/Entity/SereinProjectData.cs b/Library/Entity/SereinProjectData.cs index 64cff65..7c21793 100644 --- a/Library/Entity/SereinProjectData.cs +++ b/Library/Entity/SereinProjectData.cs @@ -178,7 +178,7 @@ namespace Serein.Library.Entity public Position Position { get; set; } /// - /// 是否选中 + /// 是否选中(暂时无效) /// public bool IsSelect { get; set; } } @@ -188,8 +188,17 @@ namespace Serein.Library.Entity /// public class Parameterdata { + /// + /// 参数类型,true时使用自定义的入参,false时由运行环境自动传参 + /// public bool State { get; set; } + /// + /// 自定义入参 + /// public string Value { get; set; } + /// + /// 表达式相关节点的表达式内容 + /// public string Expression { get; set; } } @@ -200,6 +209,9 @@ namespace Serein.Library.Entity /// public class Position { + /// + /// 构造一个坐标 + /// public Position(double x, double y) { this.X = x; this.Y = y; diff --git a/Library/Enums/RunState.cs b/Library/Enums/RunState.cs new file mode 100644 index 0000000..7d649a2 --- /dev/null +++ b/Library/Enums/RunState.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Enums +{ + /// + /// 流程运行状态 + /// + public enum RunState + { + /// + /// 初始化值,等待开始。只有初始化时才会存在该值,后续每次重新开始都是从 Completion 变成 Running) + /// + NoStart, + /// + /// 正在运行 + /// + Running, + /// + /// 运行完成 + /// + Completion, + } +} diff --git a/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs b/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs index 7130379..a0e06c0 100644 --- a/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs +++ b/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs @@ -34,6 +34,12 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle /// public event Action> OnExceptionTracking; + /// + /// 添加消息处理与异常处理 + /// + /// + /// + /// 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) + /// + /// 移除某个模块的WebSocket消息处理 + /// + /// + public void RemoveModule(ISocketHandleModule socketControlBase) { var type = socketControlBase.GetType(); var moduleAttribute = type.GetCustomAttribute(); @@ -66,7 +76,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle /// - /// 添加 + /// 添加消息处理以及异常处理 /// /// /// diff --git a/Library/Utils/EmitHelper.cs b/Library/Utils/EmitHelper.cs index 23e4782..200e3b4 100644 --- a/Library/Utils/EmitHelper.cs +++ b/Library/Utils/EmitHelper.cs @@ -50,6 +50,12 @@ namespace Serein.Library.Utils } } + /// + /// 根据方法信息创建动态调用的委托,返回方法类型,以及传出一个委托 + /// + /// + /// + /// public static EmitMethodType CreateDynamicMethod( MethodInfo methodInfo,out Delegate @delegate) { bool IsTask = IsGenericTask(methodInfo.ReturnType, out var taskGenericsType); diff --git a/Net462DllTest/Web/PlcSocketService.cs b/Net462DllTest/Web/PlcSocketService.cs index 5821118..cfeaeab 100644 --- a/Net462DllTest/Web/PlcSocketService.cs +++ b/Net462DllTest/Web/PlcSocketService.cs @@ -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((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 SendMsg, Func SendObj) { diff --git a/NodeFlow/Base/NodeModelBaseFunc.cs b/NodeFlow/Base/NodeModelBaseFunc.cs index b2ff125..3c84bd3 100644 --- a/NodeFlow/Base/NodeModelBaseFunc.cs +++ b/NodeFlow/Base/NodeModelBaseFunc.cs @@ -94,6 +94,36 @@ namespace Serein.NodeFlow.Base #region 节点方法的执行 + /// + /// 是否应该退出执行 + /// + /// + /// + /// + 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; + } + + + /// /// 开始执行 /// @@ -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 /// /// 更新节点数据,并检查监视表达式是否生效 /// - /// + /// 上下文 + /// 节点Moel + /// 新的数据 + /// 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); // 上下文中更新数据 } } diff --git a/NodeFlow/FlowEnvironment.cs b/NodeFlow/FlowEnvironment.cs index c92bd8b..f105b67 100644 --- a/NodeFlow/FlowEnvironment.cs +++ b/NodeFlow/FlowEnvironment.cs @@ -150,6 +150,14 @@ namespace Serein.NodeFlow #endregion #region 属性 + /// + /// 如果没有全局触发器,且没有循环分支,流程执行完成后自动为 Completion 。 + /// + public RunState FlowState { get; set; } = RunState.NoStart; + /// + /// 如果全局触发器还在运行,则为 Running 。 + /// + public RunState FlipFlopState { get; set; } = RunState.NoStart; /// /// 环境名称 @@ -166,8 +174,18 @@ namespace Serein.NodeFlow /// public ChannelFlowInterrupt ChannelFlowInterrupt { get; set; } + /// + /// 单例模式IOC容器,内部维护了一个实例字典,默认使用类型的FullName作为Key,如果以“接口-实现类”的方式注册,那么将使用接口类型的FullName作为Key。 + /// 当某个类型注册绑定成功后,将不会因为其它地方尝试注册相同类型的行为导致类型被重新创建。 + /// public ISereinIOC IOC { get => this; } + + /// + /// 描述所有DLL中NodeAction特性的方法的原始副本 + /// + public Dictionary> MethodDetailss { get; } = []; + #endregion #region 私有变量 @@ -185,10 +203,7 @@ namespace Serein.NodeFlow /// public List NodeLibrarys { get; } = []; - /// - /// 描述所有DLL中NodeAction特性的方法的原始副本 - /// - public Dictionary> MethodDetailss { get; } = []; + /// /// 环境加载的节点集合 @@ -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 /// /// /// - 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 /// 目标节点Guid /// 连接关系 /// - 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);// 被父节点移除连接关系的子节点若为触发器,且无上级节点,则当前流程正在运行,则加载到运行环境中 diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index e3b844c..70ed71c 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -17,48 +17,28 @@ using static Serein.Library.Utils.ChannelFlowInterrupt; namespace Serein.NodeFlow { + + /// /// 流程启动器 /// - /// - /// public class FlowStarter { /// /// 全局触发器CTS /// public const string FlipFlopCtsName = "<>.FlowFlipFlopCts"; + /// + /// 流程运行CTS + /// + public const string FlowRungCtsName = "<>.FlowRungCtsName"; public FlowStarter() { } - /// - /// 流程运行状态 - /// - public enum RunState - { - /// - /// 等待开始 - /// - NoStart, - /// - /// 正在运行 - /// - Running, - /// - /// 运行完成 - /// - Completion, - } - /// - /// 起点流程运行状态 - /// - public RunState FlowState { get; private set; } = RunState.NoStart; - /// - /// 全局触发器运行状态 - /// - public RunState FlipFlopState { get; private set; } = RunState.NoStart; + + /// /// 控制触发器 @@ -115,11 +95,11 @@ namespace Serein.NodeFlow List loadingMethods, List 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 } diff --git a/NodeFlow/Model/CompositeConditionNode.cs b/NodeFlow/Model/CompositeConditionNode.cs index e14627e..506b46a 100644 --- a/NodeFlow/Model/CompositeConditionNode.cs +++ b/NodeFlow/Model/CompositeConditionNode.cs @@ -37,7 +37,7 @@ namespace Serein.NodeFlow.Model break; } } - return Task.FromResult( PreviousNode?.GetFlowData()); + return Task.FromResult(PreviousNode?.GetFlowData()); // 条件区域透传上一节点的数据 } diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs index 1c7b705..264c0f0 100644 --- a/NodeFlow/Model/SingleConditionNode.cs +++ b/NodeFlow/Model/SingleConditionNode.cs @@ -32,35 +32,33 @@ namespace Serein.NodeFlow.Model public override Task 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) diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs index 9c3083e..2fd2f38 100644 --- a/NodeFlow/Model/SingleExpOpNode.cs +++ b/NodeFlow/Model/SingleExpOpNode.cs @@ -21,7 +21,7 @@ namespace Serein.NodeFlow.Model //public override async Task Executing(IDynamicContext context) public override Task ExecutingAsync(IDynamicContext context) { - var data = PreviousNode?.GetFlowData(); + var data = PreviousNode?.GetFlowData(); // 表达式节点使用上一节点数据 try { diff --git a/NodeFlow/SereinOutputFileData.cs b/NodeFlow/SereinOutputFileData.cs deleted file mode 100644 index e714650..0000000 --- a/NodeFlow/SereinOutputFileData.cs +++ /dev/null @@ -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 -{ - /* /// - /// 输出文件 - /// - public class SereinOutputFileData - { - /// - /// 基础 - /// - - public Basic basic { get; set; } - - /// - /// 依赖的DLL - /// - - public Library[] library { get; set; } - - /// - /// 起始节点GUID - /// - - public string startNode { get; set; } - - /// - /// 节点信息集合 - /// - - public NodeInfo[] nodes { get; set; } - - /// - /// 区域集合 - /// - - public Region[] regions { get; set; } - - } - - /// - /// 基础 - /// - public class Basic - { - /// - /// 画布 - /// - - public FlowCanvas canvas { get; set; } - - /// - /// 版本 - /// - - public string versions { get; set; } - - // 预览位置 - - // 缩放比例 - } - /// - /// 画布 - /// - public class FlowCanvas - { - /// - /// 宽度 - /// - public float width { get; set; } - /// - /// 高度 - /// - public float lenght { get; set; } - } - - /// - /// DLL - /// - public class Library - { - /// - /// DLL名称 - /// - - public string name { get; set; } - - /// - /// 路径 - /// - - public string path { get; set; } - - /// - /// 提示 - /// - - public string tips { get; set; } - - } - /// - /// 节点 - /// - public class NodeInfo - { - /// - /// GUID - /// - - public string guid { get; set; } - - /// - /// 名称 - /// - - public string name { get; set; } - - /// - /// 显示标签 - /// - - public string label { get; set; } - - /// - /// 类型 - /// - - public string type { get; set; } - - /// - /// 于画布中的位置 - /// - - public Position position { get; set; } - - /// - /// 真分支节点GUID - /// - - public string[] trueNodes { get; set; } - - /// - /// 假分支节点 - /// - - 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; } - - } - - - /// - /// 节点于画布中的位置 - /// - public class Position - { - public float x { get; set; } - public float y { get; set; } - } - - - /// - /// 区域 - /// - public class Region - { - public string guid { get; set; } - public NodeInfo[] childNodes { get; set; } - - }*/ -} diff --git a/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs b/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs index 178902f..bf6fe0a 100644 --- a/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs +++ b/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs @@ -530,13 +530,9 @@ namespace Serein.NodeFlow.Tool.SereinExpression { ">" => ValueTypeConditionResolver.Operator.GreaterThan, "<" => ValueTypeConditionResolver.Operator.LessThan, - "=" => ValueTypeConditionResolver.Operator.Equal, "==" => ValueTypeConditionResolver.Operator.Equal, - ">=" => ValueTypeConditionResolver.Operator.GreaterThanOrEqual, - "≥" => ValueTypeConditionResolver.Operator.GreaterThanOrEqual, - "<=" => ValueTypeConditionResolver.Operator.LessThanOrEqual, - "≤" => ValueTypeConditionResolver.Operator.LessThanOrEqual, - "equals" => ValueTypeConditionResolver.Operator.Equal, + ">=" or "≥" => ValueTypeConditionResolver.Operator.GreaterThanOrEqual, + "<=" or "≤" => ValueTypeConditionResolver.Operator.LessThanOrEqual, "in" => ValueTypeConditionResolver.Operator.InRange, "!in" => ValueTypeConditionResolver.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 /// 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.") }; } diff --git a/SereinFlow.sln b/SereinFlow.sln index 4c07d4a..55ea542 100644 --- a/SereinFlow.sln +++ b/SereinFlow.sln @@ -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 diff --git a/WorkBench.ControlLibrary.Core/AssemblyInfo.cs b/WorkBench.ControlLibrary.Core/AssemblyInfo.cs new file mode 100644 index 0000000..b0ec827 --- /dev/null +++ b/WorkBench.ControlLibrary.Core/AssemblyInfo.cs @@ -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) +)] diff --git a/WorkBench.ControlLibrary.Core/CustomControl1.cs b/WorkBench.ControlLibrary.Core/CustomControl1.cs new file mode 100644 index 0000000..be2ef34 --- /dev/null +++ b/WorkBench.ControlLibrary.Core/CustomControl1.cs @@ -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 +{ + /// + /// 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. + /// + /// + /// + /// + public class CustomControl1 : Control + { + static CustomControl1() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1))); + } + } +} \ No newline at end of file diff --git a/WorkBench.ControlLibrary.Core/Serein.WorkBench.ControlLibrary.Core.csproj b/WorkBench.ControlLibrary.Core/Serein.WorkBench.ControlLibrary.Core.csproj new file mode 100644 index 0000000..6c78894 --- /dev/null +++ b/WorkBench.ControlLibrary.Core/Serein.WorkBench.ControlLibrary.Core.csproj @@ -0,0 +1,10 @@ + + + + net8.0-windows + enable + true + enable + + + diff --git a/WorkBench.Remote/App.xaml b/WorkBench.Remote/App.xaml new file mode 100644 index 0000000..8a1c6e9 --- /dev/null +++ b/WorkBench.Remote/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/WorkBench.Remote/App.xaml.cs b/WorkBench.Remote/App.xaml.cs new file mode 100644 index 0000000..0f6171d --- /dev/null +++ b/WorkBench.Remote/App.xaml.cs @@ -0,0 +1,14 @@ +using System.Configuration; +using System.Data; +using System.Windows; + +namespace Serein.RemoteWorkBench +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } + +} diff --git a/WorkBench.Remote/AssemblyInfo.cs b/WorkBench.Remote/AssemblyInfo.cs new file mode 100644 index 0000000..b0ec827 --- /dev/null +++ b/WorkBench.Remote/AssemblyInfo.cs @@ -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) +)] diff --git a/WorkBench.Remote/MainWindow.xaml b/WorkBench.Remote/MainWindow.xaml new file mode 100644 index 0000000..d3961c5 --- /dev/null +++ b/WorkBench.Remote/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/WorkBench.Remote/MainWindow.xaml.cs b/WorkBench.Remote/MainWindow.xaml.cs new file mode 100644 index 0000000..1df662e --- /dev/null +++ b/WorkBench.Remote/MainWindow.xaml.cs @@ -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 +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/WorkBench.Remote/Node/NodeControlViewModelBase.cs b/WorkBench.Remote/Node/NodeControlViewModelBase.cs new file mode 100644 index 0000000..91273af --- /dev/null +++ b/WorkBench.Remote/Node/NodeControlViewModelBase.cs @@ -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; + } + + /// + /// 对应的节点实体类 + /// + internal NodeModelBase Node { get; } + + + private bool isSelect; + /// + /// 表示节点控件是否被选中 + /// + 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; + } + + + } +} diff --git a/WorkBench.Remote/Node/View/ActionNodeControl.xaml b/WorkBench.Remote/Node/View/ActionNodeControl.xaml new file mode 100644 index 0000000..4faf4b3 --- /dev/null +++ b/WorkBench.Remote/Node/View/ActionNodeControl.xaml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WorkBench.Remote/Node/View/ActionNodeControl.xaml.cs b/WorkBench.Remote/Node/View/ActionNodeControl.xaml.cs new file mode 100644 index 0000000..f8852ec --- /dev/null +++ b/WorkBench.Remote/Node/View/ActionNodeControl.xaml.cs @@ -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 +{ + /// + /// ActionNode.xaml 的交互逻辑 + /// + public partial class ActionNodeControl : NodeControlBase + { + public ActionNodeControl(ActionNodeControlViewModel viewModel):base(viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} diff --git a/WorkBench.Remote/Node/View/ActionRegionControl.xaml b/WorkBench.Remote/Node/View/ActionRegionControl.xaml new file mode 100644 index 0000000..236d16d --- /dev/null +++ b/WorkBench.Remote/Node/View/ActionRegionControl.xaml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/WorkBench.Remote/Node/View/ActionRegionControl.xaml.cs b/WorkBench.Remote/Node/View/ActionRegionControl.xaml.cs new file mode 100644 index 0000000..60889d0 --- /dev/null +++ b/WorkBench.Remote/Node/View/ActionRegionControl.xaml.cs @@ -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 +{ + /// + /// ActionRegion.xaml 的交互逻辑 + /// + 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); + } + } + } + + + } +} diff --git a/WorkBench.Remote/Node/View/ConditionNodeControl.xaml b/WorkBench.Remote/Node/View/ConditionNodeControl.xaml new file mode 100644 index 0000000..92adc6d --- /dev/null +++ b/WorkBench.Remote/Node/View/ConditionNodeControl.xaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WorkBench.Remote/Node/View/ConditionNodeControl.xaml.cs b/WorkBench.Remote/Node/View/ConditionNodeControl.xaml.cs new file mode 100644 index 0000000..7b32096 --- /dev/null +++ b/WorkBench.Remote/Node/View/ConditionNodeControl.xaml.cs @@ -0,0 +1,26 @@ +using Serein.NodeFlow.Model; +using Serein.WorkBench.Node.ViewModel; + +namespace Serein.WorkBench.Node.View +{ + /// + /// ConditionNode.xaml 的交互逻辑 + /// + 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(); + } + + } +} diff --git a/WorkBench.Remote/Node/View/ConditionRegionControl.xaml b/WorkBench.Remote/Node/View/ConditionRegionControl.xaml new file mode 100644 index 0000000..b303a69 --- /dev/null +++ b/WorkBench.Remote/Node/View/ConditionRegionControl.xaml @@ -0,0 +1,19 @@ + + + + + + + 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(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(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(); - } - } - /// - /// 成功加载的工程文件 - /// - 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(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(content); - App.FileDataPath =System.IO.Path.GetDirectoryName(filePath)!; // filePath;// - } -#endif - - - } } diff --git a/WorkBench/LogWindow.xaml.cs b/WorkBench/LogWindow.xaml.cs index 7e12f7a..d6ff400 100644 --- a/WorkBench/LogWindow.xaml.cs +++ b/WorkBench/LogWindow.xaml.cs @@ -6,18 +6,24 @@ namespace Serein.WorkBench /// DebugWindow.xaml 的交互逻辑 /// using System; + using System.IO; using System.Text; using System.Threading.Tasks; using System.Timers; using System.Windows; + /// + /// LogWindow.xaml 的交互逻辑 + /// 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); } /// @@ -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 /// private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { - //logUpdateTimer?.Stop(); - //logUpdateTimer?.Close(); - //logUpdateTimer?.Dispose(); logBuffer?.Clear(); Clear(); e.Cancel = true; // 取消关闭操作 diff --git a/WorkBench/MainWindow.xaml b/WorkBench/MainWindow.xaml index 331533c..d7e1713 100644 --- a/WorkBench/MainWindow.xaml +++ b/WorkBench/MainWindow.xaml @@ -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"> - - - - + + + + diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index 0b76323..cab86a7 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -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); } /// @@ -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,定时器精度丢失 } diff --git a/WorkBench/Themes/IOCObjectViewControl.xaml.cs b/WorkBench/Themes/IOCObjectViewControl.xaml.cs index cce3074..fe85eb6 100644 --- a/WorkBench/Themes/IOCObjectViewControl.xaml.cs +++ b/WorkBench/Themes/IOCObjectViewControl.xaml.cs @@ -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); + }); + } /// diff --git a/WorkBench/Tool/Converters/InvertableBooleanToVisibilityConverter.cs b/WorkBench/Tool/Converters/InvertableBooleanToVisibilityConverter.cs index 3f85c37..4ff145e 100644 --- a/WorkBench/Tool/Converters/InvertableBooleanToVisibilityConverter.cs +++ b/WorkBench/Tool/Converters/InvertableBooleanToVisibilityConverter.cs @@ -9,6 +9,9 @@ using System.Windows; namespace Serein.WorkBench.Tool.Converters { + /// + /// 根据bool类型控制可见性 + /// [ValueConversion(typeof(bool), typeof(Visibility))] public class InvertableBooleanToVisibilityConverter : IValueConverter { diff --git a/WorkBench/Tool/Converters/ThumbPositionConverter.cs b/WorkBench/Tool/Converters/ThumbPositionConverter.cs new file mode 100644 index 0000000..e4f405c --- /dev/null +++ b/WorkBench/Tool/Converters/ThumbPositionConverter.cs @@ -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 +{ + /// + /// 画布拉动范围距离计算器 + /// + 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(); + } + } + /// + /// 画布拉动范围距离计算器 + /// + 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(); + } + } + /// + /// 画布拉动范围距离计算器 + /// + 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(); + } + } + /// + /// 画布拉动范围距离计算器 + /// + 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(); + } + } + +} diff --git a/WorkBench/Tool/Converters/TypeToColorConverter.cs b/WorkBench/Tool/Converters/TypeToColorConverter.cs index f9d904b..b4bc639 100644 --- a/WorkBench/Tool/Converters/TypeToColorConverter.cs +++ b/WorkBench/Tool/Converters/TypeToColorConverter.cs @@ -5,6 +5,9 @@ using System.Windows.Media; namespace Serein.WorkBench.Tool.Converters { + /// + /// 根据控件类型切换颜色 + /// public class TypeToColorConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture)