Qt图形连线功能升级:支持多拐点和颜色区分

news/2025/3/4 6:15:51/文章来源:https://www.cnblogs.com/wsry/p/18746630

摘要:本文在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()函数,对每个拐点进行计算,刷新连线;

  • 添加测试代码,添加第二个拐点;


效果如下:
可见,两条线的颜色在视觉上是有互换的情况的。本人能力有限,欢迎各位讨论。
image


完整代码:

  • 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/892347.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《历史代码分析》1、接口安全校验-拦截器的使用

1、接口安全校验-拦截器的使用 ​​ 本系列《历史代码分析》为工作中遇到具有代表性的代码,已做脱敏处理。今天我们讲一下接口安全检验,使用到Spring中的拦截器。 请先看下面代码: package tech.xueyao.filter.interceptor;import tech.xueyao.contant.properties.SystemPro…

Anaconda安装指南(conda 不是内部或外部命令,也不是可运行的程序 或批处理文件)

原文链接:https://zhuanlan.zhihu.com/p/101434455 第一步:附上软件下载链接,自行下载 以下是Anaconda的下载链接及相关信息: 官方下载链接Anaconda官网下载地址:https://www.anaconda.com/products/distribution。Anaconda中文官网下载地址:https://www.anacondacn.com/…

docker-compose本地部署前后端分离的项目

本文使用docker-compose进行容器化部署前后端分离的项目(前端vue、后端springboot),部署的虚拟机是centOS系统1.准备打包项目 使用maven打包springboot项目为.jar文件得到springboot-0.0.1-SNAPSHOT.jar打包vue项目 npm install -g @vue/cli安装Vue CLI 在项目根目录下,运行…

使用云效实现流水线部署前后端分离的项目

使用云效实现流水线部署前后端分离的项目1.流水线远程自动化部署 1.1核心代码准备 使用git add .,git commit,git push将本地部署项目中的文档上传到远端仓库(新建的docker12)代码已上传成功修改Dockerfile有关镜像部分,使用阿里云容器镜像服务制品中心的base镜像。 jdk镜…

2.1 TCP/IP网络模型

不同设备的进程间通信,需要网络通信。 又设备多样,需要兼容,协商出来了网络协议。 应用层:专注为用户提供应用功能,如HTTP、FTP、Telnet、DNS等 工作在 用户态。 传输层:为应用层提供网络支持。(数据传输媒介) 传输协议:TCP和UDP。 网络层:实际的传输功能。 常用IP协…

关于flex布局

1.flex初探 简单示例 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title><style>.menu {border: 1px solid red;width: 500px;display: flex;}.menu .item{width: 45px;heigh…

构建窗体--java进阶day03

1.窗体对象Jframe 要创建窗体就需要Jframe对象,窗体创建好不会自己显示,还需要我们自己写一段代码让其显示2.设置窗体可见--setVisible(true) 该方法用于显示窗体3.窗体大小调节--setSize() 该方法用于调节窗体的大小,如下图宽(左到右)500,长(上到下)8004.修改窗体…

私藏!程序员必备的 6 款画图工具,工作效率直线飙升!

宝子们,日常工作里,画图可是个高频需求,不管是梳理思路、设计架构,还是给团队讲解方案,都离不开各种图形。今天我就把私藏多年的6款画图工具分享给大家,每一款都超实用,能帮咱高效搞定各类图形绘制工作! 一、Draw.io:功能强大的绘图宝藏 Draw.io堪称免费绘图工具里的…

[Webpack] 打包优化 一

代码准备 由于需要演示webpack的一些优化处理,因此代码提前做了一些处理,引入了vue-router路由,创建了几个view页面 引入了element-plus,axios,echarts,vue-echarts,@vueuse等组件丰富页面展示 引入了unplugin-vue-components,unplugin-auto-import等处理element-plus的…

[Esbuild] 自定义插件

esbuild自定义插件 插件开发其实就是基于原有的体系结构中进行扩展和自定义。 Esbuild 插件也不例外,通过 Esbuild 插件我们可以扩展 Esbuild 原有的路径解析、模块加载等方面的能力,并在 Esbuild 的构建过程中执行一系列自定义的逻辑。 esbuild 插件是一个带有name和setup函…

ASP.NET Core 3框架揭秘 10

IHostBuilder  IHost  IHostService  IHostApplicationLifetimeWebApplication ==> createbuilder创建了一个新的new WebApplicationBuilder。 WebApplicationBuilder里的初始话方法中,首先会创建一个HostApplicationBuilder对象,HostApplicationBuilder主要用于表示…

Vitepress+EdgeOne Pages快速迁移旧网站内容

本文详细记录使用Vitepress+腾讯云EdgeOne Pages迁移旧网站内容的过程。Vitepress+EdgeOne Pages快速迁移旧网站内容 目录Vitepress+EdgeOne Pages快速迁移旧网站内容下载旧网站文章、图片网站文章转MarkdownVitepress项目快速开始EdgeOne Pages零帧起手参考材料 去年在阿里云码…