QT图形/视图架构详解(一)

ops/2024/12/17 15:35:46/

场景、视图与图形项

图形/视图架构主要由 3 个部分组成,即场景、视图和图形项,三者的关系如图所示:

场景、视图和图形项的关系

场景(QGraphicsScene 类)

场景不是界面组件,它是不可见的。场景是一个抽象的管理图形项的容器,我们可以向场景添加图形项,可以获取场景中的某个图形项。场景主要具有如下一些功能。

• 提供管理大量图形项的快速接口。

• 将事件传播给每个图形项。

• 管理每个图形项的状态,例如选择状态、焦点状态等。

• 管理未经变换的渲染功能,主要用于打印。

视图(QGraphicsView类)

QGraphicsView 是图形/视图架构中的视图组件。所以它是一个界面组件,用于显示场景中的内容。可以为一个场景设置多个视图,用于对同一个场景提供不同的显示界面。

在上图中,虚线框部分是一个场景,视图 1(场景外部的实线矩形框)比场景大,显示场景的全部内容。默认情况下,当视图大于场景时,场景在视图的中央位置显示,也可以通过设置视图的 Alignment 属性来控制场景在视图中的显示位置。视图 2(场景内部的实线矩形框)比场景小,视图 2 只能显示场景的部分内容,但是会自动提供卷滚条实现在整个场景内移动。

图形项(QGraphicsItem类)

图形项相当于模型中的数据,一个图形项存储了绘制这个图形项的各种参数,场景管理所

有图形项,而视图组件则负责绘制这些图形项。

QGraphicsItem 支持如下一些操作:

• 鼠标事件响应。

• 键盘输入,以及按键事件。

• 拖放操作。

• 支持组合,可以是父子图形项关系组合,也可以通过 QGraphicsItemGroup 类进行组合。

总结:场景是图形项的容器,可以在场景中添加很多图形项,每个图形项就是一 个对象。视图是显示场景的全部或部分区域的视口,也就是显示场景中的图形项,这些图形项可以被选择和拖动。

图形/视图架构的坐标系

图形/视图架构有 3 个有效的坐标系:场景坐标系、视图坐标系、图形项坐标系。3 个坐标系的

示意如图所示。

绘图的时候,场景坐标系等价于 QPainter的逻辑坐标系,一般以场景的中心为原点;视图坐标系与绘图设备坐标系相同,是物理坐标系,默认以左上角为原点;图形项坐标系是局部逻辑坐标系,一般以图形项的中心为原点。

图形项坐标系

图形项使用自己的局部坐标系,通常以其中心为原点(0, 0),这也是各种坐标变换的中心。绘制图形项时只需考虑其局部坐标系,QGraphicsScene 和 QGraphicsView 会自动进行坐标变换。

图形项的位置是指其中心在父对象坐标系中的坐标。 其父对象可以是场景或者其他父图形项。

QGraphicsItem 的大多数函数都在其局部坐标系上操作,QGraphicsItem::pos()是仅有的几个例外之一,它返回的是图形项在父图形项坐标系中的坐标,如果是顶层图形项,则返回的是在场景中的坐标。

视图坐标系

视图坐标系就是视图组件的物理坐标系,单位是像素。视图坐标系只与视图组件或视口有关,而与观察的场景无关。QGraphicsView 视口的左上角坐标总是(0,0)。

所有的鼠标事件、拖放事件的坐标首先是由视图坐标系定义的,然后用户需要将这些坐标映射为场景坐标,以便和图形项交互。

场景坐标系

场景坐标系定义了所有图形项的基础坐标,场景坐标系描述了每个顶层图形项的位置。创建场景时可以定义场景矩形区域的坐标范围。

scene= new QGraphicsScene(-400,-300,800,600); 

这样定义的 scene 是左上角坐标为(−400,−300)、宽度为 800 像素、高度为 600 像素的矩形区域,单位是像素。

每个图形项在场景里有一个位置坐标,由函数 QGraphicsItem::scenePos()给出,还有一个图形项边界矩形,由函数 QGraphicsItem::sceneBoundingRect()给出。边界矩形可以使场景知道场景的哪个区域发生了变化。场景发生变化时会发射 QGraphicsScene::changed()信号,参数是场景内的矩形区域列表,表示发生变化的矩形区域。

坐标映射

在场景中操作图形项时,会进行场景到图形项、图形项到图形项或视图到场景的坐标变换。例如,在 QGraphicsView 的视口上点击鼠标时,通过函数 QGraphicsView::mapToScene() 可以将视图坐标映射为场景坐标,然后用 QGraphicsScene::itemAt()函数可以获取场景中鼠标光标 处的图形项。

相关类的介绍

图形/视图架构主要的类包括视图类 QGraphicsView、场景类 QGraphicsScene,以及各种图形

项类,图形项类的基类是 QGraphicsItem。

QGraphicsView 类

QGraphicsView 是用于观察场景的物理窗口,其视口坐标系等同于显示设备的物理坐标系,也可以对 QGraphicsView 的坐标系进行平移、旋转、缩放等变换。

QGraphicsView 的主要接口函数如下表所示,一般设置函数有一个对应的读取函数,set_XXX对应get_XXX,这里只列出设置函数。

QGraphicsScene 类

QGraphicsScene 是用于管理图形项的场景,是图形项的容器。主要接口函数如下表所示,表中仅列出函数的返回值类型,省略了输入参数。

QGraphicsItem类

QGraphicsItem 是所有图形项类的基类,我们可以从 QGraphicsItem 继承定义自己的图形项。Qt 定义了一些常见的图形项,这些常见的图形项类的继承关系如下所示。

例如要设置图形项是一张图像,可以使用QGraphicsPixmapItem类和QImage、QPixmap配合设置设置显示的图像。

有关图形项操作的函数,主要的接口函数如下表所示,表中仅列出函数的返回值类型,省略了输入参数。

函数 setFlags()用于设置图形项的操作标志,包括可选择、可移动、可获取焦点等。函数 setPos()用于设置图形项在父图形项中的坐标,如果没有父图形项,则是在场景中的坐标。函数 setZValue()用于控制图形项的叠放次序,当多个图形项重叠时,zValue 值越大的越显示在前面,相当于优先级,值越大,优先级越高。

示例程序解读

该程序的主要功能:

• 工作区是一个 TGraphicsView 组件,其作为绘图的视图组件。

• 程序中创建了一个 QGraphicsScene 场景,场景的大小就是下图中的实线矩形框的大小。

• 若改变窗口大小,当视图大于场景时,矩形框总是居于视图的中央;当视图小于场景时,在视图窗口中会自动出现卷滚条。

• 椭圆正好处于场景的中央,圆位于场景的右下角。当图形项位置不在场景的矩形框中时,图形项也是可以显示的。

• 当鼠标光标在窗口上移动时,状态栏上会显示当前光标位置在视图中的坐标和在场景中的坐标。在某个图形项上点击鼠标时,状态栏上还会显示图形项中的局部坐标。

自定义QGraphicsView视图组件

为了实现一些定制化功能,从 QGraphicsView 继承定义了一个类 TGraphicsView ,重新实现事件处理函数mouseMoveEvent()和 mousePressEvent(),把鼠标事件转换为自定义信号,这样就可以在使用TGraphicsView 组件的主窗口里设计槽函数响应这些鼠标事件。

类声明与定义如下:

//.h文件
#ifndef TGRAPHICSVIEW_H
#define TGRAPHICSVIEW_H#include <QObject>
#include <QGraphicsView>//自定义TGraphicsView类继承QGraphicsView类
class TGraphicsView : public QGraphicsView
{Q_OBJECTprotected:    //重新实现下面两个函数void mouseMoveEvent(QMouseEvent *event);void mousePressEvent(QMouseEvent *event);public:TGraphicsView(QWidget *parent = nullptr);signals://信号会附带发送当前鼠标坐标void mouseMovePoint(QPoint point);void mouseClicked(QPoint point);
};#endif // TGRAPHICSVIEW_H///.cpp文件
#include    "tgraphicsview.h"
#include    <QMouseEvent>
#include    <QPoint>void TGraphicsView::mouseMoveEvent(QMouseEvent *event)
{//鼠标移动事件QPoint point=event->pos();      //QGraphicsView的坐标emit mouseMovePoint(point);     //释放信号QGraphicsView::mouseMoveEvent(event);   //调用父类的事件函数
}void TGraphicsView::mousePressEvent(QMouseEvent *event)
{ //鼠标左键按下事件if (event->button()==Qt::LeftButton){QPoint point=event->pos();  //QGraphicsView的坐标emit mouseClicked(point);   //释放信号}QGraphicsView::mousePressEvent(event);  //父类事件
}//构造函数
TGraphicsView::TGraphicsView(QWidget *parent):QGraphicsView(parent)
{}

这两个事件处理函数 mouseMoveEvent()和 mousePressEvent(),并定义了两个信号 mouseMovePoint()和 mouseClicked()。这两个函数的参数 event 都是 QMouseEvent 类型,如果鼠标发生相应的事件会将将发生的event传入事件处理函数。

通过 event->pos()可以获取鼠标光标在视图中的坐标 point,在发射自定义信号 mouseMovePoint()和 mouseClicked()时将 point 作为信号的参数。在其他地方可以使用connect()将该信号与相应的槽函数连接后就可以实现相应的功能。

主窗口

在主窗口函数中,设置视图的属性,连接视图的鼠标事件与槽函数,然后在视图中创建场景并将其与界面上的视图组件 view 关联起来,在场景中添加图形项,这里需要注意图形项的一些属性设置。若不调用函数 setPos()设置图形项在场景中的位置,默认位置为(0, 0)。

MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);//状态栏设计labViewCord=new QLabel("View 坐标:", this);labViewCord->setMinimumWidth(150);ui->statusBar->addWidget(labViewCord);labSceneCord=new QLabel("Scene 坐标:", this);labSceneCord->setMinimumWidth(150);ui->statusBar->addWidget(labSceneCord);labItemCord=new QLabel("Item 坐标:", this);labItemCord->setMinimumWidth(150);ui->statusBar->addWidget(labItemCord);ui->view->setCursor(Qt::CrossCursor);   //十字光标ui->view->setMouseTracking(true);       //鼠标跟踪ui->view->setDragMode(QGraphicsView::RubberBandDrag);   //矩形选择框// ui->view->setDragMode(QGraphicsView::ScrollHandDrag);connect(ui->view,SIGNAL(mouseMovePoint(QPoint)),this, SLOT(do_mouseMovePoint(QPoint)));connect(ui->view,SIGNAL(mouseClicked(QPoint)),this, SLOT(do_mouseClicked(QPoint)));iniGraphicsSystem();    //Graphics View结构初始化
}//创建Graphics View结构中的各项
void MainWindow::iniGraphicsSystem()
{QRectF   rect(-200,-100,400,200);  //该坐标是在view中的坐标scene=new QGraphicsScene(rect,this); //scene逻辑坐标系定义ui->view->setScene(scene);      //为视图设置场景//画一个矩形框,大小等于sceneQGraphicsRectItem   *item=new QGraphicsRectItem(rect);  //矩形框正好等于scene的大小//    item->setFlag(QGraphicsItem::ItemIsSelectable);         //可选中//    item->setFlag(QGraphicsItem::ItemIsFocusable);          //可以获得焦点//    item->setFlag(QGraphicsItem::ItemIsMovable,false);      //不能移动//可选,可以有焦点,但是不能移动item->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable);QPen    pen;pen.setWidth(2);item->setPen(pen);//    item->setPos(500,0);//缺省位置在scene的(0,0)scene->addItem(item);//一个位于scene中心的椭圆,测试局部坐标QGraphicsEllipseItem   *item2=new QGraphicsEllipseItem(-100,-50,200,100); //矩形框内创建椭圆,绘图项的局部坐标,左上角(-100,-50),宽200,高100item2->setPos(0,0);     //图形项在场景中的坐标item2->setBrush(QBrush(Qt::blue));item2->setFlag(QGraphicsItem::ItemIsMovable);      //能移动item2->setFlag(QGraphicsItem::ItemIsSelectable);   //可选中item2->setFlag(QGraphicsItem::ItemIsFocusable);    //可以获得焦点//    item2->setFlags(QGraphicsItem::ItemIsMovable//                    | QGraphicsItem::ItemIsSelectable//                    | QGraphicsItem::ItemIsFocusable);scene->addItem(item2);//一个圆,中心位于scene的边缘QGraphicsEllipseItem   *item3=new QGraphicsEllipseItem(-50,-50,100,100); //矩形框内创建椭圆,绘图项的局部坐标,左上角(-100,-50),宽200,高100item3->setPos(rect.right(),rect.bottom());        //图形项在场景中的坐标item3->setBrush(QBrush(Qt::red));item3->setFlag(QGraphicsItem::ItemIsMovable);      //能移动item3->setFlag(QGraphicsItem::ItemIsSelectable);   //可选中item3->setFlag(QGraphicsItem::ItemIsFocusable);    //可以获得焦点//    item3->setFlags(QGraphicsItem::ItemIsMovable//                    | QGraphicsItem::ItemIsSelectable//                    | QGraphicsItem::ItemIsFocusable);scene->addItem(item3);scene->clearSelection();
}

对于视图属性的设置:

• setCursor(Qt::CrossCursor):将鼠标光标设置为十字形。

• setMouseTracking(true):开启鼠标跟踪,在鼠标移动时就会产生 mouseMove 事件。默认是不开启鼠标跟踪的,那么只有在按下鼠标某个键时才会产生 mouseMove 事件。

• setDragMode(QGraphicsView::RubberBandDrag):在场景的背景上点击并拖动鼠标时,显示 一个矩形框。默认是不响应(QGraphicsView::NoDrag),其值还可以设置为 QGraphicsView:: ScrollHandDrag,拖动鼠标时鼠标光标变成手形,拖动鼠标就会使卷滚条移动。

关联信号与槽函数

连接的两个自定义槽函数用于与界面上的 TGraphicsView 组件的两个信号关联,在鼠标移动时显示视图中的坐标和场景中的坐标,在点击鼠标时显示图形项的局部坐标。

槽函数定义如下所示:


//鼠标移动,point是 GraphicsView的坐标,物理坐标
void MainWindow::do_mouseMovePoint(QPoint point)
{labViewCord->setText(QString::asprintf("View 坐标:%d,%d",point.x(),point.y()));QPointF pointScene=ui->view->mapToScene(point);     //转换到Scene坐标labSceneCord->setText(QString::asprintf("Scene 坐标:%.0f,%.0f", pointScene.x(),pointScene.y()));
}//鼠标单击
void MainWindow::do_mouseClicked(QPoint point)
{QPointF pointScene=ui->view->mapToScene(point);         //转换到Scene坐标QGraphicsItem  *item=NULL;item=scene->itemAt(pointScene,ui->view->transform());   //获取光标下的绘图项if (item != NULL)   //有绘图项{QPointF pointItem=item->mapFromScene(pointScene);   //转换为绘图项的局部坐标labItemCord->setText(QString::asprintf("Item 坐标:%.0f,%.0f", pointItem.x(),pointItem.y()));}
}

鼠标事件中,参数 point 是鼠标在视图中的坐标,使用 QGraphicsView 的 mapToScene()函数可以将此坐标变换为场景中的坐标。先用 QGraphicsView 的mapToScene()函数将此坐标变换为场景中的坐标 pointScene,然后用 QGraphicsScene 的 itemAt()函数获得光标处的图形项。如果鼠标光标处有图形项,就用图形项的 mapFromScene()函数将pointScene 变换为图形项的局部坐标 pointItem。

自定义TGraphicsView的UI提升

我们使用时需要的是TGraphicsView,但是QT可选组件中只有GraphicsView,因此需要将视图组件升级为 TGraphicsView 类。升级方法如下所示,之前在QT界面设计中添加的是QGraphicsView,右键该组件点击提升为----->TGraphicView即可。这是使用自定义界面组件的一种方法。

为了测试视图与场景的尺寸变化,主窗口还重定义了事件处理函数 resizeEvent(),以便在窗口大小变化时,显示视图区域和场景的大小信息。其代码如下:

//窗口变化大小时的事件
void MainWindow::resizeEvent(QResizeEvent *event)
{//    Q_UNUSED(event);//Graphics View坐标,左上角总是(0,0),宽度=,长度=QString str=QString::asprintf("Graphics View坐标,左上角总是(0,0),宽度=%d,高度=%d",ui->view->width(),ui->view->height());ui->labViewSize->setText(str);//    ui->labViewSize->setText(QString::asprintf("Graphics View坐标,左上角总是(0,0),宽度=%d,高度=%d",//                             ui->view->width(),ui->view->height()));QRectF  rectF=ui->view->sceneRect(); //场景的矩形区QString str2=QString::asprintf("QGraphicsView::sceneRect=(Left,Top,Width,Height)""=%.0f,%.0f,%.0f,%.0f", rectF.left(),rectF.top(),rectF.width(),rectF.height());ui->labSceneRect->setText(str2);//    ui->labSceneRect->setText(QString::asprintf("QGraphicsView::sceneRect=(Left,Top,Width,Height)=%.0f,%.0f,%.0f,%.0f",//                              rectF.left(),rectF.top(),rectF.width(),rectF.height()));event->accept();
}

