摘要:本文在Qt图形框架中扩展了连线功能,实现了给连线添加多个拐点并使用不同颜色绘制的效果。该实现优化了连线的可视化效果,提升了代码可扩展性,为复杂图形编辑工具的开发提供了参考。
关键词:QGraphicsPathItem、拐点、QPainterPath、颜色设置、Qt图形框架、连线
完整代码见最后。
在上一篇文章的基础上继续实现两个功能:
添加代码:- // 连线类,描述连线
- class CustomPath : public QGraphicsPathItem
- {
- public:
- CustomPath(QGraphicsItem *start, QGraphicsItem *end, QGraphicsItem *parent = nullptr);
- void updatePosition(); // 刷新连线
- void addPoint(CustomPoint *point); // 设置拐点
- protected:
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
- private:
- QGraphicsItem *mStartItem = nullptr; // 起点
- QGraphicsItem *mEndItem = nullptr; // 终点
- QList<CustomPoint *> mPointList; // 拐点列表
- QPainterPath mPath1; // 连线1
- QPainterPath mPath2; // 连线2
- 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);
- };
- void CustomPath::addPoint(CustomPoint *point)
- {
- mPointList.append(point);
- }
- void CustomPath::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
- {
- painter->save();
- // 用不同的画笔绘制连线
- painter->setPen(QPen(Qt::blue, 2.0));
- painter->drawPath(mPath1);
- painter->setPen(QPen(Qt::red, 2.0));
- painter->drawPath(mPath2);
- painter->restore();
- }
- void CustomPath::updatePosition()
- {
- QPointF start = mStartItem->pos();
- QPointF end = mEndItem->pos();
- QPointF start_offset = getOffset(start, mPointList.first()->pos());
- QPointF start_p1 = start + start_offset;
- QPointF start_p2 = start - start_offset;
- mPath1.clear();
- mPath2.clear();
- mPath1.moveTo(start_p1);
- mPath2.moveTo(start_p2);
- QList<QPointF> points;
- points.append(start);
- for (int i = 0, size = mPointList.size(); i < size; ++i) {
- points.append(mPointList.at(i)->pos());
- }
- points.append(end);
- Q_ASSERT(points.size() >= 3);
- // 记录拐点的偏移点的位置
- QPointF next_start_p1 = start_p1, next_start_p2 = start_p2;
- // 每个拐点都只绘制前半段
- for (int i = 1, size = points.size(); i < size - 1; ++i) {
- QPointF temp_start = points.at(i-1), temp_point = points.at(i), temp_end = points.at(i+1);
- // 计算角平分线
- QLineF bisector_line = calculateAngleBisector(temp_start, temp_point, temp_end);
- QLineF start_line(temp_start, temp_point);
- // 计算交点
- QPointF p1_bst_itst = calculateBisectorPoint(start_line, bisector_line, next_start_p1);
- QPointF p2_bst_itst = calculateBisectorPoint(start_line, bisector_line, next_start_p2);
- // 判断是否交叉
- if (calculateLineIsIntersect(next_start_p1, p1_bst_itst, next_start_p2, p2_bst_itst)) {
- // 如果交叉
- mPath1.lineTo(p2_bst_itst);
- mPath2.lineTo(p1_bst_itst);
- next_start_p1 = p2_bst_itst;
- next_start_p2 = p1_bst_itst;
- } else {
- mPath1.lineTo(p1_bst_itst);
- mPath2.lineTo(p2_bst_itst);
- next_start_p1 = p1_bst_itst;
- next_start_p2 = p2_bst_itst;
- }
- }
- QPointF end_offset = getOffset(mPointList.last()->pos(), end);
- QPointF end_p1 = end + end_offset;
- QPointF end_p2 = end - end_offset;
- // 最后的一段
- if (calculateLineIsIntersect(next_start_p1, end_p1, next_start_p2, end_p2)) {
- // 如果交叉
- mPath1.lineTo(end_p2);
- mPath2.lineTo(end_p1);
- } else {
- mPath1.lineTo(end_p1);
- mPath2.lineTo(end_p2);
- }
- QPainterPath path;
- path.addPath(mPath1);
- path.addPath(mPath2);
- 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 *point1 = new CustomPoint(path);
- point1->setPos(100, 150);
- path->addPoint(point1);
- point1->setPathItem(path);
- CustomPoint *point2 = new CustomPoint(path);
- point2->setPos(150, 100);
- path->addPoint(point2);
- point2->setPathItem(path);
- path->updatePosition();
复制代码 在这段代码中,
- 给CustomPath添加了拐点列表QList,用于多拐点的存储;
- 添加了QPainterPath 的两条连线,用于不同的画笔绘制;
- 重写paint()函数;
- 对应拐点列表,修改updatePosition()函数,对每个拐点进行计算,刷新连线;
- 添加测试代码,添加第二个拐点;
效果如下:
可见,两条线的颜色在视觉上是有互换的情况的。本人能力有限,欢迎各位交流讨论。
完整代码:
点击折叠或展开代码- #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 addPoint(CustomPoint *point); // 设置拐点
- protected:
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
- private:
- QGraphicsItem *mStartItem = nullptr; // 起点
- QGraphicsItem *mEndItem = nullptr; // 终点
- QList<CustomPoint *> mPointList; // 拐点列表
- QPainterPath mPath1; // 连线1
- QPainterPath mPath2; // 连线2
- 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;
- }
- 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 *point1 = new CustomPoint(path);
- point1->setPos(100, 150);
- path->addPoint(point1);
- point1->setPathItem(path);
- CustomPoint *point2 = new CustomPoint(path);
- point2->setPos(150, 100);
- path->addPoint(point2);
- point2->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));
- }
- 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 = mStartItem->pos();
- QPointF end = mEndItem->pos();
- QPointF start_offset = getOffset(start, mPointList.first()->pos());
- QPointF start_p1 = start + start_offset;
- QPointF start_p2 = start - start_offset;
- mPath1.clear();
- mPath2.clear();
- mPath1.moveTo(start_p1);
- mPath2.moveTo(start_p2);
- QList<QPointF> points;
- points.append(start);
- for (int i = 0, size = mPointList.size(); i < size; ++i) {
- points.append(mPointList.at(i)->pos());
- }
- points.append(end);
- Q_ASSERT(points.size() >= 3);
- QPointF next_start_p1 = start_p1, next_start_p2 = start_p2;
- for (int i = 1, size = points.size(); i < size - 1; ++i) {
- QPointF temp_start = points.at(i-1), temp_point = points.at(i), temp_end = points.at(i+1);
- // 计算角平分线
- QLineF bisector_line = calculateAngleBisector(temp_start, temp_point, temp_end);
- QLineF start_line(temp_start, temp_point);
- // 计算交点
- QPointF p1_bst_itst = calculateBisectorPoint(start_line, bisector_line, next_start_p1);
- QPointF p2_bst_itst = calculateBisectorPoint(start_line, bisector_line, next_start_p2);
- // 判断是否交叉
- if (calculateLineIsIntersect(next_start_p1, p1_bst_itst, next_start_p2, p2_bst_itst)) {
- // 如果交叉
- mPath1.lineTo(p2_bst_itst);
- mPath2.lineTo(p1_bst_itst);
- next_start_p1 = p2_bst_itst;
- next_start_p2 = p1_bst_itst;
- } else {
- mPath1.lineTo(p1_bst_itst);
- mPath2.lineTo(p2_bst_itst);
- next_start_p1 = p1_bst_itst;
- next_start_p2 = p2_bst_itst;
- }
- }
- QPointF end_offset = getOffset(mPointList.last()->pos(), end);
- QPointF end_p1 = end + end_offset;
- QPointF end_p2 = end - end_offset;
- if (calculateLineIsIntersect(next_start_p1, end_p1, next_start_p2, end_p2)) {
- // 如果交叉
- mPath1.lineTo(end_p2);
- mPath2.lineTo(end_p1);
- } else {
- mPath1.lineTo(end_p1);
- mPath2.lineTo(end_p2);
- }
- QPainterPath path;
- path.addPath(mPath1);
- path.addPath(mPath2);
- setPath(path);
- }
- void CustomPath::addPoint(CustomPoint *point)
- {
- mPointList.append(point);
- }
- void CustomPath::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
- {
- painter->save();
- painter->setPen(QPen(Qt::blue, 2.0));
- painter->drawPath(mPath1);
- painter->setPen(QPen(Qt::red, 2.0));
- painter->drawPath(mPath2);
- painter->restore();
- }
- // 计算角平分线
- 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;
- }
- }
- 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;
- }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |