diff --git a/README.md b/README.md index 5834afa..04126de 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ # 自述 -基于Dotnet 8 的流程可视化编辑器,需二次开发。 +基于Dotnet 8 的流程可视化框架,运行以来、运行环境、编辑环境全部开源,可二次开发。 不定期在Bilibili个人空间上更新相关的视频。 https://space.bilibili.com/33526379 -# 计划任务 2024年10月28日更新 -* 重新完善远程管理与远程客户端的功能(目前仅支持远程修改节点属性、添加/移除节点、启动流程、停止流程) -* 重新完善节点树视图、IOC容器对象视图(目前残废版) -* 计划实现单步执行(暂未想到如何在不影响异步流程的前提下停止流程) -* 考虑模仿AST的方式,将流程图以“Node token → C# Code”的方式进行原生代码支持 +# 计划任务 2025年6月1日更新 +* 重新实现远程控制逻辑(添加对多画布多客户端在线支持) + # 如何加载我的DLL? 为你的工程添加**Serein.Library**项目引用(也可在Negut上下载),使用 **DynamicFlow** 特性标记你的类,可以参照 **Net461DllTest** 的实现(该示例工程的设计并不完善,并未做依赖分离,仅做参考)。编译为 Dll文件 后,拖入到软件中即可。 如果你不想下载整个工程文件,“FLowEdit”目录下放有“FlowEdit可视化流程编辑器.zip”压缩包,可以直接解压使用(但可能需要你安装 .Net8 运行环境)。 @@ -17,7 +15,7 @@ https://space.bilibili.com/33526379 * 动作节点 - Action * 触发器节点 - Flipflop * UI节点 - UI -# 关于 IDynamicContext 说明(重要)** +# 关于 IDynamicContext 说明(重要) * 基本说明:IDynamicContext 是节点之间传递数据的接口、载体,其实例由 FlowEnvironment 运行环境自动实现,内部提供全局单例的环境接口,用以注册、获取实例(单例模式),一般情况下,你无须关注 FlowEnvironment 对外暴露的属性方法。 * 重要概念: * 每个节点其实对应类库中的某一个方法,这些方法组合起来,加上预先设置的逻辑分支,就是一个完整的节点流。在这个节点流当中,从第一个节点开始、直到所有可达的节点,都会通过同一个流程上下文传递、共享数据。为了符合多线程操作的理念,每个运行起来的节点流之间,流程数据并不互通,从根本隔绝了“脏数据”的产生。 @@ -50,10 +48,10 @@ https://space.bilibili.com/33526379 * 入参:根据目标节点变化。 * 描述:有时我们需要将流程图的逻辑解耦,单独使用某个画布编写逻辑,抽取出一个通用的处理模板,在其它流程图中像接口一样进行调用。 * 使用方式: - 0. 创建两个画布,分别命名画布A,画布B。 - 1. 在画布A上,选择希望暴露出去的一个节点,勾选“全局公开”。 - 2. 在画布B上,拖拽创建“流程接口”节点,画布选择“画布A”,节点选择公开的节点。 - 3. 在画布B上正常调用即可。 + 1. 创建两个画布,分别命名画布A,画布B。 + 2. 在画布A上,选择希望暴露出去的一个节点,勾选“全局公开”。 + 3. 在画布B上,拖拽创建“流程接口”节点,画布选择“画布A”,节点选择公开的节点。 + 4. 在画布B上正常调用即可。 * 关于“参数共享”设置的说明: * 如果启用了,那么流程接口对应的节点,入参参数将在全局保持一致,一处地方修改后,处处同步(仅限于启用了这一设置的相同的流程接口节点)。 * 如果没有启用,那么流程接口将会从目标节点拷贝相同的入参,单独使用,并不受目标节点的入参参数修改而修改。 @@ -146,24 +144,50 @@ https://space.bilibili.com/33526379 * **ExpCondition - 条件表达式节点** * 入参: 自定义。 * 描述:与表达式节点不同,条件表达式节点是判断条件是否成立,如果成立,返回true,否则返回false,如果表达式执行失败,而进入 error 分支。 - * 增加描述:如果入参数据为某个对象,需要得到其属性/字段,可以在表达式输入框使用“ .[property]/[field]< type> [op] value ”的方式判断条件,注意,必须使用“.”符号,这有助于显然的表达需要从入参对象中取内部某个值,另外,也可以在入参数据编辑框,使用“@Get .[property]/[field]”的方式重新定义入参数据。 + * 使用方式: + * 入参说明:默认从上一节点获取,也可以显式设定值,也可以参考表达式节点,使用“@Get .[property]/[field]”的方式重新定义入参数据,用以条件表达式判断。 + * 条件表达式:默认“PASS”,代表跳过判断直接进入下一个分支。 + * 条件表达式格式“.[property]/[field]< type> [op] value”,注意,开头必须使用“.”符号,这有助于显然的表达需要从入参对象中取内部某个值。 * 表达式符号说明: - * [property] /[field] : 属性/字段 - * [op] : 操作符 - 1. bool表达式:== - 2. 数值表达式 :==、>=、 <=、in a-b (表示判断是否在a至b的数值范围内), !in a-b(取反); - 3. 文本表达式:==/equals(等于)、!=/notequals(不等于)、c/contains(出现过)、nc/doesnotcontain(没有出现过)、sw/startswith(开头等于)、ew/endswith(结尾等于) - * [value] : 条件值 + * [property] /[field] : 属性/字段 + * [op] : 操作符 + 1. bool表达式:== + 2. 数值表达式 :==、>=、 <=、in a-b (表示判断是否在a至b的数值范围内), !in a-b(取反); + 3. 文本表达式:==/equals(等于)、!=/notequals(不等于)、c/contains(出现过)、nc/doesnotcontain(没有出现过)、sw/startswith(开头等于)、ew/endswith(结尾等于) + * < type> :指定需要转换为某个类型,可不选。 + * [value] : 条件值 + * 使用示例: +~~~ +场景1:上一个节点传入了该对象(伪代码): +class Data +{ + string Name; // 性能 + string Age; // 年龄,外部传入了文本类型 + string IdentityCardNumber; // 身份证号 + +} +需求:需要判断年龄是否在某个区间,例如需要大于18岁,小于35岁。 +条件表达式:.Age in 18-35 + + + +需求:需要判断是否是北京身份证(开头为”1100”)。 +条件表达式:.IdentityCardNumber sw 1100 +另一种方法: + 入参使用表达式:@Get .IdentityCardNumber + 条件表达式:sw 1100 + +~~~ ## 3. 从DLL生成控件的枚举值: * **Action - 动作** - * 入参:自定义。如果入参类型为IDynamicContext,会传入当前的上下文;如果入参类型为NodeBase,会传入节点对应的Model。如果不显式指定参数来源,参数会尝试获取运行时上一节点返回值,并根据当前入参类型尝试进行类型转换。 + * 入参:自定义。如果入参类型为IDynamicContext,会传入当前的上下文;如果入参类型为IFlowNode,会传入节点对应的实体Model。如果不显式指定参数来源,参数会尝试获取运行时上一节点返回值,并根据当前入参类型尝试进行类型转换。 * 返回值:自定义,支持异步等待。 * 描述:同步执行对应的方法。 * **Flipflop - 触发器** * 全局触发器 * 入参:依照Action节点。 * 返回值:Task`>` - * 描述:运行开始时,所有无上级节点的触发器节点(在当前分支中作为起始节点),分别建立新的线程运行,然后异步等待触发(如果有)。这种触发器拥有独自的DynamicContext上下文(共用同一个Ioc),执行完成之后,会重新从分支起点的触发器开始等待。 + * 描述:运行开始时,所有无上级节点的触发器节点(在当前分支中作为起始节点),分别建立新的线程运行,然后异步等待触发(如果有)。这种触发器拥有独自的IDynamicContext上下文(共用同一个Ioc),执行完成之后,会重新从分支起点的触发器开始等待。 * 分支中的触发器 * 入参:依照Action节点。 * 返回值:Task`>` diff --git a/Workbench/Views/FlowCanvasView.xaml.cs b/Workbench/Views/FlowCanvasView.xaml.cs index a070243..bfd61f2 100644 --- a/Workbench/Views/FlowCanvasView.xaml.cs +++ b/Workbench/Views/FlowCanvasView.xaml.cs @@ -1204,7 +1204,7 @@ namespace Serein.Workbench.Views var nodeConotrol = selectNodeControls[0]; // 选取了控件 flowNodeService.CurrentSelectNodeControl = nodeConotrol; // 更新选取节点显示 - App.GetService().CurrentMethodDetailsInfo = nodeConotrol.ViewModel.NodeModel.MethodDetails.ToInfo(); + // App.GetService().CurrentMethodDetailsInfo = nodeConotrol.ViewModel.NodeModel.MethodDetails.ToInfo(); // ChangeViewerObjOfNode(selectNodeControls[0]); } diff --git a/Workbench/Views/FlowWorkbenchView.xaml.cs b/Workbench/Views/FlowWorkbenchView.xaml.cs index c6b6125..fd4af08 100644 --- a/Workbench/Views/FlowWorkbenchView.xaml.cs +++ b/Workbench/Views/FlowWorkbenchView.xaml.cs @@ -48,5 +48,22 @@ namespace Serein.Workbench.Views LogWindow.Instance.Close(); System.Windows.Application.Current.Shutdown(); } + + protected override void OnPreviewMouseDown(MouseButtonEventArgs e) + { + // 获取当前的焦点控件 + var element = FocusManager.GetFocusedElement(this); + + // 如果当前有焦点控件,且点击的区域不在该控件上,则清除焦点 + if (element != null && !element.IsMouseOver) + { + // 将焦点设置到窗口本身或其他透明控件 + FocusManager.SetFocusedElement(this, this); + } + + // 继续处理默认的鼠标按下事件 + base.OnPreviewMouseDown(e); + } + } }