图形视图框架

图形视图提供了一种管理、交互大量自定义2D图形元素以及用于可视化的视图小部件的表面,并支持缩放和旋转。

该框架包含一个事件传播架构,允许场景中元素具有精确的双精度交互能力。元素可以处理键事件、鼠标按下、移动、释放和双击事件,也可以跟踪鼠标移动。

图形视图使用BSP(二叉空间划分)树来提供非常快速的项目发现,因此它可以实时可视化大型场景,甚至包含数百万个元素。

图形视图是Qt 4.2中引入的,取代了其前身,QCanvas。

主题

图形视图架构

图形视图提供了一种基于项的模型-视图编程方法,类似于InterView的方便类QTableViewQTreeViewQListView。几个视图可以观察一个场景,场景包含各种不同几何形状的项目。

场景

QGraphicsScene提供了图形视图场景。场景具有以下职责

  • 提供管理大量项目的快速接口
  • 将事件传播到每个项目
  • 管理项目状态,如选择和焦点处理
  • 提供未转换的渲染功能;主要是用于打印

场景作为QGraphicsItem对象的容器。通过调用QGraphicsScene::addItem()将项目添加到场景中,然后通过调用众多项目发现函数之一来检索它们。 QGraphicsScene::items()及其重载返回包含或与点、矩形、多边形或通用矢量路径相交的所有项目。 QGraphicsScene::itemAt()返回特定点的最顶层的项目。所有项目发现函数都按降序堆叠顺序返回项目(即,第一个返回的项目是最顶部的,最后一个项目是最底部的)。

QGraphicsScene scene;
QGraphicsRectItem *rect = scene.addRect(QRectF(0, 0, 100, 100));

QGraphicsItem *item = scene.itemAt(50, 50, QTransform());

QGraphicsScene的事件传播架构安排场景事件以交付给元素,并管理元素之间的事件传播。如果场景在某个位置接收到鼠标按下事件,场景将事件传递给该位置的项目。

QGraphicsScene 还管理某些项目状态,例如项目选择和焦点。您可以通过调用 QGraphicsScene::setSelectionArea() 来在场景中选择项目,传入一个任意形状。此功能也是 QGraphicsView 中橡皮筋选择的依据。要获取所有当前选中项目的列表,请调用 QGraphicsScene::selectedItems()(). QGraphicsScene 还处理另一个状态,即项目是否具有键盘输入焦点。您可以通过调用 QGraphicsScene::setFocusItem() 或 QGraphicsItem::setFocus() 来将焦点设置在项目上,或通过调用 QGraphicsScene::focusItem() 来获取当前焦点项目。

最后,QGraphicsScene 允许您通过 QGraphicsScene::render() 函数将场景的一部分渲染到绘图设备中。您可以在文档后面的打印部分中了解更多关于此的信息。

视图

QGraphicsView 提供视图小部件,它可视化场景的内容。您可以将多个视图附加到同一场景,以提供同一数据集的多个视口。视图小部件是一个滚动区域,并提供滚动条来浏览大型场景。要启用 OpenGL 支持,您可以通过调用 QGraphicsView::setViewport() 将 QOpenGLWidget 设置为视口。

QGraphicsScene scene;
myPopulateScene(&scene);
QGraphicsView view(&scene);
view.show();

视图从键盘和鼠标接收输入事件,将这些事件转换为场景事件(在适当的情况下转换坐标),然后再将事件发送到可视化的场景。

使用其转换矩阵,QGraphicsView::transform(),视图可以 转换 场景的坐标系。这使得高级导航功能(如缩放和旋转)成为可能。为了方便,QGraphicsView 还提供了在视图和场景坐标之间转换的功能:QGraphicsView::mapToScene() 和 QGraphicsView::mapFromScene()。

项目

QGraphicsItem 是场景中图形项目的基类。图形视图提供了一些标准项目,用于典型形状,例如矩形(QGraphicsRectItem)、椭圆(QGraphicsEllipseItem)和文本项(QGraphicsTextItem),但是您编写自定义项目时可以享受到 QGraphicsItem 最强大的功能。在许多其他功能中,QGraphicsItem 支持以下功能

  • 鼠标按下、移动、释放和双击事件,以及鼠标悬停事件、轮事件和上下文菜单事件。
  • 键盘输入焦点和键事件
  • 拖放
  • 分组,通过父子关系以及 QGraphicsItemGroup
  • 碰撞检测

项目存在于一个局部坐标系中,并且与 QGraphicsView 类似,它还提供了许多映射项目与场景、场景与项目之间坐标的功能。同样,与 QGraphicsView 类似,它还可以使用矩阵来转换其坐标系:QGraphicsItem::transform()。这对于旋转和缩放单个项目非常有用。

项目可以包含其他项目(子项)。父项目的转换被其所有子项继承。尽管如此,无论项目的累积转换如何,所有函数(例如,QGraphicsItem::contains()、QGraphicsItem::boundingRect()、QGraphicsItem::collidesWith())仍然在局部坐标中操作。

QGraphicsItem 支持通过 QGraphicsItem::shape() 函数和虚拟函数 QGraphicsItem::collidesWith() 进行碰撞检测。通过从 QGraphicsItem::shape() 返回您项的形状作为一个局部坐标 QPainterPathQGraphicsItem 将为您处理所有的碰撞检测。但是,如果您想提供自己的碰撞检测,则可以重新实现 QGraphicsItem::collidesWith()。

图形视图框架中的类

这些类提供了一个创建交互式应用程序的框架。

QAbstractGraphicsShapeItem

路径项的通用基类

QGraphicsAnchor

表示在 QGraphicsAnchorLayout 中两个项之间的锚点

QGraphicsAnchorLayout

在图形视图中可以将小部件锚点在一起的区域布局

QGraphicsEffect

所有图形效果的基础类

QGraphicsEllipseItem

可以添加到 QGraphicsScene 中的椭圆项

QGraphicsGridLayout

用于在图形视图中管理小部件的栅格布局

QGraphicsItem

QGraphicsScene 中所有图形项的基类

QGraphicsItemGroup

将一组项视为单个项的容器

QGraphicsLayout

图形视图中所有布局的基类

QGraphicsLayoutItem

可以继承以允许您的自定义项被布局管理

QGraphicsLineItem

可以添加到 QGraphicsScene 中的线条项

QGraphicsLinearLayout

用于在图形视图中管理小部件的水平或垂直布局

QGraphicsObject

需要信号、槽和属性的所有图形项的基类

QGraphicsPathItem

可以添加到 QGraphicsScene 中的路径项

QGraphicsPixmapItem

可以添加到 QGraphicsScene 中的位图项

QGraphicsPolygonItem

可以添加到 QGraphicsScene 中的多边形项

QGraphicsProxyWidget

用于在 QGraphicsScene 中嵌入 QWidget 的代理层

QGraphicsRectItem

可以添加到 QGraphicsScene 中的矩形项

QGraphicsScene

用于管理大量二维图形项的表面

QGraphicsSceneContextMenuEvent

图形视图框架中的上下文菜单事件

QGraphicsSceneDragDropEvent

图形视图框架中的拖放事件

QGraphicsSceneEvent

所有图形视图相关事件的基类

QGraphicsSceneHelpEvent

请求工具提示时的事件

QGraphicsSceneHoverEvent

图形视图框架中的悬停事件

QGraphicsSceneMouseEvent

图形视图框架中的鼠标事件

QGraphicsSceneMoveEvent

图形视图框架中小部件移动的事件

QGraphicsSceneResizeEvent

图形视图框架中小部件调整大小的事件

QGraphicsSceneWheelEvent

图形视图框架中的滚轮事件

QGraphicsSimpleTextItem

可以添加到 QGraphicsScene 中的简单文本路径项

QGraphicsSvgItem

可用于渲染 SVG 文件内容的 QGraphicsItem

QGraphicsTextItem

