基于Qt的在QGraphicsView中绘制带有可动拐点的连线的一种方法 ...

打印 上一主题 下一主题

主题 864|帖子 864|积分 2594

    摘要:本文详细先容了基于Qt框架在QGraphicsView 中实现带有可动拐点连线的绘制方法。通过自定CustomItem和CustomPath类,结合QGraphicsItem的几何变化事故与QPainterPath的路径绘制功能,实现了动态连线的基本框架。进一步探讨了平行线偏移规则的计划与拐点交织问题的办理方案,通过角平分线计算和交织检测优化路径毗连效果。最终提出了一种支持用户拖拽拐点、自动革新连线的交互式图形方案,并展示了代码实现与效果演示,为复杂图形编辑工具的开辟提供了参考。

    关键词:QGraphicsView、QGraphicsItem、QPainterPath、可动拐点、平行线偏移、角平分线、交织检测、Qt图形框架


    完整代码见末了。


1、QGraphicsItem和QPainterPath的底子利用

    做一点准备工作,先用一个简朴的案例,创建代码基本框架。
问题描述:
    已知起点和终点,怎样绘制过两点的线段?
    要求:点图形可动,连线图形可革新
办理思绪:
    1、准备工作,需要创建可动的图形类CustomItem和连线类CustomPath,以便观察各种环境。
    2、利用QPainterPath的moveTo()和lineTo()绘制连线。
    3、在图形类CustomItem的itemChange函数中革新连线。
代码如下:
  1. class CustomPath;
  2. // 图形类,描述起点和终点
  3. class CustomItem : public QGraphicsRectItem
  4. {
  5. public:
  6.     CustomItem(QGraphicsItem *parent = nullptr);
  7.     void addPath(CustomPath *path);
  8. protected:
  9.     QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
  10. private:
  11.     QList<CustomPath *> mPathList; // 连线列表
  12. };
  13. CustomItem::CustomItem(QGraphicsItem *parent) : QGraphicsRectItem(parent)
  14. {
  15.     // 设置形状
  16.     setRect(-5, -5, 10, 10);
  17.     // 设置颜色
  18.     setBrush(Qt::black);
  19.     // 设置可移动
  20.     setFlag(QGraphicsItem::ItemIsMovable, true);
  21.     // 设置可发送几何变动,可在itemChange中进行检测
  22.     setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
  23. }
  24. // 添加连线
  25. void CustomItem::addPath(CustomPath *path)
  26. {
  27.     mPathList.append(path);
  28. }
  29. QVariant CustomItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
  30. {
  31.     switch (change) {
  32.     // 当位置变动时,刷新连线
  33.     case QGraphicsItem::ItemPositionHasChanged:
  34.     {
  35.         for (int i = 0, size = mPathList.size(); i < size; ++i) {
  36.             mPathList.at(i)->updatePosition();
  37.         }
  38.     }
  39.     default:
  40.         break;
  41.     }
  42.     return QGraphicsItem::itemChange(change, value);
  43. }
复制代码
    在这段代码中,创建了图形类CustomItem,设置图形可移动,同时在移动时革新与图形相连的连线。

  1. // 连线类,描述连线
  2. class CustomPath : public QGraphicsPathItem
  3. {
  4. public:
  5.     CustomPath(QGraphicsItem *start, QGraphicsItem *end, QGraphicsItem *parent = nullptr);
  6.     void updatePosition(); // 刷新连线
  7. private:
  8.     QGraphicsItem *mStartItem = nullptr;  // 起点
  9.     QGraphicsItem *mEndItem = nullptr;    // 终点
  10. };
  11. CustomPath::CustomPath(QGraphicsItem *start, QGraphicsItem *end, QGraphicsItem *parent)
  12.     : QGraphicsPathItem(parent), mStartItem(start), mEndItem(end)
  13. {
  14.     // 设置绘制画笔,颜色黑色,笔宽为1
  15.     setPen(QPen(Qt::black, 1));
  16. }
  17. // 刷新连线
  18. void CustomPath::updatePosition()
  19. {
  20.     // 获取两端的位置
  21.     QPointF start_pos = mStartItem->pos();
  22.     QPointF end_pos = mEndItem->pos();
  23.     // 绘制连线
  24.     QPainterPath path;
  25.     path.moveTo(start_pos);
  26.     path.lineTo(end_pos);
  27.     // 设置连线
  28.     setPath(path);
  29. }
复制代码
    在这段代码中,创建了连线类CustomPath,主要作用是革新连线updatePosition函数。

  1.     // 创建画布
  2.     QGraphicsScene *scene = new QGraphicsScene(this);
  3.     ui->graphicsView->setScene(scene);
  4.     // 创建起点
  5.     CustomItem *item_start = new CustomItem;
  6.     item_start->setPos(100, 100);
  7.     scene->addItem(item_start);
  8.     // 创建终点
  9.     CustomItem *item_end = new CustomItem;
  10.     item_end->setPos(200, 200);
  11.     scene->addItem(item_end);
  12.     // 创建连线
  13.     CustomPath *path = new CustomPath(item_start, item_end);
  14.     item_start->addPath(path);
  15.     item_end->addPath(path);
  16.     path->updatePosition();
  17.     scene->addItem(path);
