《Qt5.9 C++ 开发指南》8.3 GraphicsView绘图框架 笔记

8.3.1 场景、视图与图形项
采用 QPainter 绘图时需要在绘图设备的 paintEvent() 事件里编写绘图程序,实现整个绘图过程。这种方法如同使用 Windows 的画图软件在绘图,绘制的图形是位图,这种方法适合于绘制复杂性不高的固定图形,不能实现图件的选择、编辑、拖放、修改等功能。
Qt为绘制复杂的可交互图形提供了 Graphics view 绘图架构,是一种基于图形项(Graphics Item)的模型/视图模式,与第5章的数据编辑与显示的 Model/View 模式类似。使用 Graphics View 架构可以绘制复杂的有几百万个基本图形元件的图形,并且每个图形元件是可选择、可拖放和修改的,类似于矢量绘图软件的绘图功能。
Graphics View 架构主要由 3 个部分组成,即场景、视图和图形项,其构成的Graphics View绘图系统如图。
《Qt5.9 C++ 开发指南》8.3 GraphicsView绘图框架 笔记_第1张图片
场景
QGraphicsScene类提供绘图场景(Scene)。场景是不可见的,是一个抽象的管理图形项的容器,可以向场景添加图形项,获取场景中的某个图形项等。场景主要具有如下一些功能:

  • 提供管理大量图形项的快速接口;
  • 将事件传播给每个图形项;
  • 管理每个图形项的状态,例如选择状态、焦点状态等;
  • 管理未经变换的渲染功能,主要用于打印。

除了图形项之外,场景还有背景层和前景层,通常由QBrush指定,也可以通过重新实现drawBackground() 和 drawForeground() 函数来实现自定义的背景和前景,实现一些特殊效果。

视图
QGraphicsView 提供绘图的视图(View)组件,用于显示场景中的内容。可以为一个场景设置几个视图,用于对同一个数据集提供不同的视口。
在图8-16中,虚线框围住的是一个场景,视图 1 比场景大,显示场景的全部内容。缺省情况下,当视图大于场景时,场景在视图的中间部分显示,也可以 设置视图的Alignment属性控制场景在视图中的显示位置;当视图小于场景时(见图 8-16 中的视图2),视图只能显示场景的一部分内容,但是会自动提供卷滚条在整个场景内移动。
视图接收键盘和鼠标输入并转换为场景事件,并进行坐标转换后传送给可视场景。

图形项
图形项(Graphics Item)就是一些基本的图形元件,图形项的基类是 QGraphicsItem。Qt 提供了一些基本的图形项,如绘制椭圆的 QGraphicsEllipseItem、绘制矩形的 QGraphicsRectItem、绘制文字的QGraphicsTextItem等。
QGraphicsItem支持如下的一些操作:

  • 鼠标事件响应,包括鼠标按下、移动、释放、双击,以及鼠标停留、滚轮、快捷菜单等事件;
  • 键盘输入,按键事件;
  • 拖放操作;
  • 支持组合,可以使父子项关系组合,也可以是通过QGraphicsItemGroup类进行组合。

所以,图形项可以被选择、拖放、组合,若编写信号槽函数代码,还可以实现各种编辑功能。一个图形项还可以包含子图形项,图形项还支持碰撞检测,即是否与其他图形项碰撞。
在图8-16所示的视图、场景和图形项之间的关系示意图中,场景是图形项的容器,可以再场景上绘制很多图形项,每个图形项就是一个对象,这些图形项可以被选择、拖动等。视图是显示场景的一部分区域的视口,一个场景可以有多个视图,一个视图现实场景的部分区域或全部区域,或从不同角度观察场景。

8.3.2 Graphics View 的坐标系统
《Qt5.9 C++ 开发指南》8.3 GraphicsView绘图框架 笔记_第2张图片
图形项坐标(局部逻辑坐标)、场景坐标(QPainter的逻辑坐标)、视图坐标(设备坐标、物理坐标):
《Qt5.9 C++ 开发指南》8.3 GraphicsView绘图框架 笔记_第3张图片图形项坐标
图形项使用自己的局部坐标(Item Coordinates),通常以其中心为(0,0),也是各种坐标变换的中心。图形项的鼠标事件的坐标使用局部坐标表示的,创建自定义图形项,绘制图形项时只需考虑其局部坐标,QGraphicsScene 和 QGraphicsView 会自动进行坐标转换。
一个图形项的位置是其中心点在父坐标系统中的坐标,对于没有父图形项的图形项,其父对象就是场景,图形项的位置就是在场景中的坐标。
如果一个图形项还是其他图形项的父项,父项进行坐标变换时,子项也做同样的坐标变换。
QGraphicsItem 的大多数函数都是在其局部坐标系上操作的,例如一个图形项的边界矩形 QGraphicsItem::boundingRect() 是用局部坐标给出的,但是 QGraphicsItem::pos() 是仅有的几个例外,它返回的是图形项在父坐标系中的坐标,如果是顶层图形项,就是在场景中的坐标。