可以添加到 QGraphicsScene 中显示格式化文本的文本项

QGraphicsTransform

在 QGraphicsItems 上构建高级转换的抽象基类

QGraphicsView

用于显示 QGraphicsScene 内容的 Widget

QGraphicsWidget

QGraphicsScene 中所有小部件项的基类

QStyleOptionGraphicsItem

用于描述绘制 QGraphicsItem 所需的参数

图形视图坐标系

图形视图基于笛卡尔坐标系;项在场景中的位置和几何形状由两个数字的集合表示:x 坐标和 y 坐标。当使用未变换的视图观察场景时,场景中的一个单位表示屏幕上的一个像素。

注意:倒置Y轴坐标系(其中 y 向上增长)不受支持,因为图形视图使用 Qt 坐标系。

图形视图中有三个有效的坐标系:项坐标系、场景坐标系和视图坐标系。为了简化您的实现,图形视图提供了方便的函数,允许您在这三个坐标系之间进行映射。

在渲染时,图形视图的场景坐标系对应于 QPainter逻辑 坐标,而视图坐标系与 设备 坐标相同。在 坐标系 文档中,您可以了解逻辑坐标和设备坐标之间的关系。

项坐标系

项生活在它们自己的局部坐标系中。它们的坐标通常以中心点 (0, 0) 为中心,这也是所有变换的中心。在项坐标系中的几何原语常被称为项点、项线或项矩形。

当创建自定义项时,您只需要关心项坐标; QGraphicsSceneQGraphicsView 将为您执行所有变换。这使得实现自定义项变得非常简单。例如,如果您接收鼠标按下或拖动进入事件,事件位置将以项坐标给出。返回 true 如果某一点位于您的项中,否则返回 false 的 QGraphicsItem::contains() 虚拟函数,将接受一个项坐标的点参数。同样,项的边界矩形和形状也在项坐标系中。

项的 位置 是项中心点在其父级坐标系中的坐标;有时也称为 父级 坐标。在此意义上,场景被视为所有无父级项的“父级”。顶级项的位置在场景坐标系中。

子项坐标相对于父级坐标。如果子项未进行变换,则子项坐标与父项坐标之间的差与在父项坐标中的项之间的距离相同。例如:如果一个未变换的子项精确地定位在父项的中心点,则这两个项的坐标系将是相同的。然而,如果子项的位置是 (10, 0),那么子项的 (0, 10) 点将与父项的 (10, 10) 点相对应。

由于项的位置和变换相对于父项,因此即使父项隐式地变换了子项,子项的坐标也不受父项变换的影响。在上面的例子中,即使父项被旋转和缩放,子项的 (0, 10) 点仍然将与父项的 (10, 10) 点相对应。然而,相对于场景,子项将跟随父项的变换和位置。如果父项以 (2x, 2x) 缩放,则子项的位置将位于场景坐标 (20, 0),它的 (10, 0) 点将对应场景上的点 (40, 0)。

除了 QGraphicsItem::pos() 是少数例外之一外, QGraphicsItem 的函数以项坐标操作,而不论项,或其任何父项的变换。例如,项的边界矩形(即 QGraphicsItem::boundingRect())始终以项坐标给出。

场景坐标系

场景表示所有物品的基础坐标系。场景坐标系描述每个顶级物品的位置,并为从视图发送到场景的所有场景事件提供基础。场景上的每个物品都有一个场景位置和边界矩形(QGraphicsItem::scenePos(),QGraphicsItem::sceneBoundingRect()),除了本地物品位置和边界矩形外。场景位置描述物品在场景坐标系中的位置,其场景边界矩形是 QGraphicsScene 确定场景哪些区域已更改的基础。场景的更改通过 QGraphicsScene::changed() 信号进行通信,传入的是一个场景矩形的列表。

视图坐标

视图坐标是部件的坐标。视图坐标中的每个单位对应一个像素。这个坐标系的特殊之处在于,它与部件(或视口)相关,不受所观测场景的影响。QGraphicsView 的视口的左上角始终是(0,0),右下角始终是(视口宽度,视口高度)。所有鼠标事件和拖放事件最初都以视图坐标接收,您需要将这些坐标映射到场景才能与物品交互。

坐标映射

在处理场景中的物品时,通常将坐标和任意形状从场景映射到物品,或者从物品到物品,或者从视图到场景是有用的。例如,当您在 QGraphicsView 的视口中点击鼠标时,您可以通过调用 QGraphicsView::mapToScene () 并随后调用 QGraphicsScene::itemAt () 来询问场景光标下有什么物品。如果您想知道物品在视口中位于何处,您可以调用物品上的 QGraphicsItem::mapToScene (),然后调用视图上的 QGraphicsView::mapFromScene ()。最后,如果您想找到视图中椭圆内的物品,可以将 QPainterPath 传递给 mapToScene (),然后将映射后的路径传递给 QGraphicsScene::items ()。

您可以通过调用 QGraphicsItem::mapToScene () 和 QGraphicsItem::mapFromScene () 来将坐标和形状映射到(从)一个物品的场景。您还可以通过调用 QGraphicsItem::mapToParent () 和 QGraphicsItem::mapFromParent () 将它们映射到(从)一个物品的父级物品,或者通过调用 QGraphicsItem::mapToItem () 和 QGraphicsItem::mapFromItem () 之间映射。所有映射函数都可以映射点、矩形、多边形和路径。

视图中也提供了相同的映射函数,用于将坐标和形状映射到(从)场景。有 QGraphicsView::mapFromScene () 和 QGraphicsView::mapToScene ()。要将视图映射到物品,您首先将映射到场景,然后再将场景映射到物品。

主要功能

缩放和旋转

QGraphicsView 支持与 QPainter 相同的仿射变换,通过 QGraphicsView::setMatrix() 实现。通过向视图中应用变换,您可以轻松地添加对常见导航功能的支持,例如缩放和旋转。

以下是如何在 QGraphicsView 的子类中实现缩放和旋转槽的示例

class View : public QGraphicsView
{
Q_OBJECT
    ...
public slots:
    void zoomIn() { scale(1.2, 1.2); }
    void zoomOut() { scale(1 / 1.2, 1 / 1.2); }
    void rotateLeft() { rotate(-10); }
    void rotateRight() { rotate(10); }
    ...
};

槽可以连接到启用QToolButtonsautoRepeat

QGraphicsView在变换视图时保持视图中心对齐。

请参阅弹性节点示例,了解如何实现基本的缩放功能。

打印

图形视图通过其渲染函数QGraphicsScene::render()和QGraphicsView::render()提供单行打印。这些函数提供相同的API:您可以通过将QPainter传递给任一渲染函数,将场景或视图的全部或部分内容渲染到任何绘图设备。此示例展示了如何使用QPrinter将整个场景打印到一整页。

QGraphicsScene scene;
QPrinter printer;
scene.addRect(QRectF(0, 0, 100, 200), QPen(Qt::black), QBrush(Qt::green));

if (QPrintDialog(&printer).exec() == QDialog::Accepted) {
    QPainter painter(&printer);
    painter.setRenderHint(QPainter::Antialiasing);
    scene.render(&painter);
}

场景和视图渲染函数之间的区别在于,一个在场景坐标下操作,另一个在视图坐标下操作。QGraphicsScene::render()通常更适用于打印未变换的场景片段,例如用于绘制几何数据或打印文本文档。另一方面,QGraphicsView::render()适合用于截屏;其默认行为是使用提供的绘图器渲染视图窗口的确切内容。

QGraphicsScene scene;
scene.addRect(QRectF(0, 0, 100, 200), QPen(Qt::black), QBrush(Qt::green));

QPixmap pixmap;
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
scene.render(&painter);
painter.end();

pixmap.save("scene.png");

当源区域和目标区域的大小不匹配时,源内容将被拉伸以适应目标区域。通过将Qt::AspectRatioMode传递给您使用的渲染函数,您可以选择在内容拉伸时保留或忽略场景的纵横比。

拖放

由于QGraphicsView间接继承自QWidget,因此它已经提供了QWidget提供的相同拖放功能。此外,为了方便起见,图形视图框架为场景以及每个项目提供了拖放支持。当视图接收到拖动事件时,它将拖放事件转换为QGraphicsSceneDragDropEvent,然后将其转发到场景。场景接管此事件的处理,并将其发送到鼠标光标下方接受拖放操作的第一个项目。

要从项目开始拖放,请创建一个QDrag对象,并传递一个指向开始拖放的窗口的指针。项目可以由多个视图同时观察,但只能有一个视图开始拖放。大多数情况下,拖放是通过按下或移动鼠标开始的,因此您可以在mousePressEvent()或mouseMoveEvent()中从事件中获取起源窗口指针。例如

void CustomItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    QMimeData *data = new QMimeData;
    QDrag *drag = new QDrag(event->widget());
    drag->setMimeData(data);
    drag->exec();
}

要拦截场景的拖放事件,请在QGraphicsScene事件处理器的文档中,重新实现QGraphicsScene::dragEnterEvent()以及您的特定场景需要的任何事件处理器。

项目可以通过调用QGraphicsItem::setAcceptDrops()来启用拖放支持。要处理传入的拖放,请重新实现QGraphicsItem::dragEnterEvent(),QGraphicsItem::dragMoveEvent(),QGraphicsItem::dragLeaveEvent()和QGraphicsItem::dropEvent()。

请参阅拖放机器人示例,了解图形视图对拖放操作的支持。

光标和工具提示

QWidget类似,QGraphicsItem也支持光标(QGraphicsItem::setCursor())和工具提示(QGraphicsItem::setToolTip())。光标和工具提示在鼠标光标进入项目区域(通过调用QGraphicsItem::contains()检测)时由QGraphicsView激活。

您也可以通过调用QGraphicsView::setCursor直接在视图中设置默认光标。

请参阅拖放机器人示例,以了解实现工具提示和光标形状处理的代码。

动画

图形视图在不同级别支持下进行动画处理。您可以通过使用动画框架轻松组装动画。为此,您需要使项目继承自QGraphicsObject,并与QPropertyAnimation关联。 QPropertyAnimation允许动画化任何QObject属性。

另一种选项是创建一个继承自QObjectQGraphicsItem的自定义项目。项目可以设置自己的计时器,并通过在QObject::timerEvent中处理增量步来控制动画。

第三种选项,主要是为了与Qt 3中的QCanvas兼容,是通过调用QGraphicsScene::advance场景,这又反过来调用了QGraphicsItem::advance

OpenGL渲染

要启用OpenGL渲染,您只需将新的QOpenGLWidget设置为你QGraphicsView的视口,通过调用QGraphicsView::setViewport完成。如果您希望OpenGL具有抗锯齿功能,需要设置一个具有所需样本数量的QSurfaceFormat(请参阅QSurfaceFormat::setSamples)。

示例

QGraphicsView view(&scene);
QOpenGLWidget *gl = new QOpenGLWidget();
QSurfaceFormat format;
format.setSamples(4);
gl->setFormat(format);
view.setViewport(gl);

项目分组

通过使一个项目成为另一个项目的子项目,您可以实现项目分组的最重要的功能:项目将一起移动,所有变换都从父项目传播到子项目。

另外,QGraphicsItemGroup是一个特殊的项目,它将子事件处理与添加和删除项目到组的有用接口相结合。将项目添加到QGraphicsItemGroup将保持项目的原始位置和变换,而一般重新放置项目将使子项目相对于其新父项目重新定位。为了方便起见,您可以通过调用QGraphicsScene::createItemGroup在场景中创建QGraphicsItemGroup

小部件和布局