复制代码
    在这段代码中,创建了点A和点B,设置它们的位置,创建了连线并革新。


效果如下:


2、怎样创建平行线

    现在要在同一个连线类CustomPath中绘制两条连线,引出偏移规则的确定方法。
问题描述:
    现有点A和点B,分别在其附近找两点(点A1A2,点B1B2),怎样绘制两条平行线?
办理思绪:
    只需确定偏移规则。好比这两点分别位于点的左右或者上下两侧。这里设置为左右偏移5个像素点。

代码如下:
  1. void CustomPath::updatePosition()
  2. {
  3.     QPointF start_pos = mStartItem->pos();
  4.     QPointF end_pos = mEndItem->pos();
  5.     // 起点左右偏移
  6.     QPointF start_p1 = start_pos + QPointF(-5, 0);
  7.     QPointF start_p2 = start_pos + QPointF(5, 0);
  8.     // 终点左右偏移
  9.     QPointF end_p1 = end_pos + QPointF(-5, 0);
  10.     QPointF end_p2 = end_pos + QPointF(5, 0);
  11.     // 两次连线
  12.     QPainterPath path;
  13.     path.moveTo(start_p1);
  14.     path.lineTo(end_p1);
  15.     path.moveTo(start_p2);
  16.     path.lineTo(end_p2);
  17.     setPath(path);
  18. }
复制代码
    这段代码中,将起点和终点分别左右偏移五个像素,然后连线,利用两次moveTo()和lineTo()。

效果如下:


3、偏移规则的问题

问题描述:
    可以发现(如图),当移动两个点位于同一水平线时,连线会发生重叠。
办理思绪:
    这是由于偏移规则的缺陷。无论是上下偏移还是左右偏移或者其他的偏移,都会产生这种环境。
    那么,这两个偏移点必须根据环境发生变化。
    确定新的偏移规则:斜向偏移,直线如果斜向右上(或者斜向左下),则偏移点为(5,5)和(-5,-5);直线如果斜向左上(或者斜向右下),则偏移点为(5,-5)和(-5,5)。
代码如下:
  1. // 偏移规则
  2. QPointF CustomPath::getOffset(const QPointF &p1, const QPointF &p2)
  3. {
  4.     QPointF dp = p1 - p2;
  5.     QPointF offset;
  6.     // 根据差值判断
  7.     if (dp.x() * dp.y() >= 0) {
  8.         // 设置偏移量
  9.         offset = QPointF(-5, 5);
  10.     } else {
  11.         offset = QPointF(5, 5);
  12.     }
  13.     return offset;
  14. }
  15. void CustomPath::updatePosition()
  16. {
  17.     QPointF start_pos = mStartItem->pos();
  18.     QPointF end_pos = mEndItem->pos();
  19.     QPointF offset = getOffset(start_pos, end_pos);
  20.     // 起点和终点偏移
  21.     QPointF start_p1 = start_pos + offset;
  22.     QPointF start_p2 = start_pos - offset;
  23.     QPointF end_p1 = end_pos + offset;
  24.     QPointF end_p2 = end_pos - offset;
  25.     QPainterPath path;
  26.     path.moveTo(start_p1);
  27.     path.lineTo(end_p1);
  28.     path.moveTo(start_p2);
  29.     path.lineTo(end_p2);
  30.     setPath(path);
  31. }
复制代码
    在这段代码中,利用了两点xy轴的差值举行判断斜向方向,同时设置了偏移量。
效果如下:


4、带有拐点的连线

问题描述:
    重新从办理简朴的问题开始:现在有点X,需要创建从A->X->B的单条连线,怎样实现?
办理思绪:
    确定拐点位置,插入利用lineTo()即可。
代码如下:
  1. // 拐点类
  2. class CustomPoint : public QGraphicsEllipseItem
  3. {
  4. public:
  5.     CustomPoint(QGraphicsItem *parent = nullptr);
  6.     void setPathItem(CustomPath *pathItem);
  7. protected:
  8.     QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
  9. private:
  10.     CustomPath *mPathItem = nullptr; // 拐点所属连线
  11. };
  12. CustomPoint::CustomPoint(QGraphicsItem *parent)
  13.     : QGraphicsEllipseItem(parent)
  14. {
  15.     // 设置图形为圆形
  16.     setRect(-2, -2, 4, 4);
  17.     setBrush(Qt::black);
  18.     setFlag(QGraphicsItem::ItemIsMovable, true);
  19.     setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
  20. }
  21. QVariant CustomPoint::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
  22. {
  23.     switch (change) {
  24.     case QGraphicsItem::ItemPositionHasChanged:
  25.     {
  26.         // 当拐点位置发生变化,刷新连线
  27.         if (mPathItem) {
  28.             mPathItem->updatePosition();
  29.         }
  30.     }
  31.     default:
  32.         break;
  33.     }
  34.     return QGraphicsItem::itemChange(change, value);
  35. }
  36. void CustomPoint::setPathItem(CustomPath *pathItem)
  37. {
  38.     mPathItem = pathItem;
  39. }
