豌畔丛 发表于 2025-6-1 21:35:07

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,用于多拐点的存储;
[*]添加了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;
}

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: Qt图形连线功能升级:支持多拐点和颜色区分