Qt圖形連線功能升級:支持多拐點(diǎn)和顏色區(qū)分
摘要:本文在Qt圖形框架中擴(kuò)展了連線功能,實(shí)現(xiàn)了給連線添加多個拐點(diǎn)并使用不同顏色繪制的效果。該實(shí)現(xiàn)優(yōu)化了連線的可視化效果,提升了代碼可擴(kuò)展性,為復(fù)雜圖形編輯工具的開發(fā)提供了參考。
關(guān)鍵詞:QGraphicsPathItem、拐點(diǎn)、QPainterPath、顏色設(shè)置、Qt圖形框架、連線
完整代碼見最后。
在上一篇文章的基礎(chǔ)上繼續(xù)實(shí)現(xiàn)兩個功能:
- 給連線添加多個拐點(diǎn)
- 使用不同的顏色給連線上色
添加代碼:
// 連線類,描述連線
class CustomPath : public QGraphicsPathItem
{
public:
CustomPath(QGraphicsItem *start, QGraphicsItem *end, QGraphicsItem *parent = nullptr);
void updatePosition(); // 刷新連線
void addPoint(CustomPoint *point); // 設(shè)置拐點(diǎn)
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
private:
QGraphicsItem *mStartItem = nullptr; // 起點(diǎn)
QGraphicsItem *mEndItem = nullptr; // 終點(diǎn)
QList<CustomPoint *> mPointList; // 拐點(diǎn)列表
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);
// 記錄拐點(diǎn)的偏移點(diǎn)的位置
QPointF next_start_p1 = start_p1, next_start_p2 = start_p2;
// 每個拐點(diǎn)都只繪制前半段
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);
// 計(jì)算角平分線
QLineF bisector_line = calculateAngleBisector(temp_start, temp_point, temp_end);
QLineF start_line(temp_start, temp_point);
// 計(jì)算交點(diǎn)
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);
// 添加拐點(diǎn)圖形
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添加了拐點(diǎn)列表QList<CustomPoint *>,用于多拐點(diǎn)的存儲; -
添加了
QPainterPath的兩條連線,用于不同的畫筆繪制; -
重寫
paint()函數(shù); -
對應(yīng)拐點(diǎn)列表,修改
updatePosition()函數(shù),對每個拐點(diǎn)進(jìn)行計(jì)算,刷新連線; -
添加測試代碼,添加第二個拐點(diǎn);
效果如下:
????可見,兩條線的顏色在視覺上是有互換的情況的。本人能力有限,歡迎各位交流討論。

完整代碼:
- mainwindow.h
點(diǎn)擊折疊或展開代碼
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtWidgets>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class CustomPath;
class CustomPoint;
// 圖形類,描述起點(diǎn)和終點(diǎn)
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); // 設(shè)置拐點(diǎn)
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
private:
QGraphicsItem *mStartItem = nullptr; // 起點(diǎn)
QGraphicsItem *mEndItem = nullptr; // 終點(diǎn)
QList<CustomPoint *> mPointList; // 拐點(diǎn)列表
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);
};
// 拐點(diǎn)類
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; // 拐點(diǎn)所屬連線
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
void initGraphics();
};
#endif // MAINWINDOW_H
- mainwindow.cpp
點(diǎn)擊折疊或展開代碼
#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);
// 添加拐點(diǎn)圖形
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)
{
// 設(shè)置形狀
setRect(-5, -5, 10, 10);
// 設(shè)置顏色
setBrush(Qt::black);
// 設(shè)置可移動
setFlag(QGraphicsItem::ItemIsMovable, true);
// 設(shè)置可發(fā)送幾何變動,可在itemChange中進(jìn)行檢測
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
}
// 添加連線
void CustomItem::addPath(CustomPath *path)
{
mPathList.append(path);
}
QVariant CustomItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
switch (change) {
// 當(dāng)位置變動時,刷新連線
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)
{
// 設(shè)置繪制畫筆,顏色黑色,筆寬為1
setPen(QPen(Qt::black, 1));
}
QPointF CustomPath::getOffset(const QPointF &p1, const QPointF &p2)
{
QPointF dp = p1 - p2;
QPointF offset;
// 根據(jù)差值判斷
if (dp.x() * dp.y() >= 0) {
// 設(shè)置偏移量
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);
// 計(jì)算角平分線
QLineF bisector_line = calculateAngleBisector(temp_start, temp_point, temp_end);
QLineF start_line(temp_start, temp_point);
// 計(jì)算交點(diǎn)
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();
}
// 計(jì)算角平分線
QLineF CustomPath::calculateAngleBisector(const QPointF &start, const QPointF &mid, const QPointF &end)
{
// 計(jì)算向量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;
// 計(jì)算角平分線向量
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;
// 從中點(diǎn)出發(fā),沿角平分線方向繪制一條直線
QPointF bisectorEnd = mid + unitBisector * 100; // 100為長度,可根據(jù)需要調(diào)整
QPointF bisectorEnd_n = mid - unitBisector * 100;
return QLineF(bisectorEnd_n, bisectorEnd);
// return unitBisector;
}
// 計(jì)算過p點(diǎn)的l1的平行線與bisector_line的交點(diǎn)
QPointF CustomPath::calculateBisectorPoint(const QLineF &l1, const QLineF &bisector_line, const QPointF &p)
{
// 起點(diǎn)到拐點(diǎn)連線的向量
QPointF lp(l1.p2() - l1.p1());
qreal length = std::hypot(lp.x(), lp.y());
QPointF unit = lp / length;
// 過偏移點(diǎn)的平行線
QLineF line(p, p+unit*100);
// 計(jì)算交點(diǎn)
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)
{
// 設(shè)置圖形為圓形
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:
{
// 當(dāng)拐點(diǎn)位置發(fā)生變化,刷新連線
if (mPathItem) {
mPathItem->updatePosition();
}
}
default:
break;
}
return QGraphicsItem::itemChange(change, value);
}
void CustomPoint::setPathItem(CustomPath *pathItem)
{
mPathItem = pathItem;
}

浙公網(wǎng)安備 33010602011771號