复制代码
    在这段代码中,创建了拐点类CustomPoint,设置它的形状、笔刷、可移动属性;当拐点位置发生变化时,刷线连线。
  1. // 对部分代码进行修改
  2. class CustomPath : public QGraphicsPathItem
  3. {
  4. public:
  5.     CustomPath(QGraphicsItem *start, QGraphicsItem *end, QGraphicsItem *parent = nullptr);
  6.     void updatePosition();
  7.     void setPoint(CustomPoint *point); // 设置拐点
  8. private:
  9.     QGraphicsItem *mStartItem = nullptr;
  10.     QGraphicsItem *mEndItem = nullptr;
  11.     CustomPoint *mPoint = nullptr;  // 拐点
  12. };
  13. void CustomPath::setPoint(CustomPoint *point)
  14. {
  15.     mPoint = point;
  16. }
  17. void CustomPath::updatePosition()
  18. {
  19.     QPointF start_pos = mStartItem->pos();
  20.     QPointF end_pos = mEndItem->pos();
  21.     QPointF point_pos = mPoint->pos();
  22.     QPainterPath path;
  23.     path.moveTo(start_pos);
  24.     path.lineTo(point_pos);   // 从起点->拐点->终点
  25.     path.lineTo(end_pos);
  26.     setPath(path);
  27. }
  28.     // 修改使用代码
  29.     QGraphicsScene *scene = new QGraphicsScene(this);
  30.     ui->graphicsView->setScene(scene);
  31.     // 创建起点
  32.     CustomItem *item_start = new CustomItem;
  33.     item_start->setPos(100, 100);
  34.     scene->addItem(item_start);
  35.     // 创建终点
  36.     CustomItem *item_end = new CustomItem;
  37.     item_end->setPos(200, 200);
  38.     scene->addItem(item_end);
  39.     // 创建连线
  40.     CustomPath *path = new CustomPath(item_start, item_end);
  41.     item_start->addPath(path);
  42.     item_end->addPath(path);
  43.     scene->addItem(path);
  44.     // 添加拐点图形
  45.     CustomPoint *point = new CustomPoint(path);
  46.     point->setPos(100, 150);
  47.     path->setPoint(point);  // 设置拐点
  48.     point->setPathItem(path); // 设置连线
  49.     path->updatePosition();
复制代码
    在这段代码中,对部分代码举行修改:在连线类中添加了拐点成员,在革新连线函数中连线到拐点,在实际利用代码中添加了拐点图形,在末了革新图形连线。
效果如下:


5、带有拐点的两条平行线

问题描述:
    那么怎样绘制带有拐点的两条连线呢?
办理思绪:
    直接将偏移规则应用到拐点位置,根据起点和拐点位置(或者拐点和终点位置)确定偏移,会怎样?
代码如下:
  1. void CustomPath::updatePosition()
  2. {
  3.     QPointF start_pos = mStartItem->pos();
  4.     QPointF end_pos = mEndItem->pos();
  5.     QPointF point_pos = mPoint->pos();
  6.     // 计算偏移
  7.     QPointF offset_sp = getOffset(start_pos, point_pos);
  8.     QPointF offset_pe = getOffset(point_pos, end_pos);
  9.     // 起点偏移
  10.     QPointF start_p1 = start_pos + offset_sp;
  11.     QPointF start_p2 = start_pos - offset_sp;
  12.     // 拐点对起点偏移
  13.     QPointF point_ps1 = point_pos + offset_sp;
  14.     QPointF point_ps2 = point_pos - offset_sp;
  15.     // 拐点对终点偏移
  16. //    QPointF point_pe1 = point_pos + offset_pe;
  17. //    QPointF point_pe2 = point_pos - offset_pe;
  18.     // 终点偏移
  19.     QPointF end_p1 = end_pos + offset_pe;
  20.     QPointF end_p2 = end_pos - offset_pe;
  21.     // 使用两个
  22.     QPainterPath path;
  23.     path.moveTo(start_p1);
  24.     path.lineTo(point_ps1);
  25. //    path.lineTo(point_pe1);
  26.     path.lineTo(end_p1);
  27.     path.moveTo(start_p2);
  28.     path.lineTo(point_ps2);
  29. //    path.lineTo(point_pe2);
  30.     path.lineTo(end_p2);
  31.     // 使用四个
  32. //    {
  33. //        path.moveTo(start_p1);
  34. //        path.lineTo(point_ps1);
  35. //        path.moveTo(point_pe1);
  36. //        path.lineTo(end_p1);
  37. //        path.moveTo(start_p2);
  38. //        path.lineTo(point_ps2);
  39. //        path.moveTo(point_pe2);
  40. //        path.lineTo(end_p2);
  41. //    }
  42.     setPath(path);
  43. }
复制代码
    在调整代码的过程中,就会发现,在拐点对起点和拐点对终点应用偏移规则时,会产生四个偏移点。
    只利用其中两个会发生什么环境?利用四个会发生什么环境?
效果如下:
    利用两个的环境:可以看到连线产生了交错,而且某些角度环境下发生重合。


    利用四个的环境:可以看到连线产生交错,而且某些角度下毗连点错开。


6、拐点处的偏移问题

问题描述:
    如果对拐点也应用偏移规则,利用两个点,会产生交错的环境;利用四个点,不但会产生交错,而且会断开。
办理思绪:
    拐点处的偏移点应该只可以有两个;其偏移点只对一个点应用;考虑利用角平分线。
    方案就是:起点-拐点-终点,形成一个角度,计算出角平分线;过起点的两个偏移点,作起点和拐点连线的两条平行线;这两条平行线和角平分线的交点,作为拐点处的偏移点;然后毗连拐点处偏移点和终点偏移点,形成连线。