视图坐标
视图坐标(View Coordinates)就是窗口界面(widget)的物理坐标,单位是像素。视图坐标只与 widget 或视口有关,而与观察的场景无关。QGraphicsView 视口的左上角坐标总是(0,0)。
所有的鼠标事件、拖放事件的坐标首先是由视图坐标定义的,然后用户需要将这些坐标映射为场景坐标,以便和图形项交互。

场景坐标
场景是所有图形项的基础坐标,场景坐标(Scene Coordinates)描述了每个顶层图形项的位置。创建场景时可以定义场景矩形区的坐标范围,例如

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

这样定义的scene是左上角坐标为(-400,-300),宽度为800,高度为600的矩形区域,单位是像素。
每个图形项在场景里都有一个位置坐标,由函数 QGraphicsItem::scenePos() 给出;还有一个图形项边界矩形,由 QGraphicsItem::sceneBoundingRect() 函数给出。边界矩形可以使 QGraphicsScene 知道场景的哪个区域发生了变化。场景发生变化时会发射 QGraphicsScene::changed() 信号,参数是一个场景的矩形列表,表示发生变化的矩形区。

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

8.3.3 Graphics View 相关的类

8.3.4 Graphics View 程序基本结构和功能实现

实例

实例主要功能包括以下几点

  • 工作区是一个从 QGraphicsView 继承的自定义类 QWGraphicsView,作为绘图的视图组件。
  • 创建一个QGraphicsScene场景,场景的大小就是图中的实线矩形框的大小。
  • 改变窗口大小,当视图大于场景时,矩形框总是居于图形视图的中央;当视图小于场景时,在视图窗口自动出现卷滚条。
  • 蓝色椭圆正好处于场景的中间,红色圆形位于场景的右下角。当图形项位置不在场景的矩形框中时,图形项也是可以显示的。
  • 当鼠标在窗口上移动时,会在状态栏显示当前光标位置的视图坐标和场景坐标,在某个图形项上单击鼠标时,还会显示在图形项中的局部坐标。

自定义图形视图组件
QGraphicsView是Qt的图形视图组件,在UI设计器的Display Widgets分组里可以拖放一个QGraphicsView组件到窗口上。但是本实例中需要实现鼠标在QGraphicsView上移动时就显示当前光标的坐标,这涉及 mouseMoveEvent() 事件的处理。QGraphicsView 没有与 mouseMoveEvent 相关的信号,因而无法定义槽函数与此事件相关联。
为此,从 QGraphicsView 继承定义一个类 QWGraphicsView,实现 mouseMoveEvent() 事件和 mousePressEvent() 事件,并把鼠标事件转换为信号,这样就可以在主程序里设计槽函数响应这些鼠标事件。下面是QWGraphicsView类的定义:

// qwgraphicsview.h
#ifndef QWGRAPHICSVIEW_H
#define QWGRAPHICSVIEW_H

#include 

class QWGraphicsView : public QGraphicsView
{
    Q_OBJECT

protected:
    void mouseMoveEvent(QMouseEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;

public:
    QWGraphicsView();
    
signals:
    void mouseMovePoint(QPoint point);
    void mouseClicked(QPoint point);
};

#endif // QWGRAPHICSVIEW_H

QWGraphicsView 的实现:

QWGraphicsVIew.cpp
#include "qwgraphicsview.h"

#include 

QWGraphicsView::QWGraphicsView()
{
    
}

void QWGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
    // 鼠标移动事件
    QPoint point = event->pos();   // 获取鼠标光标在视图中的坐标 point
    emit mouseMovePoint(point);   // 发射信号
    QGraphicsView::mouseMoveEvent(event);
}

