Qt/C++实现的节点式图形编辑器,可以添加左侧树上的节点,面板可以放大缩小,节点之间有输入输出接口,使用线进行连接,通过鼠标选中节点或线按住delete键可以进行删除,可以生成节点的图片进行导出的功能。 源码: 使用Qt5.6.1_MinGW、Qt5.13.1_MinGW编译通过,其他版本请自行尝试。

在某个摸鱼的下午突然想搞点可视化编程的东西,于是撸了个能拖拽节点的图形编辑器。核心思路其实挺简单——每个节点都是场景里的可交互元件,接口连线本质上是数学坐标系下的路径计算。

先看节点类的定义:

class NodeItem : public QGraphicsItem {
public:
    QRectF boundingRect() const override {
        return QRectF(-width/2, -height/2, width, height);
    }
    
    void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) override {
        // 绘制主体
        painter->setBrush(QColor(45, 45, 48));
        painter->drawRoundedRect(boundingRect(), 5, 5);
        
        // 绘制接口
        drawPorts(painter);
    }
    
private:
    void drawPorts(QPainter* painter) {
        const int portRadius = 6;
        QPointF inputPos(-width/2, -portRadius*2);
        QPointF outputPos(width/2 - portRadius*2, -portRadius*2);
        
        painter->setBrush(Qt::cyan);
        painter->drawEllipse(inputPos, portRadius, portRadius); // 输入口
        
        painter->setBrush(Qt::magenta);
        painter->drawEllipse(outputPos, portRadius, portRadius); // 输出口
    }
    
    const int width = 120;
    const int height = 80;
};

这个节点的妙处在于坐标系处理——所有绘制都以节点中心为原点,移动时直接用setPos()就能准确定位。接口位置计算用简单算术搞定,输入口固定在左侧边缘,输出口在右侧,后续连线时用scene坐标转换就能获取准确位置。

连线功能涉及贝塞尔曲线:

void ConnectionItem::updatePath() {
    QPointF start = mapFromScene(startPort->scenePos());
    QPointF end = mapFromScene(endPort->scenePos());
    
    QPainterPath path(start);
    path.cubicTo(
        start + QPointF(50, 0),  // 控制点1
        end - QPointF(50, 0),    // 控制点2 
        end
    );
    
    setPath(path);
}

这里用了三次贝塞尔曲线让连线看起来自然。控制点的水平偏移量固定为50像素,这样无论节点间距多大,连线都能呈现平滑的S形。当节点移动时,只需要调用updatePath()就能实时更新连线轨迹。

删除功能实现得相当暴力:

void Scene::keyPressEvent(QKeyEvent* event) {
    if(event->key() == Qt::Key_Delete) {
        foreach(auto item, selectedItems()) {
            if(auto conn = dynamic_cast<ConnectionItem*>(item)) {
                conn->disconnectPorts(); // 先断开端口关联
            }
            delete item; // 直接删除图形项
        }
    }
}

这里直接捕获场景的键盘事件,把选中的元素统统干掉。注意连线删除前要先断开端口关联,否则可能出现野指针。这种处理方式虽然简单,但胜在直观有效。

导出图片就调个现成接口:

void exportToImage() {
    QImage image(scene->sceneRect().size().toSize(), QImage::Format_ARGB32);
    image.fill(Qt::transparent);
    
    QPainter painter(&image);
    scene->render(&painter);
    image.save("node_graph.png");
}

把整个场景渲染到QImage里,想存什么格式改后缀就行。不过要注意场景坐标系和图像尺寸的对应关系,否则可能导出空白图片。

(注:实测Qt5.6和5.13版本可用,其他版本可能需要微调图形项缓存相关的代码)

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