代码如下:
  1. // 计算角平分线
  2. QLineF CustomPath::calculateAngleBisector(const QPointF &start, const QPointF &mid, const QPointF &end)
  3. {
  4.     // 计算向量A和B
  5.     QPointF vectorA = start - mid;
  6.     QPointF vectorB = end - mid;
  7.     // 归一化向量A和B
  8.     qreal lengthA = std::hypot(vectorA.x(), vectorA.y());
  9.     qreal lengthB = std::hypot(vectorB.x(), vectorB.y());
  10.     QPointF unitA = vectorA / lengthA;
  11.     QPointF unitB = vectorB / lengthB;
  12.     // 计算角平分线向量
  13.     QPointF bisector = unitA + unitB;
  14.     // 如果共线则向量为零,需要使用垂线
  15.     if (bisector.isNull()) {
  16.         bisector = QPointF(-unitA.y(), unitA.x());
  17.     }
  18.     // 归一化角平分线向量
  19.     qreal lengthBisector = std::hypot(bisector.x(), bisector.y());
  20.     QPointF unitBisector = bisector / lengthBisector;
  21.     // 从中点出发,沿角平分线方向绘制一条直线
  22.     QPointF bisectorEnd = mid + unitBisector * 100; // 100为长度,可根据需要调整
  23.     QPointF bisectorEnd_n = mid - unitBisector * 100;
  24.     return QLineF(bisectorEnd_n, bisectorEnd);
  25.     //    return unitBisector;
  26. }
  27. // 计算过p点的l1的平行线与bisector_line的交点
  28. QPointF CustomPath::calculateBisectorPoint(const QLineF &l1, const QLineF &bisector_line, const QPointF &p)
  29. {
  30.     // 起点到拐点连线的向量
  31.     QPointF lp(l1.p2() - l1.p1());
  32.     qreal length = std::hypot(lp.x(), lp.y());
  33.     QPointF unit = lp / length;
  34.     // 过偏移点的平行线
  35.     QLineF line(p, p+unit*100);
  36.     // 计算交点
  37.     QPointF intersection;
  38.     QLineF::IntersectType type = line.intersects(bisector_line, &intersection);
  39.     return intersection;
  40. }
  41. void CustomPath::updatePosition()
  42. {
  43.     QPointF start_pos = mStartItem->pos();
  44.     QPointF end_pos = mEndItem->pos();
  45.     QPointF point_pos = mPoint->pos();
  46.     // 计算偏移
  47.     QPointF offset_sp = getOffset(start_pos, point_pos);
  48.     QPointF offset_pe = getOffset(point_pos, end_pos);
  49.     // 起点偏移
  50.     QPointF start_p1 = start_pos + offset_sp;
  51.     QPointF start_p2 = start_pos - offset_sp;
  52.     // 终点偏移
  53.     QPointF end_p1 = end_pos + offset_pe;
  54.     QPointF end_p2 = end_pos - offset_pe;
  55.     // 计算角平分线
  56.     QLineF bisector_line = calculateAngleBisector(start_pos, point_pos, end_pos);
  57.     QLineF start_line(start_pos, point_pos);
  58.     // 计算交点
  59.     QPointF p1_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p1);
  60.     QPointF p2_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p2);
  61.     // 连线
  62.     QPainterPath path;
  63.     path.moveTo(start_p1);
  64.     path.lineTo(p1_bst_itst);
  65.     path.lineTo(end_p1);
  66.     path.moveTo(start_p2);
  67.     path.lineTo(p2_bst_itst);
  68.     path.lineTo(end_p2);
  69.     setPath(path);
  70. }
复制代码
    在这段代码中,计算起点-拐点-终点形成角度的角平分线,考虑三点共线环境下,利用垂线向量;然后有起点到拐点的连线,过两起点偏移点,作平行线,并得到和角平分线的交点;从交点连线到终点偏移点。
效果如图:
    可见在拐点和终点的两条连线发生了交织,继续美满。


7、后半段交织问题

问题描述:
    偏移点并不总是对应的,拐点到终点的连线发生了交织。
办理思绪:
    判断后半段是否交织,如果交织,则互换偏移点。
