qt5-入门-2D绘图-Graphics View 架构

embedded/2024/9/25 13:01:42/

参考:
Qt Graphics View Framework_w3cschool
https://www.w3cschool.cn/learnroadqt/4mvj1j53.html

C++ GUI Programming with Qt 4, Second Edition

本地环境:
win10专业版,64位,Qt 5.12


基础知识

QPainter比较适合少量绘图的情况,如果一个图里包含成千上百个单元,同时要让用户可以点击、拖动、选择,那么应该使用grahics view。
这个架构包含:一个scene(对应QGraphicsScene类),里面包含很多item(对应QGraphicsItem类)。用户要看到这个scene,需要借助view(对应QGraphicsView类)。一个scene可以对应多个view,比如不同的view可以看到scene的不同部分,或者不同的view可以看到经过不同transformation后的scene。如下图所示:
在这里插入图片描述
一个scene包含三层:foreground layer, background layer和item layer。前两个可以用QBrush修改,也可以通过重写drawBackground()drawForeground()实现完全的控制。举个例子,如果我们想用一个pixmap作为背景,那可以用QBrush制作一个基于这个pixmap的纹理。

scene可以告诉我们哪些item冲突了,哪些被选择了,或者哪些在哪里这样的信息。item也可以嵌套,此时如果对这个item做transformation,那么它包含的item也会自动被transform。

graphics view架构提供了两种item分组方式:1是前面提到的嵌套,就是让一个item是另一个item的孩子;2是使用QGraphicsItemGroup,加入group的item不会像嵌套一样传递transformation,它们每一个都像独立的个体(类似集合?)

item有预定义的(见下图),也可以重写。
在这里插入图片描述

QGraphicsView实际是一个组件(widget),提供滚动条(如果需要),并且通过transformation改变scene的呈现效果。它默认使用Qt内置的2D绘制引擎,但是也可以用OpenGL组件(构造后调用setViewport(),用到再写)。

graphics view架构使用三种坐标系:(同时提供了三种坐标系互转的函数)

  • viewport坐标系:QGraphicsView的viewport
  • scene坐标系:是逻辑坐标系,用来放置scene顶层的item
  • item坐标系:只与item相关,用来放置子item和绘制item
    在实际操作中,我们通常只关心后两个坐标系。

简单例子

如下图所示,在窗口中绘制一条线,无论怎么放缩窗口,这条线始终居中。如果使用QPainter来画,保持居中就需要一些计算,但是用graphics view架构,代码非常短,也没有计算。
在这里插入图片描述

#include <QGraphicsScene>
#include <QGraphicsView>QGraphicsScene *scene = new QGraphicsScene;
scene->addLine(10, 10, 150, 300);
QGraphicsView *view = new QGraphicsView(scene);
view->resize(500, 500);
view->setWindowTitle("Graphics View");
view->show();

进阶例子:自定义item

效果

在这里插入图片描述
在这里插入图片描述

例子来自ch8-Item-Based Rendering with Graphics View-diagram(实在找不到书和代码的可以私信我,虽然我觉得挺好找的)。大概是构造一个软件,用户可以放置一些Node,并且彼此相连,同时可以拖动上面的元素,并且设置一些属性。

知识储备

  • QGraphicsLineItem不是QObject的子类,如果要添加信号需要另外继承QObject(多继承)
  • graphics-view架构使用bounding rectangle技术。通过这个,可以判断某个点是否在item只中,也可以判断两个item是否碰撞,或者某个item是否需要被绘制(如果scene很大,当只需要绘制一部分时,如果某个item不在可视范围,那通过这个矩形是可以快速判断的,从而提升效率)
  • 因此如果要完全自定义item,需要重新实现boundingRect()paint()(也许还有shape()) 。其中shape是比boundingrect更精确的形状,可以应对细粒度碰撞检测(fine-grained collision detection)的情况,特别是当外形是圆角矩形时。比如如果重写了shape(),那么当点击夹在shape和bounding rectangle之间的区域时,是不会选中item的。也可以不重写它。
  • 根据前面的知识可知,如果图形要重绘需要调用update(),那么如果bounding rectangle可能会改变,就要调用prepareGeometryChange()

设计思路

此处不展开完整的实现,只说明文档结构。
在这里插入图片描述
link是两个node之间的连线,也是最简单的item,因为只需要考虑起点、终点和线条颜色(线宽默认),所以使用QGraphicsLineItem的一些函数就可以写好。Node是包含文字的圆角矩形框,需要完整地绘制,包括bounding rectangle,也包括一些互动(双击快速修改文字,修改属性)。propertiesdialog是属性设置窗口,写的是窗口上那些按钮的响应。最后diagramwindow就是主窗口,写的是主窗口上那些工具的布局、QAction和元素的初始化等。