void QWGraphicsView::mousePressEvent(QMouseEvent *event)
{
    // 鼠标左键按下事件
    if(event->button() == Qt::LeftButton)
    {
        QPoint point = event->pos();    // 获取鼠标光标在视图中的坐标 point
        emit mouseClicked(point);   // 发射信号
    }
    QGraphicsView::mousePressEvent(event);
}

MainWindow 类的完整定义如下:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include 
#include 

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    QGraphicsScene *scene;
    QLabel *labViewCord;
    QLabel *labSceneCord;
    QLabel *labItemCord;
    void iniGraphicsSystem();   // 创建 Graphics View 的各项
    
protected:
    void resizeEvent(QResizeEvent *event);
    
private slots:
    void on_mouseMovePoint(QPoint point);
    void on_mouseClicked(QPoint point);
    
private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
#include 

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    labViewCord = new QLabel("View coordinates: ");
    labViewCord->setMinimumWidth(150);
    ui->statusBar->addWidget(labViewCord);

    labSceneCord = new QLabel("Scene coordinates: ");
    labSceneCord->setMinimumWidth(150);
    ui->statusBar->addWidget(labSceneCord);

    labItemCord = new QLabel("Item coordinates: ");
    labItemCord->setMinimumWidth(150);
    ui->statusBar->addWidget(labItemCord);

    ui->graphicsView->setCursor(Qt::CrossCursor);
    ui->graphicsView->setMouseTracking(true);
    ui->graphicsView->setDragMode(QGraphicsView::RubberBandDrag);

    QObject::connect(ui->graphicsView, SIGNAL(mouseMovePoint(QPoint)), this, SLOT(on_mouseMovePoint(QPoint)));
    QObject::connect(ui->graphicsView, SIGNAL(mouseClicked(QPoint)), this, SLOT(on_mouseClicked(QPoint)));

    iniGraphicsSystem();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_mouseMovePoint(QPoint point)
{
    labViewCord->setText(QString::asprintf("View coordiantes: %d, %d", point.x(), point.y()));
    QPointF pointScene = ui->graphicsView->mapToScene(point);
    labSceneCord->setText(QString::asprintf("Scene coordinates: %.0f, %.0f", pointScene.x(), pointScene.y()));
}

void MainWindow::on_mouseClicked(QPoint point)
{
    QPointF pointScene = ui->graphicsView->mapToScene(point);
    QGraphicsItem *item = NULL;
    item = scene->itemAt(pointScene, ui->graphicsView->transform());
    if(item != NULL)
    {
        QPointF pointItem = item->mapFromScene(pointScene);
        labItemCord->setText(QString::asprintf("Item coordinates: %.0f, %.0f", pointItem.x(), pointItem.y()));
    }
}

void MainWindow::iniGraphicsSystem()
{
    // 构造Graphics View的各项
    QRectF rect(-200, -100, 400, 200);   // 矩形自身坐标系的左上角坐标,宽度,高度
    scene = new QGraphicsScene(rect);   // scene逻辑坐标系定义
    ui->graphicsView->setScene(scene);

    // 画一个矩形,大小等于scene
    QGraphicsRectItem *item = new QGraphicsRectItem(rect);
    item->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable);   // 设置flags

    QPen pen;
    pen.setWidth(2);
    item->setPen(pen);
    scene->addItem(item);

    // 画一个位于scene中心的椭圆,测试局部坐标
    QGraphicsEllipseItem *item2 = new QGraphicsEllipseItem(-100, -50, 200, 100);   // 椭圆自身坐标系的左上角坐标、宽度、高度
    item2->setPos(0, 0);   // 椭圆在场景中的位置
    item2->setBrush(QBrush(Qt::blue));
    item2->setFlags(QGraphicsItem::ItemIsMovable
                  | QGraphicsItem::ItemIsSelectable
                  | QGraphicsItem::ItemIsFocusable);
    scene->addItem(item2);

    // 画一个圆,中心位于scene的边缘
    QGraphicsEllipseItem *item3 = new QGraphicsEllipseItem(-50, -50, 100, 100);
    item3->setPos(rect.right(), rect.bottom());
    item3->setFlags(QGraphicsItem::ItemIsMovable
                  | QGraphicsItem::ItemIsSelectable
                  | QGraphicsItem::ItemIsFocusable);
    scene->addItem(item3);
    scene->clearSelection();
}

你可能感兴趣的:(C++,Qt)