代码如下:
  1. // 判断是否交叉
  2. bool CustomPath::calculateLineIsIntersect(const QPointF &start1, const QPointF &end1,
  3.                                                 const QPointF &start2, const QPointF &end2)
  4. {
  5.     QLineF line1(start1, end1);
  6.     QLineF line2(start2, end2);
  7.     QPointF intersection;
  8.     QLineF::IntersectType type = line1.intersects(line2, &intersection);
  9.     if (type == QLineF::BoundedIntersection && ! intersection.isNull()) {
  10.         return true;
  11.     } else {
  12.         return false;
  13.     }
  14. }
  15. void CustomPath::updatePosition()
  16. {
  17.     QPointF start_pos = mStartItem->pos();
  18.     QPointF end_pos = mEndItem->pos();
  19.     QPointF point_pos = mPoint->pos();
  20.     QPointF offset_sp = getOffset(start_pos, point_pos);
  21.     QPointF offset_pe = getOffset(point_pos, end_pos);
  22.     QPointF start_p1 = start_pos + offset_sp;
  23.     QPointF start_p2 = start_pos - offset_sp;
  24.     QPointF end_p1 = end_pos + offset_pe;
  25.     QPointF end_p2 = end_pos - offset_pe;
  26.     // 计算角平分线
  27.     QLineF bisector_line = calculateAngleBisector(start_pos, point_pos, end_pos);
  28.     QLineF start_line(start_pos, point_pos);
  29.     // 计算交点
  30.     QPointF p1_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p1);
  31.     QPointF p2_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p2);
  32.     QPainterPath path;
  33.     // 前半段
  34.     path.moveTo(start_p1);
  35.     path.lineTo(p1_bst_itst);
  36.     path.moveTo(start_p2);
  37.     path.lineTo(p2_bst_itst);
  38.     // 后半段,判断是否交叉
  39.     if (calculateLineIsIntersect(end_p1, p1_bst_itst, end_p2, p2_bst_itst)) {
  40.         // 如果交叉
  41.         path.moveTo(p1_bst_itst);
  42.         path.lineTo(end_p2);
  43.         path.moveTo(p2_bst_itst);
  44.         path.lineTo(end_p1);
  45.     } else {
  46.         path.moveTo(p1_bst_itst);
  47.         path.lineTo(end_p1);
  48.         path.moveTo(p2_bst_itst);
  49.         path.lineTo(end_p2);
  50.     }
  51.     setPath(path);
  52. }
复制代码
    在这段代码中,修改了绘制连线的顺序,先绘制前半段,再绘制后半段;如果后半段发生交织,则互换末了的终点偏移点。
效果如下:
    可见当形成的角度极小的时间,拐点处会非常尖锐,对这个问题我没有很好的办法。还好拐点是可以移动的。如果你有想法,接待共同讨论。


总结:

    本文系统性地办理了在Qt图形视图中绘制动态连线的技术难点。首先,通过继承QGraphicsItem实现可拖拽的图形项CustomItem,利用itemChange事故触发连线革新,确保了图形与路径的实时联动。其次,引入CustomPath类管理路径绘制,通过QPainterPath机动构建线段与拐点毗连逻辑。针对平行线偏移问题,提出基于斜向偏移与角平分线的动态调整策略,有用制止了路径重叠与错位。然而,在非常角度下拐点处仍可能因偏移计算产生尖锐毗连,需进一步优化算法或引入平滑曲线处理。

完整代码:



  • mainwindow.h
点击折叠或睁开代码
  1. #ifndef MAINWINDOW_H
  2. #define MAINWINDOW_H
  3. #include <QMainWindow>
  4. #include <QtWidgets>
  5. QT_BEGIN_NAMESPACE
  6. namespace Ui { class MainWindow; }
  7. QT_END_NAMESPACE
  8. class CustomPath;
  9. class CustomPoint;
  10. // 图形类,描述起点和终点
  11. class CustomItem : public QGraphicsRectItem
  12. {
  13. public:
  14.    CustomItem(QGraphicsItem *parent = nullptr);
  15.    void addPath(CustomPath *path);
  16. protected:
  17.    QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
  18. private:
  19.    QList<CustomPath *> mPathList; // 连线列表
  20. };
  21. // 连线类,描述连线
  22. class CustomPath : public QGraphicsPathItem
  23. {
  24. public:
  25.    CustomPath(QGraphicsItem *start, QGraphicsItem *end, QGraphicsItem *parent = nullptr);
  26.    void updatePosition();   // 刷新连线
  27.    void setPoint(CustomPoint *point);   // 设置拐点
  28. private:
  29.    QGraphicsItem *mStartItem = nullptr;  // 起点
  30.    QGraphicsItem *mEndItem = nullptr;    // 终点
  31.    CustomPoint *mPoint = nullptr;     // 拐点
  32.    QPointF getOffset(const QPointF &p1, const QPointF &p2);
  33.    QLineF calculateAngleBisector(const QPointF& start, const QPointF& mid, const QPointF& end);
  34.    QPointF calculateBisectorPoint(const QLineF &l1, const QLineF &bisector_line, const QPointF &p);
  35.    bool calculateLineIsIntersect(const QPointF &start1, const QPointF &end1, const QPointF &start2, const QPointF &end2);
  36. };
  37. // 拐点类
  38. class CustomPoint : public QGraphicsEllipseItem
  39. {
  40. public:
  41.    CustomPoint(QGraphicsItem *parent = nullptr);
  42.    void setPathItem(CustomPath *pathItem);
  43. protected:
  44.    QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
  45. private:
  46.    CustomPath *mPathItem = nullptr;   // 拐点所属连线
  47. };
  48. class MainWindow : public QMainWindow
  49. {
  50.    Q_OBJECT
  51. public:
  52.    MainWindow(QWidget *parent = nullptr);
  53.    ~MainWindow();
  54. private:
  55.    Ui::MainWindow *ui;
  56.    void initGraphics();
  57. };
  58. #endif // MAINWINDOW_H
复制代码


  • mainwindow.cpp
