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

news2024/11/21 0:35:50

参考:
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_NAMESPACE

class Node : public QGraphicsItem
{
    // 允许用tr(),不用再写QObject::tr()或者QCoreApplication::translate()
    Q_DECLARE_TR_FUNCTIONS(Node)

public:
    Node();
    ~Node();
    // 一些setter, getter
    void 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:
    // 双击以快速修改text
    void 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时候要删掉所有关联的link
    foreach (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.coloradmin.cn/o/1636329.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于ssm+vue+Mysql的房屋租赁系统求租合同

开发语言&#xff1a;Java框架&#xff1a;ssmJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.…

Qt+Ubuntu20.04:打包qt

打包程序 参考 qt项目在Linux平台上面发布成可执行程序.run_qt.run不是虚拟机的配置文件-CSDN博客 Linux下Qt程序的打包发布(1)-不使用第三方工具 - 知乎 (zhihu.com) 过程 1、Release编译 先将你的程序在release下编译通过&#xff0c;保证下面打包的程序是你最新的。 2…

基于Matlab使用深度学习的多曝光图像融合

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 在图像处理领域&#xff0c;多曝光图像融合技术是一种重要的技术&#xff0c;它可以将不同曝光条件下…

【记录】Python3| 将 PDF 转换成 HTML/XML(✅⭐pdfminer.six)

本文将会被汇总至 【记录】Python3&#xff5c;2024年 PDF 转 XML 或 HTML 的第三方库的使用方式、测评过程以及对比结果&#xff08;汇总&#xff09;&#xff0c;更多其他工具请访问该文章查看。 注意&#xff01;pdfminer.six 和 pdfminer3k 不是同一个&#xff01;&#xf…

闪存存储和制造技术概述

闪存存储技术 引言 性能由高到低排序&#xff1a;SLC -> MLC -> TLC -> QLC 根据这个排序读写速度也越来越低&#xff0c;价格越来越便宜 1. SLC SLC&#xff08;Single-Level Cell&#xff0c;单层单元&#xff09;&#xff1a; SLC 闪存具有最高的性能、耐用性和可…

linus下Anaconda创建虚拟环境pytorch

一、虚拟环境 1.创建 输入下面命令 conda create -n env_name python3.8 输入y 2.激活环境 输入 conda activate env_name 二、一些常用的命令 在Linux的控制平台 切换到当前的文件夹 cd /根目录/次目录 查看conda目录 conda list 查看pip目录 pip list查看历史命…

三. Django项目之电商购物商城 -- 校验用户名 , 数据入库

Django项目之电商购物商城 – 校验用户名 , 数据入库 需要开发文档和前端资料的可私聊 一. 路由匹配获得用户名 在注册时 , 用户输入用户名 , 通过ajax请求发送到服务器 , 在路由中设置对应url , 响应视图 , 将用户输入的用户名传入视图 , 与数据库进行校验检查用户名是否重…

安全免费的远程软件有哪些?

远程访问软件&#xff0c;又称远程协助软件或远程控制软件&#xff0c;正在迅速走红。这类软件无论您身处何地&#xff0c;都能轻松实现远程访问和计算机控制。对于个人而言&#xff0c;远程控制工具使工作更加灵活、便捷&#xff1b;而对企业而言&#xff0c;远程访问软件也是…

【webrtc】MessageHandler 1: 基于线程的消息处理:以10毫秒处理音频为例

基于m98 G:\CDN\rtcCli\m98\src\audio\null_audio_poller.h分发的消息由MessageHandler 类通过其抽象接口OnMessage 实现处理 NullAudioPoller NullAudioPoller 是一个处理audio的消息的分发器 poll 启动:

spring boot运行过程中动态加载Controller

1.被加载的jar代码 package com.dl;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplication public class App {public static void main(String[] args) {SpringApplication.run(A…

自学Vue3 Day2

一、组合式Api组件通信 1.父与子之间 父传子&#xff1a;父导入子组件&#xff0c;定义好数据&#xff0c;子组件用props接收&#xff0c;这里defineProps底层本质还是props. 注意模板渲染过程不需要写props 子传 父&#xff1a; 2.模版引用&#xff08;ref&#xff09;和组…

学习VUE2第6天

一.请求拦截器 可以节流&#xff0c;防止多次点击请求 toast是单例 二.前置路由守卫 在Vue.js中&#xff0c;前置路由守卫是指在路由转换实际发生之前执行的钩子函数。这是Vue Router&#xff08;Vue.js官方的路由管理器&#xff09;提供的一种功能&#xff0c;允许开发者在用…

Django后台项目开发实战四

用户可以浏览工作列表以及工作详情 第四阶段 在 jobs 文件夹下创建 templates 文件夹&#xff0c;在里面创建 base.html 网页&#xff0c;内容如下 <!-- base.html --> <div style"text-align:center;"><h1 style "margin:auto; width:50%;&…

DS高阶:图论基础知识

一、图的基本概念及相关名词解释 1.1 图的基本概念 图是比线性表和树更为复杂且抽象的结&#xff0c;和以往所学结构不同的是图是一种表示型的结构&#xff0c;也就是说他更关注的是元素与元素之间的关系。下面进入正题。 图是由顶点集合及顶点间的关系组成的一种数据结构&…

基于网络爬虫技术的网络新闻分析参考论文(论文 + 源码)

【免费】基于网络爬虫技术的网络新闻分析系统.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89248815 基于网络爬虫技术的网络新闻分析 摘 要 自从大数据的概念被提出后&#xff0c;互联网数据成为了越来越多的科研单位进行数据挖掘的对象。网络新闻数据占据了…

EasyRecovery2024汉化版电脑数据恢复软件下载

EasyRecovery是一款功能强大的数据恢复软件&#xff0c;其主要功能包括但不限于以下几点&#xff1a; 硬盘数据恢复&#xff1a;能够扫描本地计算机中的所有卷&#xff0c;建立丢失和被删除文件的目录树&#xff0c;实现硬盘格式化、重新分区、误删数据、重建RAID等硬盘数据恢…

RTSP,RTP,RTCP

机器学习 Machine Learning&#xff08;ML&#xff09; 深度学习&#xff08;DL&#xff0c;Deep Learning&#xff09; CV计算机视觉&#xff08;computer vision&#xff09; FFMPEG&#xff0c;MPEG2-TS,H.264,H.265,AAC rstp,rtp,rtmp,webrtc onvif,gb28181 最详细的音…

数智新重庆 | 推进信号升格 打造算力山城

2024年&#xff0c;是实现“十四五”规划目标任务的关键一年&#xff0c;高质量的5G网络、强大的AI能力作为新质生产力的重要组成部分&#xff0c;将有效赋能包括制造业在内的千行万业数字化化、智能化、绿色化转型升级&#xff0c;推动融合应用新业态、新模式蓬勃兴起&#xf…

上传jar到github仓库,作为maven依赖存储库

记录上传maven依赖包到github仓库问题 利用GitHubPackages作为依赖的存储库踩坑1 仓库地址问题踩坑2 Personal access tokens正确姿势一、创建一个普通仓库&#xff0c;比如我这里是fork的腾讯Shadow到本地。地址是&#xff1a;https://github.com/dhs964057117/Shadow二、生成…

Postgresql 从小白到高手 十一 :数据迁移ETL方案

文章目录 Postgresql 数据迁移ETL方案1、Pg 同类型数据库2 、Pg 和 不同数据库 Postgresql 数据迁移ETL方案 1、Pg 同类型数据库 备份 : pg_dump -U username -d dbname -f backup.sql插入数据&#xff1a; psql -U username -d dbname -f backup.sqlpg_restore -U username…