找回密码
 立即注册
首页 业界区 安全 Qt图形连线功能升级:支持多拐点和颜色区分 ...

Qt图形连线功能升级:支持多拐点和颜色区分

豌畔丛 7 天前
摘要:本文在Qt图形框架中扩展了连线功能,实现了给连线添加多个拐点并使用不同颜色绘制的效果。该实现优化了连线的可视化效果,提升了代码可扩展性,为复杂图形编辑工具的开发提供了参考。

关键词:QGraphicsPathItem、拐点、QPainterPath、颜色设置、Qt图形框架、连线

完整代码见最后。

在上一篇文章的基础上继续实现两个功能:

  • 给连线添加多个拐点
  • 使用不同的颜色给连线上色

添加代码:
  1. // 连线类,描述连线
  2. class CustomPath : public QGraphicsPathItem
  3. {
  4. public:
  5.     CustomPath(QGraphicsItem *start, QGraphicsItem *end, QGraphicsItem *parent = nullptr);
  6.     void updatePosition();   // 刷新连线
  7.     void addPoint(CustomPoint *point);   // 设置拐点
  8. protected:
  9.     void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
  10. private:
  11.     QGraphicsItem *mStartItem = nullptr;  // 起点
  12.     QGraphicsItem *mEndItem = nullptr;    // 终点
  13.     QList<CustomPoint *> mPointList;     // 拐点列表
  14.     QPainterPath mPath1;  // 连线1
  15.     QPainterPath mPath2;  // 连线2
  16.     QPointF getOffset(const QPointF &p1, const QPointF &p2);
  17.     QLineF calculateAngleBisector(const QPointF& start, const QPointF& mid, const QPointF& end);
  18.     QPointF calculateBisectorPoint(const QLineF &l1, const QLineF &bisector_line, const QPointF &p);
  19.     bool calculateLineIsIntersect(const QPointF &start1, const QPointF &end1, const QPointF &start2, const QPointF &end2);
  20. };
  21. void CustomPath::addPoint(CustomPoint *point)
  22. {
  23.     mPointList.append(point);
  24. }
  25. void CustomPath::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  26. {
  27.     painter->save();
  28.     // 用不同的画笔绘制连线
  29.     painter->setPen(QPen(Qt::blue, 2.0));
  30.     painter->drawPath(mPath1);
  31.     painter->setPen(QPen(Qt::red, 2.0));
  32.     painter->drawPath(mPath2);
  33.     painter->restore();
  34. }
  35. void CustomPath::updatePosition()
  36. {
  37.     QPointF start = mStartItem->pos();
  38.     QPointF end = mEndItem->pos();
  39.     QPointF start_offset = getOffset(start, mPointList.first()->pos());
  40.     QPointF start_p1 = start + start_offset;
  41.     QPointF start_p2 = start - start_offset;
  42.     mPath1.clear();
  43.     mPath2.clear();
  44.     mPath1.moveTo(start_p1);
  45.     mPath2.moveTo(start_p2);
  46.     QList<QPointF> points;
  47.     points.append(start);
  48.     for (int i = 0, size = mPointList.size(); i < size; ++i) {
  49.         points.append(mPointList.at(i)->pos());
  50.     }
  51.     points.append(end);
  52.     Q_ASSERT(points.size() >= 3);
  53.     // 记录拐点的偏移点的位置
  54.     QPointF next_start_p1 = start_p1, next_start_p2 = start_p2;
  55.     // 每个拐点都只绘制前半段
  56.     for (int i = 1, size = points.size(); i < size - 1; ++i) {
  57.         QPointF temp_start = points.at(i-1), temp_point = points.at(i), temp_end = points.at(i+1);
  58.         // 计算角平分线
  59.         QLineF bisector_line = calculateAngleBisector(temp_start, temp_point, temp_end);
  60.         QLineF start_line(temp_start, temp_point);
  61.         // 计算交点
  62.         QPointF p1_bst_itst = calculateBisectorPoint(start_line, bisector_line, next_start_p1);
  63.         QPointF p2_bst_itst = calculateBisectorPoint(start_line, bisector_line, next_start_p2);
  64.         // 判断是否交叉
  65.         if (calculateLineIsIntersect(next_start_p1, p1_bst_itst, next_start_p2, p2_bst_itst)) {
  66.             // 如果交叉
  67.             mPath1.lineTo(p2_bst_itst);
  68.             mPath2.lineTo(p1_bst_itst);
  69.             next_start_p1 = p2_bst_itst;
  70.             next_start_p2 = p1_bst_itst;
  71.         } else {
  72.             mPath1.lineTo(p1_bst_itst);
  73.             mPath2.lineTo(p2_bst_itst);
  74.             next_start_p1 = p1_bst_itst;
  75.             next_start_p2 = p2_bst_itst;
  76.         }
  77.     }
  78.     QPointF end_offset = getOffset(mPointList.last()->pos(), end);
  79.     QPointF end_p1 = end + end_offset;
  80.     QPointF end_p2 = end - end_offset;
  81.     // 最后的一段
  82.     if (calculateLineIsIntersect(next_start_p1, end_p1, next_start_p2, end_p2)) {
  83.         // 如果交叉
  84.         mPath1.lineTo(end_p2);
  85.         mPath2.lineTo(end_p1);
  86.     } else {
  87.         mPath1.lineTo(end_p1);
  88.         mPath2.lineTo(end_p2);
  89.     }
  90.     QPainterPath path;
  91.     path.addPath(mPath1);
  92.     path.addPath(mPath2);
  93.     setPath(path);
  94. }
  95.     QGraphicsScene *scene = new QGraphicsScene(this);
  96.     ui->graphicsView->setScene(scene);
  97.     CustomItem *item_start = new CustomItem;
  98.     item_start->setPos(100, 100);
  99.     scene->addItem(item_start);
  100.     CustomItem *item_end = new CustomItem;
  101.     item_end->setPos(200, 200);
  102.     scene->addItem(item_end);
  103.     CustomPath *path = new CustomPath(item_start, item_end);
  104.     item_start->addPath(path);
  105.     item_end->addPath(path);
  106.     scene->addItem(path);
  107.     // 添加拐点图形
  108.     CustomPoint *point1 = new CustomPoint(path);
  109.     point1->setPos(100, 150);
  110.     path->addPoint(point1);
  111.     point1->setPathItem(path);
  112.     CustomPoint *point2 = new CustomPoint(path);
  113.     point2->setPos(150, 100);
  114.     path->addPoint(point2);
  115.     point2->setPathItem(path);
  116.     path->updatePosition();