点击折叠或睁开代码
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. MainWindow::MainWindow(QWidget *parent)
  4.    : QMainWindow(parent)
  5.    , ui(new Ui::MainWindow)
  6. {
  7.    ui->setupUi(this);
  8.    initGraphics();
  9. }
  10. MainWindow::~MainWindow()
  11. {
  12.    delete ui;
  13. }
  14. // 问题1、2、3
  15. //void MainWindow::initGraphics()
  16. //{
  17. //    // 创建画布
  18. //    QGraphicsScene *scene = new QGraphicsScene(this);
  19. //    ui->graphicsView->setScene(scene);
  20. //    // 创建起点
  21. //    CustomItem *item_start = new CustomItem;
  22. //    item_start->setPos(100, 100);
  23. //    scene->addItem(item_start);
  24. //    // 创建终点
  25. //    CustomItem *item_end = new CustomItem;
  26. //    item_end->setPos(200, 200);
  27. //    scene->addItem(item_end);
  28. //    // 创建连线
  29. //    CustomPath *path = new CustomPath(item_start, item_end);
  30. //    item_start->addPath(path);
  31. //    item_end->addPath(path);
  32. //    scene->addItem(path);
  33. //    path->updatePosition();
  34. //}
  35. // 问题4、5
  36. void MainWindow::initGraphics()
  37. {
  38.    QGraphicsScene *scene = new QGraphicsScene(this);
  39.    ui->graphicsView->setScene(scene);
  40.    CustomItem *item_start = new CustomItem;
  41.    item_start->setPos(100, 100);
  42.    scene->addItem(item_start);
  43.    CustomItem *item_end = new CustomItem;
  44.    item_end->setPos(200, 200);
  45.    scene->addItem(item_end);
  46.    CustomPath *path = new CustomPath(item_start, item_end);
  47.    item_start->addPath(path);
  48.    item_end->addPath(path);
  49.    scene->addItem(path);
  50.    // 添加拐点图形
  51.    CustomPoint *point = new CustomPoint(path);
  52.    point->setPos(100, 150);
  53.    path->setPoint(point);
  54.    point->setPathItem(path);
  55.    path->updatePosition();
  56. }
  57. CustomItem::CustomItem(QGraphicsItem *parent) : QGraphicsRectItem(parent)
  58. {
  59.    // 设置形状
  60.    setRect(-5, -5, 10, 10);
  61.    // 设置颜色
  62.    setBrush(Qt::black);
  63.    // 设置可移动
  64.    setFlag(QGraphicsItem::ItemIsMovable, true);
  65.    // 设置可发送几何变动,可在itemChange中进行检测
  66.    setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
  67. }
  68. // 添加连线
  69. void CustomItem::addPath(CustomPath *path)
  70. {
  71.    mPathList.append(path);
  72. }
  73. QVariant CustomItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
  74. {
  75.    switch (change) {
  76.    // 当位置变动时,刷新连线
  77.    case QGraphicsItem::ItemPositionHasChanged:
  78.    {
  79.        for (int i = 0, size = mPathList.size(); i < size; ++i) {
  80.            mPathList.at(i)->updatePosition();
  81.        }
  82.    }
  83.    default:
  84.        break;
  85.    }
  86.    return QGraphicsItem::itemChange(change, value);
  87. }
  88. CustomPath::CustomPath(QGraphicsItem *start, QGraphicsItem *end, QGraphicsItem *parent)
  89.    : QGraphicsPathItem(parent), mStartItem(start), mEndItem(end)
  90. {
  91.    // 设置绘制画笔,颜色黑色,笔宽为1
  92.    setPen(QPen(Qt::black, 1));
  93. }
  94. // 问题1
  95. //void CustomPath::updatePosition()
  96. //{
  97. //    // 获取两端的位置
  98. //    QPointF start_pos = mStartItem->pos();
  99. //    QPointF end_pos = mEndItem->pos();
  100. //    // 绘制连线
  101. //    QPainterPath path;
  102. //    path.moveTo(start_pos);
  103. //    path.lineTo(end_pos);
  104. //    // 设置连线
  105. //    setPath(path);
  106. //}
  107. // 问题2
  108. //void CustomPath::updatePosition()
  109. //{
  110. //    QPointF start_pos = mStartItem->pos();
  111. //    QPointF end_pos = mEndItem->pos();
  112. //    // 起点左右偏移
  113. //    QPointF start_p1 = start_pos + QPointF(-5, 0);
  114. //    QPointF start_p2 = start_pos + QPointF(5, 0);
  115. //    // 终点左右偏移
  116. //    QPointF end_p1 = end_pos + QPointF(-5, 0);
  117. //    QPointF end_p2 = end_pos + QPointF(5, 0);
  118. //    // 两次连线
  119. //    QPainterPath path;
  120. //    path.moveTo(start_p1);
  121. //    path.lineTo(end_p1);
  122. //    path.moveTo(start_p2);
  123. //    path.lineTo(end_p2);
  124. //    setPath(path);
  125. //}
  126. QPointF CustomPath::getOffset(const QPointF &p1, const QPointF &p2)
  127. {
  128.    QPointF dp = p1 - p2;
  129.    QPointF offset;
  130.    // 根据差值判断
  131.    if (dp.x() * dp.y() >= 0) {
  132.        // 设置偏移量
  133.        offset = QPointF(-5, 5);
  134.    } else {
  135.        offset = QPointF(5, 5);
  136.    }
  137.    return offset;
  138. }
  139. // 问题3
  140. //void CustomPath::updatePosition()
  141. //{
  142. //    QPointF start_pos = mStartItem->pos();
  143. //    QPointF end_pos = mEndItem->pos();
  144. //    QPointF offset = getOffset(start_pos, end_pos);
  145. //    // 起点和终点偏移
  146. //    QPointF start_p1 = start_pos + offset;
  147. //    QPointF start_p2 = start_pos - offset;
  148. //    QPointF end_p1 = end_pos + offset;
  149. //    QPointF end_p2 = end_pos - offset;
  150. //    QPainterPath path;
  151. //    path.moveTo(start_p1);
  152. //    path.lineTo(end_p1);
  153. //    path.moveTo(start_p2);
  154. //    path.lineTo(end_p2);
  155. //    setPath(path);
  156. //}
  157. // 问题4
  158. //void CustomPath::updatePosition()
  159. //{
  160. //    QPointF start_pos = mStartItem->pos();
  161. //    QPointF end_pos = mEndItem->pos();
  162. //    QPointF point_pos = mPoint->pos();
  163. //    QPainterPath path;
  164. //    path.moveTo(start_pos);
  165. //    path.lineTo(point_pos);   // 从起点->拐点->终点
  166. //    path.lineTo(end_pos);
  167. //    setPath(path);
  168. //}
  169. // 问题5
  170. //void CustomPath::updatePosition()
  171. //{
  172. //    QPointF start_pos = mStartItem->pos();
  173. //    QPointF end_pos = mEndItem->pos();
  174. //    QPointF point_pos = mPoint->pos();
  175. //    // 计算偏移
  176. //    QPointF offset_sp = getOffset(start_pos, point_pos);
  177. //    QPointF offset_pe = getOffset(point_pos, end_pos);
  178. //    // 起点偏移
  179. //    QPointF start_p1 = start_pos + offset_sp;
  180. //    QPointF start_p2 = start_pos - offset_sp;
  181. //    // 拐点对起点偏移
  182. //    QPointF point_ps1 = point_pos + offset_sp;
  183. //    QPointF point_ps2 = point_pos - offset_sp;
  184. //    // 拐点对终点偏移
  185. //    QPointF point_pe1 = point_pos + offset_pe;
  186. //    QPointF point_pe2 = point_pos - offset_pe;
  187. //    // 终点偏移
  188. //    QPointF end_p1 = end_pos + offset_pe;
  189. //    QPointF end_p2 = end_pos - offset_pe;
  190. //    // 使用两个
  191. //    QPainterPath path;
  192. //    path.moveTo(start_p1);
  193. //    path.lineTo(point_ps1);
  194. ////    path.lineTo(point_pe1);
  195. //    path.lineTo(end_p1);
  196. //    path.moveTo(start_p2);
  197. //    path.lineTo(point_ps2);
  198. ////    path.lineTo(point_pe2);
  199. //    path.lineTo(end_p2);
  200. //      // 使用四个
  201. ////    {
  202. ////        path.moveTo(start_p1);
  203. ////        path.lineTo(point_ps1);
  204. ////        path.moveTo(point_pe1);
  205. ////        path.lineTo(end_p1);
  206. ////        path.moveTo(start_p2);
  207. ////        path.lineTo(point_ps2);
  208. ////        path.moveTo(point_pe2);
  209. ////        path.lineTo(end_p2);
  210. ////    }
  211. //    setPath(path);
  212. //}
  213. // 问题6
  214. //void CustomPath::updatePosition()
  215. //{
  216. //    QPointF start_pos = mStartItem->pos();
  217. //    QPointF end_pos = mEndItem->pos();
  218. //    QPointF point_pos = mPoint->pos();
  219. //    QPointF offset_sp = getOffset(start_pos, point_pos);
  220. //    QPointF offset_pe = getOffset(point_pos, end_pos);
  221. //    QPointF start_p1 = start_pos + offset_sp;
  222. //    QPointF start_p2 = start_pos - offset_sp;
  223. //    QPointF end_p1 = end_pos + offset_pe;
  224. //    QPointF end_p2 = end_pos - offset_pe;
  225. //    // 计算角平分线
  226. //    QLineF bisector_line = calculateAngleBisector(start_pos, point_pos, end_pos);
  227. //    QLineF start_line(start_pos, point_pos);
  228. //    // 计算交点
  229. //    QPointF p1_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p1);
  230. //    QPointF p2_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p2);
  231. //    QPainterPath path;
  232. //    path.moveTo(start_p1);
  233. //    path.lineTo(p1_bst_itst);
  234. //    path.lineTo(end_p1);
  235. //    path.moveTo(start_p2);
  236. //    path.lineTo(p2_bst_itst);
  237. //    path.lineTo(end_p2);
  238. //    setPath(path);
  239. //}
  240. // 问题7
  241. void CustomPath::updatePosition()
  242. {
  243.    QPointF start_pos = mStartItem->pos();
  244.    QPointF end_pos = mEndItem->pos();
  245.    QPointF point_pos = mPoint->pos();
  246.    QPointF offset_sp = getOffset(start_pos, point_pos);
  247.    QPointF offset_pe = getOffset(point_pos, end_pos);
  248.    QPointF start_p1 = start_pos + offset_sp;
  249.    QPointF start_p2 = start_pos - offset_sp;
  250.    QPointF end_p1 = end_pos + offset_pe;
  251.    QPointF end_p2 = end_pos - offset_pe;
  252.    // 计算角平分线
  253.    QLineF bisector_line = calculateAngleBisector(start_pos, point_pos, end_pos);
  254.    QLineF start_line(start_pos, point_pos);
  255.    // 计算交点
  256.    QPointF p1_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p1);
  257.    QPointF p2_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p2);
  258.    QPainterPath path;
  259.    // 前半段
  260.    path.moveTo(start_p1);
  261.    path.lineTo(p1_bst_itst);
  262.    path.moveTo(start_p2);
  263.    path.lineTo(p2_bst_itst);
  264.    // 后半段,判断是否交叉
  265.    if (calculateLineIsIntersect(end_p1, p1_bst_itst, end_p2, p2_bst_itst)) {
  266.        // 如果交叉
  267.        path.moveTo(p1_bst_itst);
  268.        path.lineTo(end_p2);
  269.        path.moveTo(p2_bst_itst);
  270.        path.lineTo(end_p1);
  271.    } else {
  272.        path.moveTo(p1_bst_itst);
  273.        path.lineTo(end_p1);
  274.        path.moveTo(p2_bst_itst);
  275.        path.lineTo(end_p2);
  276.    }
  277.    setPath(path);
  278. }
  279. // 计算角平分线
  280. QLineF CustomPath::calculateAngleBisector(const QPointF &start, const QPointF &mid, const QPointF &end)
  281. {
  282.    // 计算向量A和B
  283.    QPointF vectorA = start - mid;
  284.    QPointF vectorB = end - mid;
  285.    // 归一化向量A和B
  286.    qreal lengthA = std::hypot(vectorA.x(), vectorA.y());
  287.    qreal lengthB = std::hypot(vectorB.x(), vectorB.y());
  288.    QPointF unitA = vectorA / lengthA;
  289.    QPointF unitB = vectorB / lengthB;
  290.    // 计算角平分线向量
  291.    QPointF bisector = unitA + unitB;
  292.    // 如果共线则向量为零,需要使用垂线
  293.    if (bisector.isNull()) {
  294.        bisector = QPointF(-unitA.y(), unitA.x());
  295.    }
  296.    // 归一化角平分线向量
  297.    qreal lengthBisector = std::hypot(bisector.x(), bisector.y());
  298.    QPointF unitBisector = bisector / lengthBisector;
  299.    // 从中点出发,沿角平分线方向绘制一条直线
  300.    QPointF bisectorEnd = mid + unitBisector * 100; // 100为长度,可根据需要调整
  301.    QPointF bisectorEnd_n = mid - unitBisector * 100;
  302.    return QLineF(bisectorEnd_n, bisectorEnd);
  303.    //    return unitBisector;
  304. }
  305. // 计算过p点的l1的平行线与bisector_line的交点
  306. QPointF CustomPath::calculateBisectorPoint(const QLineF &l1, const QLineF &bisector_line, const QPointF &p)
  307. {
  308.    // 起点到拐点连线的向量
  309.    QPointF lp(l1.p2() - l1.p1());
  310.    qreal length = std::hypot(lp.x(), lp.y());
  311.    QPointF unit = lp / length;
  312.    // 过偏移点的平行线
  313.    QLineF line(p, p+unit*100);
  314.    // 计算交点
  315.    QPointF intersection;
  316.    QLineF::IntersectType type = line.intersects(bisector_line, &intersection);
  317.    return intersection;
  318. }
  319. // 判断是否交叉
  320. bool CustomPath::calculateLineIsIntersect(const QPointF &start1, const QPointF &end1,
  321.                                                const QPointF &start2, const QPointF &end2)
  322. {
  323.    QLineF line1(start1, end1);
  324.    QLineF line2(start2, end2);
  325.    QPointF intersection;
  326.    QLineF::IntersectType type = line1.intersects(line2, &intersection);
  327.    if (type == QLineF::BoundedIntersection && ! intersection.isNull()) {
  328.        return true;
  329.    } else {
  330.        return false;
  331.    }
  332. }
  333. void CustomPath::setPoint(CustomPoint *point)
  334. {
  335.    mPoint = point;
  336. }
  337. CustomPoint::CustomPoint(QGraphicsItem *parent)
  338.    : QGraphicsEllipseItem(parent)
  339. {
  340.    // 设置图形为圆形
  341.    setRect(-2, -2, 4, 4);
  342.    setBrush(Qt::black);
  343.    setFlag(QGraphicsItem::ItemIsMovable, true);
  344.    setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
  345. }
  346. QVariant CustomPoint::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
  347. {
  348.    switch (change) {
  349.    case QGraphicsItem::ItemPositionHasChanged:
  350.    {
  351.        // 当拐点位置发生变化,刷新连线
  352.        if (mPathItem) {
  353.            mPathItem->updatePosition();
  354.        }
  355.    }
  356.    default:
  357.        break;
  358.    }
  359.    return QGraphicsItem::itemChange(change, value);
  360. }
  361. void CustomPoint::setPathItem(CustomPath *pathItem)
  362. {
  363.    mPathItem = pathItem;
  364. }
复制代码

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用多少眼泪才能让你相信

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表