自学内容网 自学内容网

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

场景、视图与图形项

图形/视图架构主要由 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_OBJECT

protected:    //重新实现下面两个函数
    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);      //为视图设置场景

//画一个矩形框,大小等于scene
QGraphicsRectItem   *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,高100
item2->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,高100
item3->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)。

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


原文地址:https://blog.csdn.net/qq_46144191/article/details/144423161

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!