摘要:本文在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; // 连线1QPainterPath mPath2; // 连线2QPointF 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_NAMESPACEclass 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; // 连线1QPainterPath mPath2; // 连线2QPointF 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_OBJECTpublic: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)
{// 设置绘制画笔,颜色黑色,笔宽为1setPen(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和BQPointF vectorA = start - mid;QPointF vectorB = end - mid;// 归一化向量A和Bqreal 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;
}