# NodeStudio **Repository Path**: cai-jingchuang/node-studio ## Basic Information - **Project Name**: NodeStudio - **Description**: WPF 节点图编辑器框架 —— 用节点可视化你的逻辑 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2026-07-31 - **Last Updated**: 2026-09-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # NodeStudio [![.NET 8](https://img.shields.io/badge/.NET-8.0-512BD4?logo=dotnet)](https://dotnet.microsoft.com/) [![Platform](https://img.shields.io/badge/Platform-Windows-0078D6?logo=windows)](https://www.microsoft.com/windows) [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE.txt) **WPF 节点图编辑器框架 —— 用节点可视化你的逻辑** NodeStudio 帮助你在 .NET 桌面应用中快速构建节点式交互体验:拖一拖节点、连一连线、点一下运行,一个可视化流程就活了。无论是数据流水线、逻辑编排,还是面向最终用户的"低代码"工具,NodeStudio 都为你准备好了画布、引擎与编辑器。 ## NodeStudio 能做的 - 🧩 **所见即所得的编辑**:拖拽创建节点、拉线连接、框选、复制粘贴、重命名删除,一切直觉操作 - 🔀 **真并行执行**:内置 DAG 执行引擎,自动追踪依赖、就绪节点并行运行、失败节点不影响下游 - 📦 **子图与模块化**:把一组节点打包成"节点块",双击即可进入内部继续编辑——复杂流程也能拆得清清楚楚 - ▶️ **一键运行,放心操作**:全局运行/暂停/连续运行,运行中画布自动进入只读保护(仍可选中与查看),不怕误操作;右键任意节点「运行」可单节点执行——不重置输入(已连接端口取上游上次输出值,未连接端口取手动值),调试单个节点不用跑整图 - 🎛️ **选中即编辑**:单击节点,右侧属性面板立即呈现可调参数,改完即生效;选中节点时相关连线自动高亮 - 📋 **多选复制粘贴**:框选/多选后 Ctrl+C 复制、Ctrl+V 或右键粘贴——单个粘贴到鼠标位置,多选按原位置整体平移并恢复相互连线;Delete 删除选中(一次确认) - 💾 **随存随取**:一键保存/加载完整图——节点、连线、参数、子图结构全部保留;格式带版本号,旧文件兼容、过新文件友好提示 - 🧩 **自定义类型与属性持久化**:注册自定义端口类型;节点复杂属性(含第三方不可序列化类型)分层序列化,属性多态(基类声明、泛型列表混装派生类)自动按 `$type` 还原为派生实例,`[NotSerialized]` 标记不保存 - 🌙 **一致的深色风格**:编辑器、弹窗、菜单、面板统一深色主题,专注不刺眼 ## 快速开始 ```bash # 构建 dotnet build NodeStudio.sln # 运行演示(图形界面) dotnet run --project TestWpf # 运行功能测试 dotnet run --project TestConsole ``` 在 TestWpf 演示中:从左侧工具箱拖入节点 → 连线 → 点 ▶ 运行 → 从"文件"菜单导出/导入你的图。 ## 内置节点 引擎自带六个开箱即用的流程/运算/同步节点,无需任何代码即可搭建条件分支、数值计算、时序控制与跨块同步流程。 **object 通配连接规则**:内置节点(延时、数据入队/出队等)使用 object 通配端口实现任意类型透传。为杜绝"运行后才发现类型不对",object 通配输出连接到具体类型输入时有以下约束: - 连接时若输出端口**当前值无法转换**到目标输入类型(如 string "abc" → int),**拒绝连接**并给出原因; - 输出值为 null 时允许连接("先搭图后运行"的工作流),但运行后上游仍传入 null 或不匹配的值时,**下游节点运行失败并给出明确报错**(内置节点已内置该检查;自定义节点可用 `GetValueStrict()` 严格读取,或用 `GetValue()` 容错读取后自行判断与报错); - 数值语义兼容的值自动转换(int 经 object 输出读入 double/int 端口正常工作)。 ### 节点块 把一组节点打包成可复用模块——外部看是一个节点,双击即可进入内部编辑器继续编辑,复杂流程拆成清晰的层次结构。 **结构与块端口** 块内部是一张独立的子图;块与外部交换数据靠「块输入 / 块输出」两个特殊节点(进入块内部编辑器后可见): ``` 外部图: [上游] ──→ (块.数值A) [下游] ←── (块.结果) │ ↑ 内部子图: [块输入]→ 数值A 块输出 ←[内部计算]──┘ └────→ [内部节点] ────┘ ``` - **添加端口**:在「块输入 / 块输出」节点上右键「新增输入 / 新增输出」即可给块加端口(**添加时选定类型**,强类型避免 object 装箱);端口名在块内唯一,添加后不可改类型,可重命名 - **数据流向**:外部连到块输入端口的值 → 内部「块输入」节点 → 流入内部节点;内部节点的结果连到「块输出」节点 → 执行结束时回写块的外部输出端口 - 块级「运行」控制端口是**块级控制信号**:不出现在内部图中、不可删除、也不占用内部端口命名 **运行语义** - 块作为**一个节点**参与外层调度:外层连到块输入的连线全部就绪后,块才开始执行内部图;内部图每轮执行前重置内部节点 - **「运行」开关**(bool 端口):连接后为 `false` 时整块跳过(标记"未运行"),下游照常——与所有节点的通用「运行」端口是同一个端口(见「扩展开发」) - **循环执行**:内部编辑器右上角配置「循环执行 / 起始 / 结束 / 步长」后,内部图按区间重复执行(每轮完整执行一次,轮间重置);计算出的循环轮数 ≤ 0 时内部图一次都不执行 - **失败语义**:内部任一节点失败 → 块标记失败(错误信息列出内部失败节点),但**不阻断外部下游**(与普通节点一致) - **取消传播**:整图取消时内部图同步响应(内部节点标记"未运行") - 块也自带通用「运行状态」输出端口(0/1/2)——接条件节点即可按**块的成败**分支;输出端口在每轮开始时清空,块未执行时下游读到 0 **编辑与复用** - **双击进入 / 面包屑返回**:进入块内部后画布只显示该块的子图,外层运行中允许进入查看(内部只读) - **嵌套**:块内可以再放块(块中块),层级不限;运行锁与全局运行模型对内层一致生效 - **复制粘贴 / 克隆**:块的内部图(节点 + 连线)整体深克隆,粘贴后内容完整恢复 - **序列化**:块端口定义与内部子图一起保存/恢复(JSON),旧格式兼容 **代码方式创建块**(模型层完整可用,编辑器创建的块同构): ```csharp var block = new NodeBlock { Name = "处理块" }; block.AddInput("数值A").AddOutput("结果"); var inner = new MultiplyNode { Name = "内部乘法" }; block.InnerGraph.AddNode(inner); block.InnerGraph.Connect(block.GetInputProxyNode("数值A"), "值", inner, "数值A"); // 块输入 → 内部 block.InnerGraph.Connect(inner, "结果", block.GetOutputProxyNode("结果"), "值"); // 内部 → 块输出 graph.AddNode(block); graph.Connect(source, "结果", block, "数值A"); // 外部与块端口按名连接 ``` ### 条件节点 按用户设定的条件表达式决定运行哪条后续路线,每个条件对应一个输出端口: - 添加多个条件(自定义名称 + 表达式),表达式为真时该路线激活;多条同时满足时并行运行 - 典型用法:条件节点输出(bool)连接到目标节点的「运行」端口(节点块/任意节点均可),由条件决定谁执行 - **表达式引擎**内置:引用输入端口值 `[输入1]`,支持四则/幂运算、比较(`>` `>=` `<` `<=` `==` `!=`)、逻辑(`&&` `||` `!`,短路求值)与常用数学函数(`abs sqrt sin cos tan pow min max log` 等),以及常量 `pi`、`e` - **对象路径引用**:输入是对象/对象列表时,用 `{input}`、`{input[0]}`(列表首元素)、`{input.Area}`(属性)、`{input[0].Area}`(链式)参与比较与运算——属性是否存在、索引是否越界在运行期报错(该条件视为不满足并显示错误) - **实时校验**:在属性面板输入表达式时即时检查括号、函数名、`[输入名]`/`{对象路径}` 格式与输入名是否存在、整体是否为判断语句;非法表达式对应的路线不生效且不阻断其他路线 > 条件节点的输出端口是 bool 类型,只能连接到 bool 输入(如节点块的「运行」端口),从类型上杜绝了误连到数值端口。 #### 按上游节点的运行结果分支(成功走一条、失败走另一条) 每个节点都自带「运行状态」int 输出端口(引擎在节点执行完成后写入),把它接到条件节点,就能按**上游是否运行成功**决定后续走哪条路线: | 运行状态值 | 含义 | |---|---| | `0` | 未运行(被禁用、「运行」开关关闭、取消未执行) | | `1` | 运行成功 | | `2` | 运行失败 | 连线方式: ``` X 节点.运行状态 → 条件节点.状态 (条件节点输入端口用 [状态] 引用) 条件节点:条件“成功” = [状态] == 1 条件“失败” = [状态] == 2 条件节点.成功 → A 节点.运行 (X 成功时才执行 A) 条件节点.失败 → B 节点.运行 (X 失败时才执行 B) ``` - 连上「运行状态」即建立了依赖:条件节点会**等 X 执行完**(状态已写入)才运行,因此读到的一定是本次结果; - 一条「运行状态」可以同时接多个条件节点;条件表达式也可写 `[状态] > 0`(已运行,不论成败)、`[状态] != 2` 等; - 「运行状态」是 int,**不能直接连「运行」端口**(bool),必须经条件节点中转——这也是类型系统的刻意约束。 ### 计算节点 按用户设定的表达式计算输入并输出结果,每个计算对应一个输出端口: - 添加多个计算(自定义名称 + 表达式 + 输出类型:int/long/float/double/decimal/bool/string),表达式如 `[输入1] + cos([输入2]) * 1` - **数值表达式引擎**内置:四则/幂运算、数学函数(`abs sqrt sin cos tan pow min max log` 等)、常量 `pi`/`e` - **对象路径引用**:输入是对象/对象列表时,用 `{input}`、`{input[0]}`(列表首元素)、`{input.Area}`(属性)、`{input[0].Area}`(链式)参与运算——如 `{input.Area} * 2`、`{input[0].Area} + 1`;属性是否存在、索引是否越界在运行期报错;也可用于拼接(`"标签:" + {input.Name}`,按 ToString 格式化) - **字符串拼接**:`+` 号任一侧为字符串字面量(双引号包裹,如 `"hello"`)或 string 类型输入端口时执行拼接——`"hello" + [a]`(a=1 → `hello1`)、`[a] + "hello"`(→ `1hello`);数值按 "G" 格式转字符串;拼接表达式只能配 string 输出类型(数值表达式配 string 输出仍按 "G" 格式化——原有行为) - **输出类型在添加时选定、之后锁定**;结果自动转换到输出类型,无法转换(如 NaN → int)时节点运行失败并给出明确错误 - 动态输入端口(**添加时可选类型**:object/short/int/float/double/bool/string,默认 object;表达式用 `[名称]` 引用数值、`{名称.属性}`/`{名称[索引]}` 引用对象,两种语法可混用),实时校验表达式格式与输入名是否存在(`- * /` 等运算与函数参数不支持字符串,静态提示) - **面板直读端口值**:属性面板的输入端口行与「输出值」区显示最近一次运行的值(含内置"运行状态"),无需悬停端口看 ToolTip > 计算节点的输出端口按选定类型(如 int/double),只能连接类型兼容的输入。 ### 延时节点 把输入值延时指定毫秒后原样输出(响应取消),用于流程中模拟耗时工具、控制时序: - 输入/输出为 **object 通配**——任意类型的数据流中间都可以插入延时节点做时序控制 - 延时毫秒在属性面板设置(0 表示不延时);图取消/暂停可中断延时 ### 数据入队 / 数据出队节点 生产者-消费者数据流(详见下文「全局数据队列」章节):数据入队把值放入指定名称的全局队列(逐条缓冲不覆盖);数据出队从队列取走一条数据输出(无数据时可等待,可配超时),等待期间同块其他分支照常并行。 ## 内置样式(可复用的控件样式库) 自定义节点控件时可以直接复用框架的深色控件样式——`App.xaml` 已全局合并样式资源,在你的 XAML 中通过 `StaticResource` 直接引用即可,无需重复定义: | 样式 | 控件类型 | 说明 | |------|----------|------| | `DialogTextBoxStyle` | TextBox | 深色输入框,聚焦时蓝色边框 | | `DarkCheckBoxStyle` | CheckBox | 深色复选框,勾选时蓝色填充 + 白色对勾,悬停高亮 | | `DialogButtonStyle` | Button | 深色普通按钮 | | `DialogPrimaryButtonStyle` | Button | 深色主按钮(高亮,用于确定类操作) | | `IconButtonStyle` | Button | 图标按钮(编辑器操作栏同款) | | `DialogCloseButtonStyle` | Button | 弹窗关闭按钮 | | `DialogMessageTextStyle` | TextBlock | 弹窗消息文本样式 | | `DarkContextMenuStyle` | ContextMenu | 深色右键菜单 | | `DarkMenuItemStyle` | MenuItem | 深色菜单项(悬停高亮) | 在自定义节点控件中直接使用: ```xml