通过测试,程序在窗口大小改变时,labViewSize 显示的视图宽度和高度是随窗口大小变化而变化的,而 labSceneRect 显示的场景的矩形区大小是固定的,总是(−200, −100, 400, 200)。

如下图,将程序窗口缩小,视图也会变小,显示的内容也会被隐藏一部分,但是场景是尺寸是不变的,所以视图是用来“看”的,而场景是用“存”的,视图中虽然有些组件看不见了,但是这些组件确确实实还在场景中,这也是为什么在视图和图形项中要套一层场景,这样更方便管理。


http://www.ppmy.cn/ops/142673.html

相关文章

网络安全—认证技术

通过了解以前前辈们使用的消息认证慢慢渐进到现代的完整的认证体系。所以在学习的时候也很蒙圈&#xff0c;因为前期的很多技术都是有很严重的不可靠性&#xff0c;因此本篇用作加深理解认证技术的发展史还有到目前为止完整的不可抵赖的认证体系是如何形成的&#xff0c;并且了…

全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之复合运算符

&#xff08;一&#xff09;、复合运算符 在C中&#xff0c;可以通过“赋值语句”来修改变量的值。赋值语句的格式&#xff1a; 变量名 值或者表达式&#xff1b;其中""称为"赋值运算符"。 除此之外&#xff0c;在赋值运算符当中&#xff0c;C有复合赋…

网络安全协议之比较(SSH、PKI、SET、SSL)

一、SSH介绍 什么是SSH&#xff1f;   传统的网络服务程序&#xff0c;如&#xff1a;ftp、pop和telnet在本质上都是不安全的&#xff0c;因为它们在网络上用明文传送口令和数据&#xff0c; 别有用心的人非常容易就可以截获这些口令和数据。而且&#xff0c;这些服务程序的…

MySQL笛卡尔积

MySQL的笛卡尔积是通过将两个表的所有行组合生成的结果集。它是一种比较耗时的操作&#xff0c;因此通常建议在实际应用中尽量避免使用笛卡尔积。 在MySQL中&#xff0c;可以通过使用CROSS JOIN来实现笛卡尔积。例如&#xff0c;假设有两个表A和B&#xff0c;可以使用以下语法…

Linux 网络流量控制 - 实现概述

摘要 Linux 提供了一整套丰富的流量控制(traffic control)功能。本文档概述了相应的内核代码设计&#xff0c;描述了其结构&#xff0c;并通过描述一种新的排队策略来说明新元素的添加。 1 引言 最近的Linux内核提供了多种流量控制功能。Alexey Kuznetsov&#xff08;kuznet…

ffmpeg C语音 读取视频帧源码

文章目录 前言主体1. AVPacket作用&#xff1a;相关字段&#xff1a;作用场景&#xff1a; 2. av_read_frame函数原型&#xff1a;作用&#xff1a;参数&#xff1a;返回值&#xff1a;相关结构体&#xff1a; 3. av_packet_unref函数原型&#xff1a;作用&#xff1a;参数&…

游戏引擎学习第48天

仓库: https://gitee.com/mrxiao_com/2d_game 回顾 我们正在进行碰撞检测的工作&#xff0c;昨天我们几乎完成了一部分代码。由于一些原因&#xff0c;昨天的直播结束时未能完成所有内容。今天我们将继续进行&#xff0c;首先回顾一下之前的进展。我们需要让角色能够正确地与…

list comprehension

python列表推导式&#xff08;list comprehension&#xff09; 列表推导式是一种简洁且高效的创建列表的方式。它的基本语法如下&#xff1a; [expression for item in iterable if condition]案例如下&#xff0c;实现对第一列加1的操作。 a[(i,i**0.5) for i in range(0,1…