文章目录


Qt节点编辑器设计与实现:动态编辑与任务流可视化(一)
深入解析Qt节点编辑器框架:交互逻辑与样式系统(二)
深入解析Qt节点编辑器框架:数据流转与扩展机制(三)
深入解析Qt节点编辑器框架:高级特性与性能优化(四)
- 本项目是由Qt开源项目NodeEditor二次开发而来,项目地址,这是个非常不错的项目,熟悉并了解它对自身与Qt画板框架的使用会有很大提升。
一、项目概述
该节点编辑器旨在提供简单易用的任务流可视化编辑功能,支持节点自由布局、动态状态设置与流程连接管理。从代码结构来看,系统采用模块化设计,主要包含样式管理、图形数据模型、节点交互逻辑等核心模块,基于Qt框架实现跨平台兼容,可广泛应用于工作流设计、数据处理流程编排等场景。
包含核心功能如下:
- 节点编辑:支持节点的添加、删除、移动、调整大小、动态端口、动态变量等操作。
- 连接编辑:支持连接的创建、删除、标签编辑等操作。。
- 画板操作:支持撤销、重做、复制、粘贴、旋转、缩放、筛选节点等操作。
- 动态样式:支持节点、连接线与画板的动态样式设置,如颜色、字体、边框等。
- 菜单栏操作:支持文件操作新建、打开、保存、导出图片等。
- 保存与加载:支持将当前任务流保存为文件,后续可加载编辑。
- 任务流加载:支持通过通信动态加载节点与连接线,从而达到任务流显示效果。
- …
二、整体架构:模型-视图分离的设计哲学
该框架严格遵循模型-视图(Model-View) 设计模式,将数据逻辑与图形展示解耦,这是处理复杂交互场景的关键。
1. 模型层:数据与业务逻辑的核心
模型层由AbstractGraphModel
(抽象基类)和DataFlowGraphModel
(具体实现)构成,负责管理节点、连接的核心数据与业务规则:
- 节点数据:包括节点类型、端口信息、位置尺寸、内部状态等。
- 连接数据:以
ConnectionId
(包含源节点、源端口、目标节点、目标端口)唯一标识一条连接,维护连接的有效性规则。 - 业务逻辑:如连接是否允许创建(类型匹配、无循环)、端口动态增删时的连接调整等。
代码中,DataFlowGraphModel
通过_models
(存储节点实例)和_connectivity
(存储连接ID集合)管理核心数据,对外提供addNode
、addConnection
等接口,确保数据操作的一致性。
2. 视图层:图形渲染与用户交互
视图层由BasicGraphicsScene
、DataFlowGraphicsScene
等场景类,以及NodeGraphicsObject
、ConnectionGraphicsObject
等图形对象类构成:
- 场景类:继承自
QGraphicsScene
,负责管理所有图形对象的生命周期,处理全局事件(如右键菜单、保存加载)。 - 图形对象类:继承自
QGraphicsItem
,负责单个节点/连接的渲染(如节点样式、连接线条)和局部交互(如拖拽、点击)。
例如,BasicGraphicsScene
的traverseGraphAndPopulateGraphicsObjects
方法会遍历模型中的节点和连接,创建对应的图形对象,实现模型到视图的初始同步。
3. 交互层:连接模型与视图的桥梁
交互层通过信号槽机制实现模型与视图的实时同步:
- 模型变化时(如
nodeCreated
、connectionDeleted
),发送信号通知视图更新图形对象。 - 视图接收用户操作(如拖拽节点、创建连接),通过命令模式(
QUndoStack
)修改模型,确保操作可撤销。
代码中,BasicGraphicsScene
在构造函数中连接了模型的多个信号(如connectionCreated
→onConnectionCreated
),保证视图能及时响应模型变化。
三、核心模块解析
1. 样式管理系统:视觉表现的基石
节点编辑器的视觉体验直接影响用户交互效率,该系统通过NodeStyle
、ConnectionStyle
与StyleCollection
构建了灵活的样式管理机制。
多状态样式支持:
NodeStyle
类通过_styleData
字典存储不同状态(如正常、选中、错误)的样式配置,包括边界颜色、渐变颜色、阴影效果等。例如,在加载JSON配置时,既支持兼容旧版本的默认样式,也支持多状态样式定义:// 多状态样式加载逻辑 for (auto it = nodeStyleObj.begin(); it != nodeStyleObj.end(); ++it) { QString state = it.key(); QJsonObject stateObj = it.value().toObject(); NodeStyleData data; // 解析颜色、尺寸等属性... _styleData[state] = data; }
JSON序列化与反序列化:样式配置支持从JSON文件/文本加载,也可导出为JSON,便于配置共享与保存。颜色解析通过
parseColor
辅助函数实现,支持RGB数组与十六进制字符串两种格式,提升配置灵活性。全局样式管理:
StyleCollection
作为单例模式,统一管理节点、连接与视图的样式,提供setNodeStyle
等方法实现全局样式切换,确保界面风格一致性。
2. 图形数据模型:数据与关系的核心载体
图形数据模型是节点编辑器的"大脑",负责管理节点、连接及其关系。系统通过AbstractGraphModel
、StandardModel
与DataFlowGraphModel
构建了层次化的模型体系。
节点与连接管理:
StandardModel
实现了节点的增删、连接的创建与删除等基础功能。通过_models
存储节点实例,_connectivity
维护连接关系,支持allNodeIds
、connections
等方法查询数据:// 节点添加逻辑 NodeId StandardModel::addNode(QString const nodeType) { std::unique_ptr<NodeDelegateModel> model = _registry->create(nodeType); if (model) { NodeId newId = newNodeId(); _models[newId] = std::move(model); Q_EMIT nodeCreated(newId); return newId; } return InvalidNodeId; }
动态端口处理:节点端口的动态增删是高级功能,
AbstractGraphModel
通过portsAboutToBeDeleted
、portsInserted
等方法,在端口变化时自动调整关联连接,避免连接失效:// 端口删除前处理关联连接 void AbstractGraphModel::portsAboutToBeDeleted(...) { for (PortIndex portIndex = first; portIndex <= clampedLast; ++portIndex) { std::unordered_set<ConnectionId> conns = connections(nodeId, portType, portIndex); for (auto connectionId : conns) { deleteConnection(connectionId); } } }
连接合法性检查:
DataFlowGraphModel
在创建连接时,通过connectionPossible
方法检查数据类型匹配、端口是否可用及是否存在循环依赖,确保流程逻辑正确:// 无环检查(深度优先遍历) auto hasLoops = [this, &connectionId]() -> bool { std::stack<NodeId> filo; filo.push(connectionId.inNodeId); while (!filo.empty()) { auto id = filo.top(); filo.pop(); if (id == connectionId.outNodeId) return true; // 发现循环 // 递归检查下游节点 } return false; };
3. 节点交互与几何计算
节点的交互体验依赖于精准的几何计算,AbstractNodeGeometry
类封装了端口位置计算、碰撞检测等核心逻辑:
端口位置计算:
portScenePosition
方法结合节点变换矩阵,计算端口在场景中的绝对位置,为连接绘制提供坐标基础。端口碰撞检测:
checkPortHit
通过计算鼠标位置与端口的距离,判断是否点击端口,支持用户发起连接操作:PortIndex AbstractNodeGeometry::checkPortHit(...) const { double const tolerance = 2.0 * nodeStyle.getNodeStyleData(nodeState).ConnectionPointDiameter; for (unsigned int portIndex = 0; portIndex < n; ++portIndex) { auto pp = portPosition(nodeId, portType, portIndex); QPointF p = pp - nodePoint; auto distance = std::sqrt(QPointF::dotProduct(p, p)); if (distance < tolerance) return portIndex; } return InvalidPortIndex; }
4. 跨平台与模块化设计
系统通过Compiler.hpp
与Export.hpp
实现跨平台兼容,支持MinGW、Clang、MSVC等主流编译器,并通过宏定义统一动态库导出/导入逻辑:
// 跨平台导出宏定义
#ifdef NODE_EDITOR_PLATFORM_WINDOWS
#define NODE_EDITOR_EXPORT __declspec(dllexport)
#define NODE_EDITOR_IMPORT __declspec(dllimport)
#elif NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR >=4 || defined(NODE_EDITOR_COMPILER_CLANG)
#define NODE_EDITOR_EXPORT __attribute__((visibility("default")))
#endif
节点数据处理采用模块化设计,MathOperationDataModel
与DecimalData
展示了如何实现具体节点逻辑(如数值计算),通过setInData
接收输入、compute
处理数据、outData
输出结果,便于扩展新节点类型。
四、实战示例:任务流配置与加载
MainWidget
中的test
方法展示了如何通过JSON配置初始化任务流,这是我随意模拟的数据,实际上的数据应该是通信数据解析而来。示例JSON包含节点(如"星期一"、“上班”)与连接关系,加载后通过定时器动态更新数据,模拟任务流的运行状态:
// 从JSON字符串加载任务流
QJsonDocument doc = QJsonDocument::fromJson(jsonString.toUtf8());
m_jsonObj = doc.object();
// 定时器更新数据
m_testTimer->start(1000);
五、总结
- 本身也就是为了学习更加深入的Qt画板框架,所以项目开源
- 节点编辑器框架的设计与实现,从基础到高级,覆盖了模型-视图分离、交互逻辑、数据流转、扩展机制等多个方面。
- 框架的核心技术点包括模型-视图分离、命令模式、信号槽机制、类型安全的传递机制、依赖驱动的更新逻辑、惰性计算、抽象接口与工厂模式、序列化等。
- 框架的优势在于其高度的可定制性、可扩展性、可维护性。
- 框架的应用场景包括可视化编程、数据处理流水线、工业控制流程图等。
- 未来可进一步优化的方向包括分布式节点计算(跨进程/网络)、节点性能分析工具、AI辅助节点推荐等。