You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Qt中QGraphicsItem子类Edge实现自环弧绘制及重写方法问题

解决QGraphicsItem自环弧绘制及边界/形状问题

我看了你的代码,自环绘制和边界计算的问题主要出在三个地方:弧的参数设置、包围盒的计算逻辑,还有自环箭头的缺失。下面一步步给你修正:

1. 修复自环的绘制逻辑(paint方法)

原来的drawArc参数有误,而且没有绘制自环的箭头。我们可以把自环做成从节点右侧出发,绕到上方再回到节点的圆弧,同时添加指向节点的箭头:

void Edge::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) {
    if (!source || !dest) return;
    QPen pen((option->state & QStyle::State_Selected ? Qt::cyan : Qt::black), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
    painter->setPen(pen);

    if (source != dest) {
        // 保留原有普通箭头绘制逻辑
        QLineF line(sourcePoint, destPoint);
        if (qFuzzyCompare(line.length(), qreal(0.))) return;
        painter->drawLine(line);
        double angle = std::atan2(-line.dy(), line.dx());
        QPointF destArrowP1 = destPoint + QPointF(sin(angle - M_PI / 1.8) * qMin(arrowSize, line.length()), cos(angle - M_PI / 1.8) * qMin(arrowSize, line.length()));
        QPointF destArrowP2 = destPoint + QPointF(sin(angle - M_PI + M_PI / 1.8) * qMin(arrowSize, line.length()), cos(angle - M_PI + M_PI / 1.8) * qMin(arrowSize, line.length()));
        painter->setBrush((option->state & QStyle::State_Selected ? Qt::cyan : Qt::black));
        painter->drawPolygon(QPolygonF() << line.p2() << destArrowP1 << destArrowP2);
    } else {
        // 自环绘制修正
        const qreal ringRadius = Node::Radius + 10; // 自环半径,比节点半径大一点,可按需调整
        const QPointF center = source->pos(); // 节点中心坐标
        // 绘制圆弧:从右侧(0度)到上方(270度),跨度270度(Qt角度单位是1/16度,所以要乘16)
        QRectF arcRect(center.x() - ringRadius, center.y() - ringRadius, 2*ringRadius, 2*ringRadius);
        painter->drawArc(arcRect, 0 * 16, 270 * 16);

        // 添加自环箭头
        painter->setBrush((option->state & QStyle::State_Selected ? Qt::cyan : Qt::black));
        QPointF arrowTip = center + QPointF(0, -ringRadius); // 箭头指向节点上方的圆弧端点
        double arrowAngle = M_PI / 2; // 对应圆弧端点的切线方向
        QPointF arrowP1 = arrowTip + QPointF(sin(arrowAngle - M_PI / 6) * arrowSize, cos(arrowAngle - M_PI / 6) * arrowSize);
        QPointF arrowP2 = arrowTip + QPointF(sin(arrowAngle + M_PI / 6) * arrowSize, cos(arrowAngle + M_PI / 6) * arrowSize);
        painter->drawPolygon(QPolygonF() << arrowTip << arrowP1 << arrowP2);
    }
}

2. 修复boundingRect和shape的计算

原来的nPolygonMath对自环用了矩形框,导致边界不准确,我们需要为自环构建精准的路径和包围盒:

QRectF Edge::boundingRect() const {
    if (!source || !dest) return QRectF();
    if (source != dest) {
        return nPolygonMath().boundingRect();
    } else {
        const qreal ringRadius = Node::Radius + 10;
        const QPointF center = source->pos();
        // 包围盒要包含圆弧和箭头,额外预留笔宽和箭头大小的空间
        qreal extra = pen().width() + arrowSize;
        return QRectF(center.x() - ringRadius - extra, center.y() - ringRadius - extra,
                      2*(ringRadius + extra), 2*(ringRadius + extra));
    }
}

QPainterPath Edge::shape() const {
    QPainterPath ret;
    if (source != dest) {
        ret.addPolygon(nPolygonMath());
    } else {
        const qreal ringRadius = Node::Radius + 10;
        const QPointF center = source->pos();
        QRectF arcRect(center.x() - ringRadius, center.y() - ringRadius, 2*ringRadius, 2*ringRadius);
        // 添加圆弧路径
        ret.arcTo(arcRect, 0, 270);
        // 添加箭头路径,确保选中时能覆盖箭头区域
        QPointF arrowTip = center + QPointF(0, -ringRadius);
        double arrowAngle = M_PI / 2;
        QPointF arrowP1 = arrowTip + QPointF(sin(arrowAngle - M_PI / 6) * arrowSize, cos(arrowAngle - M_PI / 6) * arrowSize);
        QPointF arrowP2 = arrowTip + QPointF(sin(arrowAngle + M_PI / 6) * arrowSize, cos(arrowAngle + M_PI / 6) * arrowSize);
        ret.addPolygon(QPolygonF() << arrowTip << arrowP1 << arrowP2);
        // 给路径添加宽度,方便选中(细线直接点击很难选中)
        QPainterPathStroker stroker;
        stroker.setWidth(pen().width() + 4);
        ret = stroker.createStroke(ret);
    }
    return ret;
}

// 保留普通边的nPolygonMath逻辑,自环部分无需处理
QPolygonF Edge::nPolygonMath() const {
    QPolygonF nPolygon;
    if (source != dest) {
        QLineF line = QLineF(sourcePoint.x(), sourcePoint.y(), destPoint.x(), destPoint.y());
        qreal radAngle = line.angle() * M_PI / 180;
        qreal selectionOffset = 3;
        qreal dx = selectionOffset * sin(radAngle);
        qreal dy = selectionOffset * cos(radAngle);
        QPointF offset1 = QPointF(dx, dy);
        QPointF offset2 = QPointF(-dx, -dy);
        nPolygon << line.p1() + offset1 << line.p1() + offset2 << line.p2() + offset2 << line.p2() + offset1;
    }
    return nPolygon;
}

3. 额外注意点

  • ringRadius可以根据你的UI需求调整,让自环和节点保持合适的距离
  • 自环的圆弧跨度(比如改成180度半圆)、箭头角度都可以按需修改
  • 确保adjust()方法中,当source == dest时无需更新sourcePoint和destPoint(自环绘制用不到这两个点)

这样修改后,自环的绘制会更准确,boundingRect和shape也能正确覆盖自环区域,选中和碰撞检测功能也能正常工作。

内容的提问来源于stack exchange,提问作者Kto To

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:49:13