实现

这里只展示 Node 和 Link。

Link

#ifndef LINK_H
#define LINK_H#include <QGraphicsLineItem>QT_BEGIN_NAMESPACE
class Node;
QT_END_NAMESPACE// QGraphicsLineItem不是QObject的子类,如果要添加信号需要另外继承QObject
// 不用额外的QColor变量记录颜色是因为颜色设置很简单,只是用一下QPen
class Link : public QGraphicsLineItem
{
public:Link(Node *fromNode, Node *toNode);~Link();// 获取两个端点Node *fromNode() const;Node *toNode() const;// 线的颜色void setColor(const QColor &color);QColor color() const;// 连线操作void trackNodes();private:Node *myFromNode;Node *myToNode;
};#endif// -------------------------------------------------------#include <QtWidgets>#include "link.h"
#include "node.h"Link::Link(Node *fromNode, Node *toNode)
{myFromNode = fromNode;myToNode = toNode;myFromNode->addLink(this);myToNode->addLink(this);// 选中是为了可以删除setFlags(QGraphicsItem::ItemIsSelectable);// z表示离窗口的远近,-1就是放在两个node下面,这样虽然是// 连接两个Node中心,但是看起来好像连接的是两个最近的边缘一样setZValue(-1);// 设置线的颜色setColor(Qt::darkRed);// 连线trackNodes();
}Link::~Link()
{myFromNode->removeLink(this);myToNode->removeLink(this);
}Node *Link::fromNode() const
{return myFromNode;
}Node *Link::toNode() const
{return myToNode;
}void Link::setColor(const QColor &color)
{// 1.0是线宽setPen(QPen(color, 1.0));
}QColor Link::color() const
{return pen().color();
}void Link::trackNodes()
{/* QGraphicsLineItem::pos()返回的是相对于父的坐标,这个父可以是* scene(如果这个item是top-level的),也可以是item(嵌套的情况)* QLineF是创建一条2D线段* QGraphicsLineItem::setLine*/setLine(QLineF(myFromNode->pos(), myToNode->pos()));
}

Node

#ifndef NODE_H
#define NODE_H#include <QApplication>
#include <QColor>
#include <QGraphicsItem>
#include <QSet>QT_BEGIN_NAMESPACE
class Link;
QT_END_NAMESPACEclass Node : public QGraphicsItem
{// 允许用tr(),不用再写QObject::tr()或者QCoreApplication::translate()Q_DECLARE_TR_FUNCTIONS(Node)public:Node();~Node();// 一些setter, gettervoid setText(const QString &text);QString text() const;void setTextColor(const QColor &color);QColor textColor() const;void setOutlineColor(const QColor &color);QColor outlineColor() const;void setBackgroundColor(const QColor &color);QColor backgroundColor() const;void addLink(Link *link);void removeLink(Link *link);QRectF boundingRect() const;QPainterPath shape() const;void paint(QPainter *painter,const QStyleOptionGraphicsItem *option, QWidget *widget);protected:// 双击以快速修改textvoid mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event);// 处理node被移动的情况,此时需要更新相关的Link// itemChange是在图形项的任何属性变化时都会被调用,而 mouseMoveEvent 只在鼠标移动时才会触发QVariant itemChange(GraphicsItemChange change,const QVariant &value);private:QRectF outlineRect() const;int roundness(double size) const;QSet<Link *> myLinks;QString myText;QColor myTextColor;QColor myBackgroundColor;QColor myOutlineColor;
};#endif// -------------------------------------------------------#include <QtWidgets>#include "link.h"
#include "node.h"Node::Node()
{myTextColor = Qt::darkGreen;myOutlineColor = Qt::darkBlue;myBackgroundColor = Qt::white;setFlags(ItemIsMovable | ItemIsSelectable);
}Node::~Node()
{// 删除node时候要删掉所有关联的linkforeach (Link *link, myLinks)delete link;
}void Node::setText(const QString &text)
{// 表示bounding rectangle可能会改变prepareGeometryChange();myText = text;// 重绘update();
}QString Node::text() const
{return myText;
}// color类的setter getter就不展示了,注意setter需要调用update()重绘void Node::addLink(Link *link)
{myLinks.insert(link);
}void Node::removeLink(Link *link)
{myLinks.remove(link);
}QRectF Node::boundingRect() const
{const int Margin = 1;// 左上右下// 表示对outlineRect()返回的矩形进行调整,向左、上、右、下各移动Margin像素// margin的取值应该至少是pen宽度的一半return outlineRect().adjusted(-Margin, -Margin, +Margin, +Margin);
}QPainterPath Node::shape() const
{QRectF rect = outlineRect();QPainterPath path;path.addRoundRect(rect, roundness(rect.width()),roundness(rect.height()));return path;
}void Node::paint(QPainter *painter,const QStyleOptionGraphicsItem *option,QWidget * /* widget */)
{QPen pen(myOutlineColor);if (option->state & QStyle::State_Selected) {pen.setStyle(Qt::DotLine);pen.setWidth(2);}painter->setPen(pen);painter->setBrush(myBackgroundColor);QRectF rect = outlineRect();painter->drawRoundRect(rect, roundness(rect.width()),roundness(rect.height()));painter->setPen(myTextColor);painter->drawText(rect, Qt::AlignCenter, myText);
}void Node::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{QString text = QInputDialog::getText(event->widget(),tr("Edit Text"), tr("Enter new text:"),QLineEdit::Normal, myText);if (!text.isEmpty())setText(text);
}QVariant Node::itemChange(GraphicsItemChange change,const QVariant &value)
{// 每次拖动Node,位置就发生变化了,此时需要重新画线if (change == ItemPositionHasChanged) {foreach (Link *link, myLinks)link->trackNodes();}return QGraphicsItem::itemChange(change, value);
}QRectF Node::outlineRect() const
{const int Padding = 8;QFontMetricsF metrics = static_cast<QFontMetricsF>(qApp->font());QRectF rect = metrics.boundingRect(myText);rect.adjust(-Padding, -Padding, +Padding, +Padding);// 转换坐标是为了让text放在中心,本来默认的坐标是左上角(0,0)rect.translate(-rect.center());return rect;
}int Node::roundness(double size) const
{const int Diameter = 12;return 100 * Diameter / int(size);
}