复制代码
在这段代码中,

  • 给CustomPath添加了拐点列表QList,用于多拐点的存储;
  • 添加了QPainterPath 的两条连线,用于不同的画笔绘制;
  • 重写paint()函数;
  • 对应拐点列表,修改updatePosition()函数,对每个拐点进行计算,刷新连线;
  • 添加测试代码,添加第二个拐点;

效果如下:
    可见,两条线的颜色在视觉上是有互换的情况的。本人能力有限,欢迎各位交流讨论。
1.gif


完整代码:



  • 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 addPoint(CustomPoint *point);   // 设置拐点
  28. protected:
  29.    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
  30. private:
  31.    QGraphicsItem *mStartItem = nullptr;  // 起点
  32.    QGraphicsItem *mEndItem = nullptr;    // 终点
  33.    QList<CustomPoint *> mPointList;     // 拐点列表
  34.    QPainterPath mPath1;  // 连线1
  35.    QPainterPath mPath2;  // 连线2
  36.    QPointF getOffset(const QPointF &p1, const QPointF &p2);
  37.    QLineF calculateAngleBisector(const QPointF& start, const QPointF& mid, const QPointF& end);
  38.    QPointF calculateBisectorPoint(const QLineF &l1, const QLineF &bisector_line, const QPointF &p);
  39.    bool calculateLineIsIntersect(const QPointF &start1, const QPointF &end1, const QPointF &start2, const QPointF &end2);
  40. };
  41. // 拐点类
  42. class CustomPoint : public QGraphicsEllipseItem
  43. {
  44. public:
  45.    CustomPoint(QGraphicsItem *parent = nullptr);
  46.    void setPathItem(CustomPath *pathItem);
  47. protected:
  48.    QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
  49. private:
  50.    CustomPath *mPathItem = nullptr;   // 拐点所属连线
  51. };
  52. class MainWindow : public QMainWindow
  53. {
  54.    Q_OBJECT
  55. public:
  56.    MainWindow(QWidget *parent = nullptr);
  57.    ~MainWindow();
  58. private:
  59.    Ui::MainWindow *ui;
  60.    void initGraphics();
  61. };
  62. #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. void MainWindow::initGraphics()
  15. {
  16.    QGraphicsScene *scene = new QGraphicsScene(this);
  17.    ui->graphicsView->setScene(scene);
  18.    CustomItem *item_start = new CustomItem;
  19.    item_start->setPos(100, 100);
  20.    scene->addItem(item_start);
  21.    CustomItem *item_end = new CustomItem;
  22.    item_end->setPos(200, 200);
  23.    scene->addItem(item_end);
  24.    CustomPath *path = new CustomPath(item_start, item_end);
  25.    item_start->addPath(path);
  26.    item_end->addPath(path);
  27.    scene->addItem(path);
  28.    // 添加拐点图形
  29.    CustomPoint *point1 = new CustomPoint(path);
  30.    point1->setPos(100, 150);
  31.    path->addPoint(point1);
  32.    point1->setPathItem(path);
  33.    CustomPoint *point2 = new CustomPoint(path);
  34.    point2->setPos(150, 100);
  35.    path->addPoint(point2);
  36.    point2->setPathItem(path);
  37.    path->updatePosition();
  38. }
  39. CustomItem::CustomItem(QGraphicsItem *parent) : QGraphicsRectItem(parent)
  40. {
  41.    // 设置形状
  42.    setRect(-5, -5, 10, 10);
  43.    // 设置颜色
  44.    setBrush(Qt::black);
  45.    // 设置可移动
  46.    setFlag(QGraphicsItem::ItemIsMovable, true);
  47.    // 设置可发送几何变动,可在itemChange中进行检测
  48.    setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
  49. }
  50. // 添加连线
  51. void CustomItem::addPath(CustomPath *path)
  52. {
  53.    mPathList.append(path);
  54. }
  55. QVariant CustomItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
  56. {
  57.    switch (change) {
  58.    // 当位置变动时,刷新连线
  59.    case QGraphicsItem::ItemPositionHasChanged:
  60.    {
  61.        for (int i = 0, size = mPathList.size(); i < size; ++i) {
  62.            mPathList.at(i)->updatePosition();
  63.        }
  64.    }
  65.    default:
  66.        break;
  67.    }
  68.    return QGraphicsItem::itemChange(change, value);
  69. }
  70. CustomPath::CustomPath(QGraphicsItem *start, QGraphicsItem *end, QGraphicsItem *parent)
  71.    : QGraphicsPathItem(parent), mStartItem(start), mEndItem(end)
  72. {
  73.    // 设置绘制画笔,颜色黑色,笔宽为1
  74.    setPen(QPen(Qt::black, 1));
  75. }
  76. QPointF CustomPath::getOffset(const QPointF &p1, const QPointF &p2)
  77. {
  78.    QPointF dp = p1 - p2;
  79.    QPointF offset;
  80.    // 根据差值判断
  81.    if (dp.x() * dp.y() >= 0) {
  82.        // 设置偏移量
  83.        offset = QPointF(-5, 5);
  84.    } else {
  85.        offset = QPointF(5, 5);
  86.    }
  87.    return offset;
  88. }
  89. void CustomPath::updatePosition()
  90. {
  91.    QPointF start = mStartItem->pos();
  92.    QPointF end = mEndItem->pos();
  93.    QPointF start_offset = getOffset(start, mPointList.first()->pos());
  94.    QPointF start_p1 = start + start_offset;
  95.    QPointF start_p2 = start - start_offset;
  96.    mPath1.clear();
  97.    mPath2.clear();
  98.    mPath1.moveTo(start_p1);
  99.    mPath2.moveTo(start_p2);
  100.    QList<QPointF> points;
  101.    points.append(start);
  102.    for (int i = 0, size = mPointList.size(); i < size; ++i) {
  103.        points.append(mPointList.at(i)->pos());
  104.    }
  105.    points.append(end);
  106.    Q_ASSERT(points.size() >= 3);
  107.    QPointF next_start_p1 = start_p1, next_start_p2 = start_p2;
  108.    for (int i = 1, size = points.size(); i < size - 1; ++i) {
  109.        QPointF temp_start = points.at(i-1), temp_point = points.at(i), temp_end = points.at(i+1);
  110.        // 计算角平分线
  111.        QLineF bisector_line = calculateAngleBisector(temp_start, temp_point, temp_end);
  112.        QLineF start_line(temp_start, temp_point);
  113.        // 计算交点
  114.        QPointF p1_bst_itst = calculateBisectorPoint(start_line, bisector_line, next_start_p1);
  115.        QPointF p2_bst_itst = calculateBisectorPoint(start_line, bisector_line, next_start_p2);
  116.        // 判断是否交叉
  117.        if (calculateLineIsIntersect(next_start_p1, p1_bst_itst, next_start_p2, p2_bst_itst)) {
  118.            // 如果交叉
  119.            mPath1.lineTo(p2_bst_itst);
  120.            mPath2.lineTo(p1_bst_itst);
  121.            next_start_p1 = p2_bst_itst;
  122.            next_start_p2 = p1_bst_itst;
  123.        } else {
  124.            mPath1.lineTo(p1_bst_itst);
  125.            mPath2.lineTo(p2_bst_itst);
  126.            next_start_p1 = p1_bst_itst;
  127.            next_start_p2 = p2_bst_itst;
  128.        }
  129.    }
  130.    QPointF end_offset = getOffset(mPointList.last()->pos(), end);
  131.    QPointF end_p1 = end + end_offset;
  132.    QPointF end_p2 = end - end_offset;
  133.    if (calculateLineIsIntersect(next_start_p1, end_p1, next_start_p2, end_p2)) {
  134.        // 如果交叉
  135.        mPath1.lineTo(end_p2);
  136.        mPath2.lineTo(end_p1);
  137.    } else {
  138.        mPath1.lineTo(end_p1);
  139.        mPath2.lineTo(end_p2);
  140.    }
  141.    QPainterPath path;
  142.    path.addPath(mPath1);
  143.    path.addPath(mPath2);
  144.    setPath(path);
  145. }
  146. void CustomPath::addPoint(CustomPoint *point)
  147. {
  148.    mPointList.append(point);
  149. }
  150. void CustomPath::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  151. {
  152.    painter->save();
  153.    painter->setPen(QPen(Qt::blue, 2.0));
  154.    painter->drawPath(mPath1);
  155.    painter->setPen(QPen(Qt::red, 2.0));
  156.    painter->drawPath(mPath2);
  157.    painter->restore();
  158. }
  159. // 计算角平分线
  160. QLineF CustomPath::calculateAngleBisector(const QPointF &start, const QPointF &mid, const QPointF &end)
  161. {
  162.    // 计算向量A和B
  163.    QPointF vectorA = start - mid;
  164.    QPointF vectorB = end - mid;
  165.    // 归一化向量A和B
  166.    qreal lengthA = std::hypot(vectorA.x(), vectorA.y());
  167.    qreal lengthB = std::hypot(vectorB.x(), vectorB.y());
  168.    QPointF unitA = vectorA / lengthA;
  169.    QPointF unitB = vectorB / lengthB;
  170.    // 计算角平分线向量
  171.    QPointF bisector = unitA + unitB;
  172.    // 如果共线则向量为零,需要使用垂线
  173.    if (bisector.isNull()) {
  174.        bisector = QPointF(-unitA.y(), unitA.x());
  175.    }
  176.    // 归一化角平分线向量
  177.    qreal lengthBisector = std::hypot(bisector.x(), bisector.y());
  178.    QPointF unitBisector = bisector / lengthBisector;
  179.    // 从中点出发,沿角平分线方向绘制一条直线
  180.    QPointF bisectorEnd = mid + unitBisector * 100; // 100为长度,可根据需要调整
  181.    QPointF bisectorEnd_n = mid - unitBisector * 100;
  182.    return QLineF(bisectorEnd_n, bisectorEnd);
  183.    //    return unitBisector;
  184. }
  185. // 计算过p点的l1的平行线与bisector_line的交点
  186. QPointF CustomPath::calculateBisectorPoint(const QLineF &l1, const QLineF &bisector_line, const QPointF &p)
  187. {
  188.    // 起点到拐点连线的向量
  189.    QPointF lp(l1.p2() - l1.p1());
  190.    qreal length = std::hypot(lp.x(), lp.y());
  191.    QPointF unit = lp / length;
  192.    // 过偏移点的平行线
  193.    QLineF line(p, p+unit*100);
  194.    // 计算交点
  195.    QPointF intersection;
  196.    QLineF::IntersectType type = line.intersects(bisector_line, &intersection);
  197.    return intersection;
  198. }
  199. // 判断是否交叉
  200. bool CustomPath::calculateLineIsIntersect(const QPointF &start1, const QPointF &end1,
  201.                                                const QPointF &start2, const QPointF &end2)
  202. {
  203.    QLineF line1(start1, end1);
  204.    QLineF line2(start2, end2);
  205.    QPointF intersection;
  206.    QLineF::IntersectType type = line1.intersects(line2, &intersection);
  207.    if (type == QLineF::BoundedIntersection && ! intersection.isNull()) {
  208.        return true;
  209.    } else {
  210.        return false;
  211.    }
  212. }
  213. CustomPoint::CustomPoint(QGraphicsItem *parent)
  214.    : QGraphicsEllipseItem(parent)
  215. {
  216.    // 设置图形为圆形
  217.    setRect(-2, -2, 4, 4);
  218.    setBrush(Qt::black);
  219.    setFlag(QGraphicsItem::ItemIsMovable, true);
  220.    setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
  221. }
  222. QVariant CustomPoint::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
  223. {
  224.    switch (change) {
  225.    case QGraphicsItem::ItemPositionHasChanged:
  226.    {
  227.        // 当拐点位置发生变化,刷新连线
  228.        if (mPathItem) {
  229.            mPathItem->updatePosition();
  230.        }
  231.    }
  232.    default:
  233.        break;
  234.    }
  235.    return QGraphicsItem::itemChange(change, value);
  236. }
  237. void CustomPoint::setPathItem(CustomPath *pathItem)
  238. {
  239.    mPathItem = pathItem;
  240. }
复制代码


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册