摘要:本文详细先容了基于Qt框架在QGraphicsView 中实现带有可动拐点连线的绘制方法。通过自定CustomItem和CustomPath类,结合QGraphicsItem的几何变化事故与QPainterPath的路径绘制功能,实现了动态连线的基本框架。进一步探讨了平行线偏移规则的计划与拐点交织问题的办理方案,通过角平分线计算和交织检测优化路径毗连效果。最终提出了一种支持用户拖拽拐点、自动革新连线的交互式图形方案,并展示了代码实现与效果演示,为复杂图形编辑工具的开辟提供了参考。
关键词:QGraphicsView、QGraphicsItem、QPainterPath、可动拐点、平行线偏移、角平分线、交织检测、Qt图形框架
完整代码见末了。
1、QGraphicsItem和QPainterPath的底子利用
做一点准备工作,先用一个简朴的案例,创建代码基本框架。
问题描述:
已知起点和终点,怎样绘制过两点的线段?
要求:点图形可动,连线图形可革新
办理思绪:
1、准备工作,需要创建可动的图形类CustomItem和连线类CustomPath,以便观察各种环境。
2、利用QPainterPath的moveTo()和lineTo()绘制连线。
3、在图形类CustomItem的itemChange函数中革新连线。
代码如下:- class CustomPath;
- // 图形类,描述起点和终点
- class CustomItem : public QGraphicsRectItem
- {
- public:
- CustomItem(QGraphicsItem *parent = nullptr);
- void addPath(CustomPath *path);
- protected:
- QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
- private:
- QList<CustomPath *> mPathList; // 连线列表
- };
- CustomItem::CustomItem(QGraphicsItem *parent) : QGraphicsRectItem(parent)
- {
- // 设置形状
- setRect(-5, -5, 10, 10);
- // 设置颜色
- setBrush(Qt::black);
- // 设置可移动
- setFlag(QGraphicsItem::ItemIsMovable, true);
- // 设置可发送几何变动,可在itemChange中进行检测
- setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
- }
- // 添加连线
- void CustomItem::addPath(CustomPath *path)
- {
- mPathList.append(path);
- }
- QVariant CustomItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
- {
- switch (change) {
- // 当位置变动时,刷新连线
- case QGraphicsItem::ItemPositionHasChanged:
- {
- for (int i = 0, size = mPathList.size(); i < size; ++i) {
- mPathList.at(i)->updatePosition();
- }
- }
- default:
- break;
- }
- return QGraphicsItem::itemChange(change, value);
- }
复制代码 在这段代码中,创建了图形类CustomItem,设置图形可移动,同时在移动时革新与图形相连的连线。
- // 连线类,描述连线
- class CustomPath : public QGraphicsPathItem
- {
- public:
- CustomPath(QGraphicsItem *start, QGraphicsItem *end, QGraphicsItem *parent = nullptr);
- void updatePosition(); // 刷新连线
- private:
- QGraphicsItem *mStartItem = nullptr; // 起点
- QGraphicsItem *mEndItem = nullptr; // 终点
- };
- CustomPath::CustomPath(QGraphicsItem *start, QGraphicsItem *end, QGraphicsItem *parent)
- : QGraphicsPathItem(parent), mStartItem(start), mEndItem(end)
- {
- // 设置绘制画笔,颜色黑色,笔宽为1
- setPen(QPen(Qt::black, 1));
- }
- // 刷新连线
- void CustomPath::updatePosition()
- {
- // 获取两端的位置
- QPointF start_pos = mStartItem->pos();
- QPointF end_pos = mEndItem->pos();
- // 绘制连线
- QPainterPath path;
- path.moveTo(start_pos);
- path.lineTo(end_pos);
- // 设置连线
- setPath(path);
- }
复制代码 在这段代码中,创建了连线类CustomPath,主要作用是革新连线updatePosition函数。
- // 创建画布
- QGraphicsScene *scene = new QGraphicsScene(this);
- ui->graphicsView->setScene(scene);
- // 创建起点
- CustomItem *item_start = new CustomItem;
- item_start->setPos(100, 100);
- scene->addItem(item_start);
- // 创建终点
- CustomItem *item_end = new CustomItem;
- item_end->setPos(200, 200);
- scene->addItem(item_end);
- // 创建连线
- CustomPath *path = new CustomPath(item_start, item_end);
- item_start->addPath(path);
- item_end->addPath(path);
- path->updatePosition();
- scene->addItem(path);
复制代码 在这段代码中,创建了点A和点B,设置它们的位置,创建了连线并革新。
效果如下:
2、怎样创建平行线
现在要在同一个连线类CustomPath中绘制两条连线,引出偏移规则的确定方法。
问题描述:
现有点A和点B,分别在其附近找两点(点A1A2,点B1B2),怎样绘制两条平行线?
办理思绪:
只需确定偏移规则。好比这两点分别位于点的左右或者上下两侧。这里设置为左右偏移5个像素点。
代码如下:- void CustomPath::updatePosition()
- {
- QPointF start_pos = mStartItem->pos();
- QPointF end_pos = mEndItem->pos();
- // 起点左右偏移
- QPointF start_p1 = start_pos + QPointF(-5, 0);
- QPointF start_p2 = start_pos + QPointF(5, 0);
- // 终点左右偏移
- QPointF end_p1 = end_pos + QPointF(-5, 0);
- QPointF end_p2 = end_pos + QPointF(5, 0);
- // 两次连线
- QPainterPath path;
- path.moveTo(start_p1);
- path.lineTo(end_p1);
- path.moveTo(start_p2);
- path.lineTo(end_p2);
- setPath(path);
- }
复制代码 这段代码中,将起点和终点分别左右偏移五个像素,然后连线,利用两次moveTo()和lineTo()。
效果如下:
3、偏移规则的问题
问题描述:
可以发现(如图),当移动两个点位于同一水平线时,连线会发生重叠。
办理思绪:
这是由于偏移规则的缺陷。无论是上下偏移还是左右偏移或者其他的偏移,都会产生这种环境。
那么,这两个偏移点必须根据环境发生变化。
确定新的偏移规则:斜向偏移,直线如果斜向右上(或者斜向左下),则偏移点为(5,5)和(-5,-5);直线如果斜向左上(或者斜向右下),则偏移点为(5,-5)和(-5,5)。
代码如下:- // 偏移规则
- QPointF CustomPath::getOffset(const QPointF &p1, const QPointF &p2)
- {
- QPointF dp = p1 - p2;
- QPointF offset;
- // 根据差值判断
- if (dp.x() * dp.y() >= 0) {
- // 设置偏移量
- offset = QPointF(-5, 5);
- } else {
- offset = QPointF(5, 5);
- }
- return offset;
- }
- void CustomPath::updatePosition()
- {
- QPointF start_pos = mStartItem->pos();
- QPointF end_pos = mEndItem->pos();
- QPointF offset = getOffset(start_pos, end_pos);
- // 起点和终点偏移
- QPointF start_p1 = start_pos + offset;
- QPointF start_p2 = start_pos - offset;
- QPointF end_p1 = end_pos + offset;
- QPointF end_p2 = end_pos - offset;
- QPainterPath path;
- path.moveTo(start_p1);
- path.lineTo(end_p1);
- path.moveTo(start_p2);
- path.lineTo(end_p2);
- setPath(path);
- }
复制代码 在这段代码中,利用了两点xy轴的差值举行判断斜向方向,同时设置了偏移量。
效果如下:
4、带有拐点的连线
问题描述:
重新从办理简朴的问题开始:现在有点X,需要创建从A->X->B的单条连线,怎样实现?
办理思绪:
确定拐点位置,插入利用lineTo()即可。
代码如下:- // 拐点类
- class CustomPoint : public QGraphicsEllipseItem
- {
- public:
- CustomPoint(QGraphicsItem *parent = nullptr);
- void setPathItem(CustomPath *pathItem);
- protected:
- QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
- private:
- CustomPath *mPathItem = nullptr; // 拐点所属连线
- };
- CustomPoint::CustomPoint(QGraphicsItem *parent)
- : QGraphicsEllipseItem(parent)
- {
- // 设置图形为圆形
- setRect(-2, -2, 4, 4);
- setBrush(Qt::black);
- setFlag(QGraphicsItem::ItemIsMovable, true);
- setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
- }
- QVariant CustomPoint::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
- {
- switch (change) {
- case QGraphicsItem::ItemPositionHasChanged:
- {
- // 当拐点位置发生变化,刷新连线
- if (mPathItem) {
- mPathItem->updatePosition();
- }
- }
- default:
- break;
- }
- return QGraphicsItem::itemChange(change, value);
- }
- void CustomPoint::setPathItem(CustomPath *pathItem)
- {
- mPathItem = pathItem;
- }
复制代码 在这段代码中,创建了拐点类CustomPoint,设置它的形状、笔刷、可移动属性;当拐点位置发生变化时,刷线连线。- // 对部分代码进行修改
- class CustomPath : public QGraphicsPathItem
- {
- public:
- CustomPath(QGraphicsItem *start, QGraphicsItem *end, QGraphicsItem *parent = nullptr);
- void updatePosition();
- void setPoint(CustomPoint *point); // 设置拐点
- private:
- QGraphicsItem *mStartItem = nullptr;
- QGraphicsItem *mEndItem = nullptr;
- CustomPoint *mPoint = nullptr; // 拐点
- };
- void CustomPath::setPoint(CustomPoint *point)
- {
- mPoint = point;
- }
- void CustomPath::updatePosition()
- {
- QPointF start_pos = mStartItem->pos();
- QPointF end_pos = mEndItem->pos();
- QPointF point_pos = mPoint->pos();
- QPainterPath path;
- path.moveTo(start_pos);
- path.lineTo(point_pos); // 从起点->拐点->终点
- path.lineTo(end_pos);
- setPath(path);
- }
- // 修改使用代码
- QGraphicsScene *scene = new QGraphicsScene(this);
- ui->graphicsView->setScene(scene);
- // 创建起点
- CustomItem *item_start = new CustomItem;
- item_start->setPos(100, 100);
- scene->addItem(item_start);
- // 创建终点
- CustomItem *item_end = new CustomItem;
- item_end->setPos(200, 200);
- scene->addItem(item_end);
- // 创建连线
- CustomPath *path = new CustomPath(item_start, item_end);
- item_start->addPath(path);
- item_end->addPath(path);
- scene->addItem(path);
- // 添加拐点图形
- CustomPoint *point = new CustomPoint(path);
- point->setPos(100, 150);
- path->setPoint(point); // 设置拐点
- point->setPathItem(path); // 设置连线
- path->updatePosition();
复制代码 在这段代码中,对部分代码举行修改:在连线类中添加了拐点成员,在革新连线函数中连线到拐点,在实际利用代码中添加了拐点图形,在末了革新图形连线。
效果如下:
5、带有拐点的两条平行线
问题描述:
那么怎样绘制带有拐点的两条连线呢?
办理思绪:
直接将偏移规则应用到拐点位置,根据起点和拐点位置(或者拐点和终点位置)确定偏移,会怎样?
代码如下:- void CustomPath::updatePosition()
- {
- QPointF start_pos = mStartItem->pos();
- QPointF end_pos = mEndItem->pos();
- QPointF point_pos = mPoint->pos();
- // 计算偏移
- QPointF offset_sp = getOffset(start_pos, point_pos);
- QPointF offset_pe = getOffset(point_pos, end_pos);
- // 起点偏移
- QPointF start_p1 = start_pos + offset_sp;
- QPointF start_p2 = start_pos - offset_sp;
- // 拐点对起点偏移
- QPointF point_ps1 = point_pos + offset_sp;
- QPointF point_ps2 = point_pos - offset_sp;
- // 拐点对终点偏移
- // QPointF point_pe1 = point_pos + offset_pe;
- // QPointF point_pe2 = point_pos - offset_pe;
- // 终点偏移
- QPointF end_p1 = end_pos + offset_pe;
- QPointF end_p2 = end_pos - offset_pe;
- // 使用两个
- QPainterPath path;
- path.moveTo(start_p1);
- path.lineTo(point_ps1);
- // path.lineTo(point_pe1);
- path.lineTo(end_p1);
- path.moveTo(start_p2);
- path.lineTo(point_ps2);
- // path.lineTo(point_pe2);
- path.lineTo(end_p2);
- // 使用四个
- // {
- // path.moveTo(start_p1);
- // path.lineTo(point_ps1);
- // path.moveTo(point_pe1);
- // path.lineTo(end_p1);
- // path.moveTo(start_p2);
- // path.lineTo(point_ps2);
- // path.moveTo(point_pe2);
- // path.lineTo(end_p2);
- // }
- setPath(path);
- }
复制代码 在调整代码的过程中,就会发现,在拐点对起点和拐点对终点应用偏移规则时,会产生四个偏移点。
只利用其中两个会发生什么环境?利用四个会发生什么环境?
效果如下:
利用两个的环境:可以看到连线产生了交错,而且某些角度环境下发生重合。
利用四个的环境:可以看到连线产生交错,而且某些角度下毗连点错开。
6、拐点处的偏移问题
问题描述:
如果对拐点也应用偏移规则,利用两个点,会产生交错的环境;利用四个点,不但会产生交错,而且会断开。
办理思绪:
拐点处的偏移点应该只可以有两个;其偏移点只对一个点应用;考虑利用角平分线。
方案就是:起点-拐点-终点,形成一个角度,计算出角平分线;过起点的两个偏移点,作起点和拐点连线的两条平行线;这两条平行线和角平分线的交点,作为拐点处的偏移点;然后毗连拐点处偏移点和终点偏移点,形成连线。
代码如下:- // 计算角平分线
- QLineF CustomPath::calculateAngleBisector(const QPointF &start, const QPointF &mid, const QPointF &end)
- {
- // 计算向量A和B
- QPointF vectorA = start - mid;
- QPointF vectorB = end - mid;
- // 归一化向量A和B
- qreal lengthA = std::hypot(vectorA.x(), vectorA.y());
- qreal lengthB = std::hypot(vectorB.x(), vectorB.y());
- QPointF unitA = vectorA / lengthA;
- QPointF unitB = vectorB / lengthB;
- // 计算角平分线向量
- QPointF bisector = unitA + unitB;
- // 如果共线则向量为零,需要使用垂线
- if (bisector.isNull()) {
- bisector = QPointF(-unitA.y(), unitA.x());
- }
- // 归一化角平分线向量
- qreal lengthBisector = std::hypot(bisector.x(), bisector.y());
- QPointF unitBisector = bisector / lengthBisector;
- // 从中点出发,沿角平分线方向绘制一条直线
- QPointF bisectorEnd = mid + unitBisector * 100; // 100为长度,可根据需要调整
- QPointF bisectorEnd_n = mid - unitBisector * 100;
- return QLineF(bisectorEnd_n, bisectorEnd);
- // return unitBisector;
- }
- // 计算过p点的l1的平行线与bisector_line的交点
- QPointF CustomPath::calculateBisectorPoint(const QLineF &l1, const QLineF &bisector_line, const QPointF &p)
- {
- // 起点到拐点连线的向量
- QPointF lp(l1.p2() - l1.p1());
- qreal length = std::hypot(lp.x(), lp.y());
- QPointF unit = lp / length;
- // 过偏移点的平行线
- QLineF line(p, p+unit*100);
- // 计算交点
- QPointF intersection;
- QLineF::IntersectType type = line.intersects(bisector_line, &intersection);
- return intersection;
- }
- void CustomPath::updatePosition()
- {
- QPointF start_pos = mStartItem->pos();
- QPointF end_pos = mEndItem->pos();
- QPointF point_pos = mPoint->pos();
- // 计算偏移
- QPointF offset_sp = getOffset(start_pos, point_pos);
- QPointF offset_pe = getOffset(point_pos, end_pos);
- // 起点偏移
- QPointF start_p1 = start_pos + offset_sp;
- QPointF start_p2 = start_pos - offset_sp;
- // 终点偏移
- QPointF end_p1 = end_pos + offset_pe;
- QPointF end_p2 = end_pos - offset_pe;
- // 计算角平分线
- QLineF bisector_line = calculateAngleBisector(start_pos, point_pos, end_pos);
- QLineF start_line(start_pos, point_pos);
- // 计算交点
- QPointF p1_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p1);
- QPointF p2_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p2);
- // 连线
- QPainterPath path;
- path.moveTo(start_p1);
- path.lineTo(p1_bst_itst);
- path.lineTo(end_p1);
- path.moveTo(start_p2);
- path.lineTo(p2_bst_itst);
- path.lineTo(end_p2);
- setPath(path);
- }
复制代码 在这段代码中,计算起点-拐点-终点形成角度的角平分线,考虑三点共线环境下,利用垂线向量;然后有起点到拐点的连线,过两起点偏移点,作平行线,并得到和角平分线的交点;从交点连线到终点偏移点。
效果如图:
可见在拐点和终点的两条连线发生了交织,继续美满。
7、后半段交织问题
问题描述:
偏移点并不总是对应的,拐点到终点的连线发生了交织。
办理思绪:
判断后半段是否交织,如果交织,则互换偏移点。
代码如下:- // 判断是否交叉
- bool CustomPath::calculateLineIsIntersect(const QPointF &start1, const QPointF &end1,
- const QPointF &start2, const QPointF &end2)
- {
- QLineF line1(start1, end1);
- QLineF line2(start2, end2);
- QPointF intersection;
- QLineF::IntersectType type = line1.intersects(line2, &intersection);
- if (type == QLineF::BoundedIntersection && ! intersection.isNull()) {
- return true;
- } else {
- return false;
- }
- }
- void CustomPath::updatePosition()
- {
- QPointF start_pos = mStartItem->pos();
- QPointF end_pos = mEndItem->pos();
- QPointF point_pos = mPoint->pos();
- QPointF offset_sp = getOffset(start_pos, point_pos);
- QPointF offset_pe = getOffset(point_pos, end_pos);
- QPointF start_p1 = start_pos + offset_sp;
- QPointF start_p2 = start_pos - offset_sp;
- QPointF end_p1 = end_pos + offset_pe;
- QPointF end_p2 = end_pos - offset_pe;
- // 计算角平分线
- QLineF bisector_line = calculateAngleBisector(start_pos, point_pos, end_pos);
- QLineF start_line(start_pos, point_pos);
- // 计算交点
- QPointF p1_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p1);
- QPointF p2_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p2);
- QPainterPath path;
- // 前半段
- path.moveTo(start_p1);
- path.lineTo(p1_bst_itst);
- path.moveTo(start_p2);
- path.lineTo(p2_bst_itst);
- // 后半段,判断是否交叉
- if (calculateLineIsIntersect(end_p1, p1_bst_itst, end_p2, p2_bst_itst)) {
- // 如果交叉
- path.moveTo(p1_bst_itst);
- path.lineTo(end_p2);
- path.moveTo(p2_bst_itst);
- path.lineTo(end_p1);
- } else {
- path.moveTo(p1_bst_itst);
- path.lineTo(end_p1);
- path.moveTo(p2_bst_itst);
- path.lineTo(end_p2);
- }
- setPath(path);
- }
复制代码 在这段代码中,修改了绘制连线的顺序,先绘制前半段,再绘制后半段;如果后半段发生交织,则互换末了的终点偏移点。
效果如下:
可见当形成的角度极小的时间,拐点处会非常尖锐,对这个问题我没有很好的办法。还好拐点是可以移动的。如果你有想法,接待共同讨论。
总结:
本文系统性地办理了在Qt图形视图中绘制动态连线的技术难点。首先,通过继承QGraphicsItem实现可拖拽的图形项CustomItem,利用itemChange事故触发连线革新,确保了图形与路径的实时联动。其次,引入CustomPath类管理路径绘制,通过QPainterPath机动构建线段与拐点毗连逻辑。针对平行线偏移问题,提出基于斜向偏移与角平分线的动态调整策略,有用制止了路径重叠与错位。然而,在非常角度下拐点处仍可能因偏移计算产生尖锐毗连,需进一步优化算法或引入平滑曲线处理。
完整代码:
点击折叠或睁开代码- #ifndef MAINWINDOW_H
- #define MAINWINDOW_H
- #include <QMainWindow>
- #include <QtWidgets>
- QT_BEGIN_NAMESPACE
- namespace Ui { class MainWindow; }
- QT_END_NAMESPACE
- class CustomPath;
- class CustomPoint;
- // 图形类,描述起点和终点
- class CustomItem : public QGraphicsRectItem
- {
- public:
- CustomItem(QGraphicsItem *parent = nullptr);
- void addPath(CustomPath *path);
- protected:
- QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
- private:
- QList<CustomPath *> mPathList; // 连线列表
- };
- // 连线类,描述连线
- class CustomPath : public QGraphicsPathItem
- {
- public:
- CustomPath(QGraphicsItem *start, QGraphicsItem *end, QGraphicsItem *parent = nullptr);
- void updatePosition(); // 刷新连线
- void setPoint(CustomPoint *point); // 设置拐点
- private:
- QGraphicsItem *mStartItem = nullptr; // 起点
- QGraphicsItem *mEndItem = nullptr; // 终点
- CustomPoint *mPoint = nullptr; // 拐点
- QPointF getOffset(const QPointF &p1, const QPointF &p2);
- QLineF calculateAngleBisector(const QPointF& start, const QPointF& mid, const QPointF& end);
- QPointF calculateBisectorPoint(const QLineF &l1, const QLineF &bisector_line, const QPointF &p);
- bool calculateLineIsIntersect(const QPointF &start1, const QPointF &end1, const QPointF &start2, const QPointF &end2);
- };
- // 拐点类
- class CustomPoint : public QGraphicsEllipseItem
- {
- public:
- CustomPoint(QGraphicsItem *parent = nullptr);
- void setPathItem(CustomPath *pathItem);
- protected:
- QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
- private:
- CustomPath *mPathItem = nullptr; // 拐点所属连线
- };
- class MainWindow : public QMainWindow
- {
- Q_OBJECT
- public:
- MainWindow(QWidget *parent = nullptr);
- ~MainWindow();
- private:
- Ui::MainWindow *ui;
- void initGraphics();
- };
- #endif // MAINWINDOW_H
复制代码
点击折叠或睁开代码- #include "mainwindow.h"
- #include "ui_mainwindow.h"
- MainWindow::MainWindow(QWidget *parent)
- : QMainWindow(parent)
- , ui(new Ui::MainWindow)
- {
- ui->setupUi(this);
- initGraphics();
- }
- MainWindow::~MainWindow()
- {
- delete ui;
- }
- // 问题1、2、3
- //void MainWindow::initGraphics()
- //{
- // // 创建画布
- // QGraphicsScene *scene = new QGraphicsScene(this);
- // ui->graphicsView->setScene(scene);
- // // 创建起点
- // CustomItem *item_start = new CustomItem;
- // item_start->setPos(100, 100);
- // scene->addItem(item_start);
- // // 创建终点
- // CustomItem *item_end = new CustomItem;
- // item_end->setPos(200, 200);
- // scene->addItem(item_end);
- // // 创建连线
- // CustomPath *path = new CustomPath(item_start, item_end);
- // item_start->addPath(path);
- // item_end->addPath(path);
- // scene->addItem(path);
- // path->updatePosition();
- //}
- // 问题4、5
- void MainWindow::initGraphics()
- {
- QGraphicsScene *scene = new QGraphicsScene(this);
- ui->graphicsView->setScene(scene);
- CustomItem *item_start = new CustomItem;
- item_start->setPos(100, 100);
- scene->addItem(item_start);
- CustomItem *item_end = new CustomItem;
- item_end->setPos(200, 200);
- scene->addItem(item_end);
- CustomPath *path = new CustomPath(item_start, item_end);
- item_start->addPath(path);
- item_end->addPath(path);
- scene->addItem(path);
- // 添加拐点图形
- CustomPoint *point = new CustomPoint(path);
- point->setPos(100, 150);
- path->setPoint(point);
- point->setPathItem(path);
- path->updatePosition();
- }
- CustomItem::CustomItem(QGraphicsItem *parent) : QGraphicsRectItem(parent)
- {
- // 设置形状
- setRect(-5, -5, 10, 10);
- // 设置颜色
- setBrush(Qt::black);
- // 设置可移动
- setFlag(QGraphicsItem::ItemIsMovable, true);
- // 设置可发送几何变动,可在itemChange中进行检测
- setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
- }
- // 添加连线
- void CustomItem::addPath(CustomPath *path)
- {
- mPathList.append(path);
- }
- QVariant CustomItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
- {
- switch (change) {
- // 当位置变动时,刷新连线
- case QGraphicsItem::ItemPositionHasChanged:
- {
- for (int i = 0, size = mPathList.size(); i < size; ++i) {
- mPathList.at(i)->updatePosition();
- }
- }
- default:
- break;
- }
- return QGraphicsItem::itemChange(change, value);
- }
- CustomPath::CustomPath(QGraphicsItem *start, QGraphicsItem *end, QGraphicsItem *parent)
- : QGraphicsPathItem(parent), mStartItem(start), mEndItem(end)
- {
- // 设置绘制画笔,颜色黑色,笔宽为1
- setPen(QPen(Qt::black, 1));
- }
- // 问题1
- //void CustomPath::updatePosition()
- //{
- // // 获取两端的位置
- // QPointF start_pos = mStartItem->pos();
- // QPointF end_pos = mEndItem->pos();
- // // 绘制连线
- // QPainterPath path;
- // path.moveTo(start_pos);
- // path.lineTo(end_pos);
- // // 设置连线
- // setPath(path);
- //}
- // 问题2
- //void CustomPath::updatePosition()
- //{
- // QPointF start_pos = mStartItem->pos();
- // QPointF end_pos = mEndItem->pos();
- // // 起点左右偏移
- // QPointF start_p1 = start_pos + QPointF(-5, 0);
- // QPointF start_p2 = start_pos + QPointF(5, 0);
- // // 终点左右偏移
- // QPointF end_p1 = end_pos + QPointF(-5, 0);
- // QPointF end_p2 = end_pos + QPointF(5, 0);
- // // 两次连线
- // QPainterPath path;
- // path.moveTo(start_p1);
- // path.lineTo(end_p1);
- // path.moveTo(start_p2);
- // path.lineTo(end_p2);
- // setPath(path);
- //}
- QPointF CustomPath::getOffset(const QPointF &p1, const QPointF &p2)
- {
- QPointF dp = p1 - p2;
- QPointF offset;
- // 根据差值判断
- if (dp.x() * dp.y() >= 0) {
- // 设置偏移量
- offset = QPointF(-5, 5);
- } else {
- offset = QPointF(5, 5);
- }
- return offset;
- }
- // 问题3
- //void CustomPath::updatePosition()
- //{
- // QPointF start_pos = mStartItem->pos();
- // QPointF end_pos = mEndItem->pos();
- // QPointF offset = getOffset(start_pos, end_pos);
- // // 起点和终点偏移
- // QPointF start_p1 = start_pos + offset;
- // QPointF start_p2 = start_pos - offset;
- // QPointF end_p1 = end_pos + offset;
- // QPointF end_p2 = end_pos - offset;
- // QPainterPath path;
- // path.moveTo(start_p1);
- // path.lineTo(end_p1);
- // path.moveTo(start_p2);
- // path.lineTo(end_p2);
- // setPath(path);
- //}
- // 问题4
- //void CustomPath::updatePosition()
- //{
- // QPointF start_pos = mStartItem->pos();
- // QPointF end_pos = mEndItem->pos();
- // QPointF point_pos = mPoint->pos();
- // QPainterPath path;
- // path.moveTo(start_pos);
- // path.lineTo(point_pos); // 从起点->拐点->终点
- // path.lineTo(end_pos);
- // setPath(path);
- //}
- // 问题5
- //void CustomPath::updatePosition()
- //{
- // QPointF start_pos = mStartItem->pos();
- // QPointF end_pos = mEndItem->pos();
- // QPointF point_pos = mPoint->pos();
- // // 计算偏移
- // QPointF offset_sp = getOffset(start_pos, point_pos);
- // QPointF offset_pe = getOffset(point_pos, end_pos);
- // // 起点偏移
- // QPointF start_p1 = start_pos + offset_sp;
- // QPointF start_p2 = start_pos - offset_sp;
- // // 拐点对起点偏移
- // QPointF point_ps1 = point_pos + offset_sp;
- // QPointF point_ps2 = point_pos - offset_sp;
- // // 拐点对终点偏移
- // QPointF point_pe1 = point_pos + offset_pe;
- // QPointF point_pe2 = point_pos - offset_pe;
- // // 终点偏移
- // QPointF end_p1 = end_pos + offset_pe;
- // QPointF end_p2 = end_pos - offset_pe;
- // // 使用两个
- // QPainterPath path;
- // path.moveTo(start_p1);
- // path.lineTo(point_ps1);
- //// path.lineTo(point_pe1);
- // path.lineTo(end_p1);
- // path.moveTo(start_p2);
- // path.lineTo(point_ps2);
- //// path.lineTo(point_pe2);
- // path.lineTo(end_p2);
- // // 使用四个
- //// {
- //// path.moveTo(start_p1);
- //// path.lineTo(point_ps1);
- //// path.moveTo(point_pe1);
- //// path.lineTo(end_p1);
- //// path.moveTo(start_p2);
- //// path.lineTo(point_ps2);
- //// path.moveTo(point_pe2);
- //// path.lineTo(end_p2);
- //// }
- // setPath(path);
- //}
- // 问题6
- //void CustomPath::updatePosition()
- //{
- // QPointF start_pos = mStartItem->pos();
- // QPointF end_pos = mEndItem->pos();
- // QPointF point_pos = mPoint->pos();
- // QPointF offset_sp = getOffset(start_pos, point_pos);
- // QPointF offset_pe = getOffset(point_pos, end_pos);
- // QPointF start_p1 = start_pos + offset_sp;
- // QPointF start_p2 = start_pos - offset_sp;
- // QPointF end_p1 = end_pos + offset_pe;
- // QPointF end_p2 = end_pos - offset_pe;
- // // 计算角平分线
- // QLineF bisector_line = calculateAngleBisector(start_pos, point_pos, end_pos);
- // QLineF start_line(start_pos, point_pos);
- // // 计算交点
- // QPointF p1_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p1);
- // QPointF p2_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p2);
- // QPainterPath path;
- // path.moveTo(start_p1);
- // path.lineTo(p1_bst_itst);
- // path.lineTo(end_p1);
- // path.moveTo(start_p2);
- // path.lineTo(p2_bst_itst);
- // path.lineTo(end_p2);
- // setPath(path);
- //}
- // 问题7
- void CustomPath::updatePosition()
- {
- QPointF start_pos = mStartItem->pos();
- QPointF end_pos = mEndItem->pos();
- QPointF point_pos = mPoint->pos();
- QPointF offset_sp = getOffset(start_pos, point_pos);
- QPointF offset_pe = getOffset(point_pos, end_pos);
- QPointF start_p1 = start_pos + offset_sp;
- QPointF start_p2 = start_pos - offset_sp;
- QPointF end_p1 = end_pos + offset_pe;
- QPointF end_p2 = end_pos - offset_pe;
- // 计算角平分线
- QLineF bisector_line = calculateAngleBisector(start_pos, point_pos, end_pos);
- QLineF start_line(start_pos, point_pos);
- // 计算交点
- QPointF p1_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p1);
- QPointF p2_bst_itst = calculateBisectorPoint(start_line, bisector_line, start_p2);
- QPainterPath path;
- // 前半段
- path.moveTo(start_p1);
- path.lineTo(p1_bst_itst);
- path.moveTo(start_p2);
- path.lineTo(p2_bst_itst);
- // 后半段,判断是否交叉
- if (calculateLineIsIntersect(end_p1, p1_bst_itst, end_p2, p2_bst_itst)) {
- // 如果交叉
- path.moveTo(p1_bst_itst);
- path.lineTo(end_p2);
- path.moveTo(p2_bst_itst);
- path.lineTo(end_p1);
- } else {
- path.moveTo(p1_bst_itst);
- path.lineTo(end_p1);
- path.moveTo(p2_bst_itst);
- path.lineTo(end_p2);
- }
- setPath(path);
- }
- // 计算角平分线
- QLineF CustomPath::calculateAngleBisector(const QPointF &start, const QPointF &mid, const QPointF &end)
- {
- // 计算向量A和B
- QPointF vectorA = start - mid;
- QPointF vectorB = end - mid;
- // 归一化向量A和B
- qreal lengthA = std::hypot(vectorA.x(), vectorA.y());
- qreal lengthB = std::hypot(vectorB.x(), vectorB.y());
- QPointF unitA = vectorA / lengthA;
- QPointF unitB = vectorB / lengthB;
- // 计算角平分线向量
- QPointF bisector = unitA + unitB;
- // 如果共线则向量为零,需要使用垂线
- if (bisector.isNull()) {
- bisector = QPointF(-unitA.y(), unitA.x());
- }
- // 归一化角平分线向量
- qreal lengthBisector = std::hypot(bisector.x(), bisector.y());
- QPointF unitBisector = bisector / lengthBisector;
- // 从中点出发,沿角平分线方向绘制一条直线
- QPointF bisectorEnd = mid + unitBisector * 100; // 100为长度,可根据需要调整
- QPointF bisectorEnd_n = mid - unitBisector * 100;
- return QLineF(bisectorEnd_n, bisectorEnd);
- // return unitBisector;
- }
- // 计算过p点的l1的平行线与bisector_line的交点
- QPointF CustomPath::calculateBisectorPoint(const QLineF &l1, const QLineF &bisector_line, const QPointF &p)
- {
- // 起点到拐点连线的向量
- QPointF lp(l1.p2() - l1.p1());
- qreal length = std::hypot(lp.x(), lp.y());
- QPointF unit = lp / length;
- // 过偏移点的平行线
- QLineF line(p, p+unit*100);
- // 计算交点
- QPointF intersection;
- QLineF::IntersectType type = line.intersects(bisector_line, &intersection);
- return intersection;
- }
- // 判断是否交叉
- bool CustomPath::calculateLineIsIntersect(const QPointF &start1, const QPointF &end1,
- const QPointF &start2, const QPointF &end2)
- {
- QLineF line1(start1, end1);
- QLineF line2(start2, end2);
- QPointF intersection;
- QLineF::IntersectType type = line1.intersects(line2, &intersection);
- if (type == QLineF::BoundedIntersection && ! intersection.isNull()) {
- return true;
- } else {
- return false;
- }
- }
- void CustomPath::setPoint(CustomPoint *point)
- {
- mPoint = point;
- }
- CustomPoint::CustomPoint(QGraphicsItem *parent)
- : QGraphicsEllipseItem(parent)
- {
- // 设置图形为圆形
- setRect(-2, -2, 4, 4);
- setBrush(Qt::black);
- setFlag(QGraphicsItem::ItemIsMovable, true);
- setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
- }
- QVariant CustomPoint::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
- {
- switch (change) {
- case QGraphicsItem::ItemPositionHasChanged:
- {
- // 当拐点位置发生变化,刷新连线
- if (mPathItem) {
- mPathItem->updatePosition();
- }
- }
- default:
- break;
- }
- return QGraphicsItem::itemChange(change, value);
- }
- void CustomPoint::setPathItem(CustomPath *pathItem)
- {
- mPathItem = pathItem;
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |