如何将Qt图形连线功能升级为支持多拐点和颜色区分?

摘要:本文在Qt图形框架中扩展了连线功能,实现了给连线添加多个拐点并使用不同颜色绘制的效果。该实现优化了连线的可视化效果,提升了代码可扩展性,为复杂图形编辑工具的开发提供了参考。
摘要:     本文在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<CustomPoint *>,用于多拐点的存储; 添加了QPainterPath 的两条连线,用于不同的画笔绘制; 重写paint()函数; 对应拐点列表,修改updatePosition()函数,对每个拐点进行计算,刷新连线; 添加测试代码,添加第二个拐点; 效果如下:     可见,两条线的颜色在视觉上是有互换的情况的。本人能力有限,欢迎各位交流讨论。 完整代码: mainwindow.h 点击折叠或展开代码 #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 mainwindow.cpp 点击折叠或展开代码 #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; }