Qt 4.4引入了对通过QGraphicsWidget的几何和布局感知项目的支持。这个特殊的基类小部件类似于QWidget,但与QWidget不同,它不继承自QPaintDevice,而是继承自QGraphicsItem。这允许您编写具有事件、信号 & slots、大小提示和策略的完整小部件,您还可以通过QGraphicsLinearLayoutQGraphicsGridLayout在布局中管理您的布局几何图形。

QGraphicsWidget

基于 QGraphicsItem 的功能和简洁的性能,QGraphicsWidget 综合了两者的优点:从 QWidget 获得额外功能,如样式、字体、调色板、布局方向和几何形状,以及来自 QGraphicsItem 的分辨率独立性和变换支持。由于图形视图使用真实坐标而不是整数,QGraphicsWidget 的几何函数也作用于 QRectFQPointF。这也适用于边框矩形、边距和间隔。例如,使用 QGraphicsWidget 可以指定 (0.5, 0.5, 0.5, 0.5) 的内容边距。您不仅可以创建子小部件,还可以创建“顶级”窗口;在某些情况下,现在您可以使用图形视图进行高级 MDI 应用程序。

QWidget 的一些属性被支持,包括窗口标志和属性,但并非全部。您应该参考 QGraphicsWidget 的类文档以完整了解哪些支持哪些不支持。例如,您可以通过将 Qt::Window 窗口标志传递到 QGraphicsWidget 的构造函数来创建装饰窗口,但图形视图当前不支持在 macOS 中常见的 Qt::SheetQt::Drawer 标志。

QGraphicsLayout

QGraphicsLayout 是为 QGraphicsWidget 设计的第二代布局框架的一部分。它的 API 非常类似于 QLayout。您可以在 QGraphicsLinearLayoutQGraphicsGridLayout 中管理小部件和子布局。您还可以通过自己 subclass QGraphicsLayout 来轻松编写自己的布局,或者通过编写 QGraphicsLayoutItem 的适配器 subclass 向布局添加自己的 QGraphicsItem 项目。

嵌入小部件支持

图形视图无缝支持将任何小部件嵌入到场景中。您可以将简单的小部件,如 QLineEditQPushButton 嵌入,复杂的小部件,如 QTabWidget,甚至是完整的窗口。要将小部件嵌入场景,只需调用 QGraphicsScene::addWidget(),或者创建 QGraphicsProxyWidget 的实例以手动嵌入小部件。

通过 QGraphicsProxyWidget,图形视图能够深入集成客户端小部件的特点,包括其光标、工具提示、鼠标、平板和键盘事件、子小部件、动画、弹出窗口(例如,QComboBoxQCompleter),以及小部件的输入焦点和激活。甚至可以将嵌入的小部件的标签顺序整合到 QGraphicsProxyWidget 中,以便您可以切换到和从嵌入的小部件中脱离。您甚至可以将新的 QGraphicsView 嵌入您的场景以提供复杂的嵌套场景。

在变换嵌入小部件时,图形视图确保小部件在无分辨率依赖性的情况下被变换,从而使字体和样式在放大时保持清晰。(请注意,无分辨率依赖性的效果取决于样式。)

性能

浮点指令

为了准确快速地应用变换和效果到元素上,图形视图是基于用户硬件能够提供合理的浮点指令性能的前提下构建的。

许多工作站和台式计算机配备了适合的硬件来加速此类计算,但某些嵌入式设备可能只提供用于处理数学运算或软件中模拟浮点指令的库。

因此,某些效果在特定设备上可能比预期运行得慢。可以通过在其他领域进行优化来弥补这种性能损失;例如,通过使用 OpenGL 渲染场景。然而,如果这类优化也依赖于浮点硬件的存在,那么它们本身也可能导致性能下降。

© 2024 Qt公司。本文档中的文档贡献版权属于各自的拥有者。本文档的提供是根据由自由软件基金会发布的 GNU 自由文档许可证 1.3 版本许可的。Qt及其相应的标志是芬兰和/或其他国家/地区的 Qt 公司的商标。所有其他商标均为其各自所有者的财产。