http://www.ppmy.cn/embedded/31687.html

相关文章

2024.4.27 —— LeetCode 高频题复盘

目录 102. 二叉树的层序遍历33. 搜索旋转排序数组121. 买卖股票的最佳时机200. 岛屿数量20. 有效的括号88. 合并两个有序数组141. 环形链表46. 全排列236. 二叉树的最近公共祖先 102. 二叉树的层序遍历 题目链接 Python 方法一 # Definition for a binary tree node. # clas…

@react-three/fiber 初学笔记

glb模型拆解&#xff0c;将模式按组进行拆解成mesh,方便添加事件 npx gltfjsx public/glb/officeModel2.glb /* Auto-generated by: https://github.com/pmndrs/gltfjsx Command: npx gltfjsx6.2.16 public/glb/officeModel2.glb */import React, { useRef } from react imp…

【讲解如何OpenCV入门】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

芒果YOLOv8改进164:检测头篇:ImplicitHead 隐性知识检测头| 即插即用,独家新颖更新,精度高效涨点

💡本篇内容:芒果YOLOv8改进164:检测头篇:ImplicitHead 独家原创检测头 | 即插即用,独家新颖更新,精度高效涨点 芒果专栏提出:原创隐性知识学习检测头 ImplicitHead 结构,改进源码教程 | 详情如下🥇 即插即用 ImplicitHead检测头,包括改进所需的 核心结构代码 文件作…

C++中的priority_queue模拟实现

目录 priority_queue模拟实现 priority_queue类定义 priority_queue构造函数 priority_queue类push()函数 priority_queue类pop()函数 priority_queue类size()函数 priority_queue类empty()函数 priority_queue类top()函数 仿函数与priority_queue类模拟实现 仿函数 …

西班牙语语法名词的复数形式,柯桥西班牙语培训

&#xff08;2&#xff09;后面加-es • 以辅音结尾的名词。例如&#xff1a; el seor - los seores 先生 la ciudad - las ciudades 城市 • 以-y 结尾的名词。例如&#xff1a; el rey - los reyes 国王 la ley - las leyes 法律 • 以-z 结尾的名词&#xff0c;将词尾…

闲话 Asp.Net Core 数据校验(三)EF Core 集成 FluentValidation 校验数据例子

前言 一个在实际应用中 EF Core 集成 FluentValidation 进行数据校验的例子。 Step By Step 步骤 创建一个 Asp.Net Core WebApi 项目 引用以下 Nuget 包 FluentValidation.AspNetCore Microsoft.AspNetCore.Identity.EntityFrameworkCore Microsoft.EntityFrameworkCore.Re…

MySQL技能树学习——数据库组成

数据库组成&#xff1a; 数据库是一个组织和存储数据的系统&#xff0c;它由多个组件组成&#xff0c;这些组件共同工作以确保数据的安全、可靠和高效的存储和访问。数据库的主要组成部分包括&#xff1a; 数据库管理系统&#xff08;DBMS&#xff09;&#xff1a; 数据库管理系…