警告
本节包含自动从 C++ 翻译到 Python 的代码片段,可能存在错误。
图形视图框架#
交互式 2D 图形的图形视图框架概述。
图形视图提供了一块管理交互式和大量自定义 2D 图形项的表面,以及支持缩放和旋转的查看小部件。
框架包括一个事件传播架构,它为场景中的项提供了精确的双精度交互能力。项可以处理按键事件、鼠标按下、移动、释放和双击事件,并且它们还可以跟踪鼠标移动。
图形视图使用 BSP(二叉空间划分)树来提供非常快速的项目发现,因此它可以实时可视化大型场景,甚至是数百万个项。
图形视图自 Qt 4.2 以来就被引入,取代了它的前辈 QCanvas。
主题
图形视图架构#
图形视图提供了基于项的模型-视图编程方法,类似于 InterView 的便捷类 QTableView
, QTreeView
和 QListView
。多个视图可以观察一个场景,场景包含各种几何形状的项。
场景#
QGraphicsScene
提供图形视图场景。场景有以下职责
提供快速接口来管理大量项
将事件传播到每个项
管理项状态,如选择和焦点处理
提供未变换的渲染功能;主要用于打印
场景是 QGraphicsItem
对象的容器。通过调用 addItem()
将项添加到场景中,然后通过调用多个项目发现函数之一来检索。 items()
和它的重载返回一个点、矩形、多边形或通用矢量路径包含或与之相交的所有项。 itemAt()
返回特定点的最顶层项。所有项发现函数按降序堆栈顺序返回项(即首先返回的项是最顶层的,最后一个项是最低层的)。
scene = QGraphicsScene() rect = scene.addRect(QRectF(0, 0, 100, 100)) item = scene.itemAt(50, 50, QTransform())
QGraphicsScene
的事件传播架构负责安排场景事件以交付给项目,并管理项目间的事件传播。如果场景在某个位置接收到鼠标按下事件,场景会将事件传递给该位置上的项目。
QGraphicsScene
还管理某些项目状态,例如项目选择和焦点。您可以通过调用 setSelectionArea()
来在场景上选择项目,传递任意形状。该功能也是 QGraphicsView
中的橡皮筋选择的基石。要获取所有当前所选项目的列表,请调用 selectedItems()
。QGraphicsScene
还处理的项目状态之一是项目是否有键盘输入焦点。您可以通过调用 setFocusItem()
或 setFocus()
来将焦点设置在项目上,或者通过调用 focusItem()
来获取当前焦点项目。
最后,QGraphicsScene
允许您通过 render()
函数将场景的一部分渲染到绘图设备。您可以在本文件的打印部分中了解更多关于此的信息。
视图#
QGraphicsView
提供了视图小部件,用于可视化场景的内容。您可以将多个视图附加到同一个场景,为相同的数据集提供多个视口。视图小部件是一个滚动区域,并为导航大型场景提供滚动条。为了启用OpenGL支持,您可以通过调用 setViewport()
将 QOpenGLWidget 设置为视口。
scene = QGraphicsScene() myPopulateScene(scene) view = QGraphicsView(scene) view.show()
视图接收来自键盘和鼠标的输入事件,将这些事件转换为场景事件(在合适的情况下转换使用的坐标到场景坐标),然后将事件发送到可视化的场景。
使用它的变换矩阵,transform(),视图可以转换场景的坐标系。这允许高级导航功能,如缩放和旋转。为了方便起见,QGraphicsView
还提供了在视图和场景坐标之间转换的函数:mapToScene()和mapFromScene()。
项目
QGraphicsItem
是场景中图形项的基类。Graphic View提供了一系列标准项,用于典型形状,如矩形(QGraphicsRectItem
)、椭圆(QGraphicsEllipseItem
)和文本项(QGraphicsTextItem
),但当你编写自定义项时,最强大的QGraphicsItem
功能即可使用。和其他事情一样,QGraphicsItem
支持以下功能
鼠标按下、移动、释放和双击事件,以及鼠标悬停事件、滚轮事件和上下文菜单事件。
键盘输入焦点和按键事件
拖放
组合,无论是通过父子关系还是通过
QGraphicsItemGroup
碰撞检测
项生活在局部坐标系中,与QGraphicsView
一样,它也提供了许多在项和场景、项与项之间映射坐标的函数,并且,就像QGraphicsView
一样,它可以使用矩阵转换其坐标系:transform()。这对于旋转和缩放单个项很有用。
项目可以包含其他项目(子项目)。父项目的变换会继承给所有子项目。然而,无论一个项目累计的变换如何,其所有功能(例如,contains()
, boundingRect()
,QGraphicsItem::collidesWith())仍然在本地坐标下操作。
QGraphicsItem
通过shape()
函数支持通过shape()
函数返回您的项目形状作为本地坐标 QPainterPath 来进行碰撞检测,以及 QGraphicsItem
的 QGraphicsItem::collidesWith(),这两个都是虚拟函数。通过从 QGraphicsItem
返回您的项的形状作为局部坐标 QPainterPath,QGraphicsItem
将为您处理所有碰撞检测。然而,如果您想提供自己的碰撞检测,则可以重写 QGraphicsItem::collidesWith() 函数。
图形视图框架中的类#
这些类提供了一个创建交互式应用程序的框架。
QGraphicsEffect 类是所有图形效果的基类。
QGraphicsAnchorLayout 类提供了一种布局,在其中可以在图形视图中结合小部件。
QGraphicsAnchor 类代表在 QGraphicsAnchorLayout 中的两个项目之间的锚点。
QGraphicsGridLayout 类为在图形视图中管理小部件提供了网格布局。
QGraphicsItem 类是 QGraphicsScene 中所有图形项目的基类。
QGraphicsObject 类为所有需要信号、槽和属性的所有图形项提供了一个基类。
QAbstractGraphicsShapeItem 类为所有路径项提供了一个公共基类。
QGraphicsPathItem 类提供了一个路径项,您可以将其添加到 QGraphicsScene。
QGraphicsRectItem 类提供了一个矩形项,您可以将其添加到 QGraphicsScene。
QGraphicsEllipseItem 类提供了一个椭圆项,您可以将其添加到 QGraphicsScene。
QGraphicsPolygonItem 类提供了一个多边形项,您可以将其添加到 QGraphicsScene。
QGraphicsLineItem 类提供了一个线项,您可以将其添加到 QGraphicsScene。
QGraphicsPixmapItem 类提供了一个可以将图像添加到 QGraphicsScene 的图元。
QGraphicsTextItem 类提供了一个可以添加到 QGraphicsScene 显示格式化文本的文本项。
QGraphicsSimpleTextItem 类提供了一个简单的文本路径项,您可以将其添加到 QGraphicsScene。
QGraphicsItemGroup 类提供了一个将一组项作为单个项处理的容器。
QGraphicsItemAnimation 类为 QGraphicsItem 提供了简单的动画支持。
QGraphicsLayout类为Graphics View中的所有布局提供了基类。
QGraphicsLayoutItem类可以被继承,以便布局管理您自定义的项。
QGraphicsLinearLayout类为在Graphics View中管理小部件提供了水平或垂直布局。
QGraphicsProxyWidget类提供了在QGraphicsScene中嵌入QWidget的代理层。
QGraphicsScene类为管理大量2D图形项提供了一个平面。
QGraphicsSceneBspTreeIndex类提供了QGraphicsScene中查找项的BSP索引算法的实现。
QGraphicsSceneEvent类为所有与图形视图相关的事件提供了一个基类。
QGraphicsSceneMouseEvent类提供了图形视图框架中的鼠标事件。
QGraphicsSceneWheelEvent类提供了图形视图框架中的滚轮事件。
QGraphicsSceneContextMenuEvent类提供了图形视图框架中的右键菜单事件。
QGraphicsSceneHoverEvent类提供了图形视图框架中的悬停事件。
QGraphicsSceneHelpEvent类在请求工具提示时提供事件。
QGraphicsSceneDragDropEvent类在图形视图框架中提供了拖放事件。
QGraphicsSceneResizeEvent类在图形视图框架中提供了控件大小调整事件。
QGraphicsSceneMoveEvent类在图形视图框架中提供了控件移动事件。
The QGraphicsSceneIndex类提供了一个基类来实现QGraphicsScene中查找项的自定义索引算法。
The QGraphicsSceneLinearIndex类提供了QGraphicsScene中查找项的线性索引算法的实现。
QGraphicsTransform类是在QGraphicsItems上构建高级变换的抽象基类。
QGraphicsView类提供了显示QGraphicsScene内容的控件。
QGraphicsWidget类是QGraphicsScene中所有小部件的基类。
QStyleOptionGraphicsItem类用于描述绘制QGraphicsItem所需的参数。
图形视图坐标系统#
图形视图基于笛卡尔坐标系;场景中项的位置和几何形状由一组两个数表示:x坐标和y坐标。当使用未变换的视图观察场景时,场景中的每一个单位由屏幕上的一个像素表示。
注意
不支持的倒Y轴坐标系(其中y向上增长),因为图形视图使用Qt的坐标系。
图形视图中同时作用着三个有效的坐标系:项目坐标、场景坐标和视图坐标。为了简化您的实现,图形视图提供了方便的函数,这些函数允许您在三个坐标系之间进行映射。
在渲染时,图形视图的场景坐标对应于QPainter的逻辑坐标,视图坐标与设备坐标相同。在坐标系文档中,您可以了解逻辑坐标和设备坐标之间的关系。
项目坐标#
项目都存在自己的局部坐标系中。它们的坐标通常以自己的中心点(0, 0)为中心,这也是所有变换的中心。在项目坐标系中的几何原始数据通常被称为项目点、项目线或项目矩形。
当创建自定义项目时,您只需关注项目坐标即可;QGraphicsScene
和 QGraphicsView
将为您执行所有变换。这使得实现自定义项目变得非常容易。例如,如果收到鼠标点击或拖动进入事件,事件位置是在项目坐标中给出的。contains()
虚拟函数,如果特定点在您的项目中,则返回 true
,否则返回 false,取的是一个点参数,在项目坐标中。类似地,一个项目的边界矩形和形状也是在项目坐标中。
项目的位置是指项目中心点在父坐标系中的坐标;有时称为父坐标。在这种意义上,场景被视为所有无父项的“父”。顶级项目的位置在场景坐标中。
子坐标相对于父坐标。如果子项未变换,则子坐标与父坐标之间的差值与项目在父坐标中的距离相同。例如:如果一个未变换的子项目精确定位在其父项目的中心点,则两个项目的坐标系将相同。然而,如果子项的位置是(10, 0),则子项的(0, 10)点将对应于父项的(10, 10)点。
由于项目位置和变换相对于父项,子项目坐标不受父项变换的影响,尽管父项的变换隐式地变换了子项。在上面的例子中,即使父项被旋转和缩放,子项的(0, 10)点仍然对应于父项的(10, 10)点。然而,相对于场景,子项将跟随父项的变换和位置。如果父项被缩放(2x, 2x),则子项的位置将位于场景坐标(20, 0),其(10, 0)点将对应于场景上的点(40, 0)。
由于 pos()
是少数例外之一,QGraphicsItem
的函数在任何项目或其父项目的变换中都在项目坐标中操作。例如,一个项目的边界矩形(即 boundingRect()
)总是给出在项目坐标中。
场景坐标#
场景代表其所有项的基本坐标系。场景坐标系描述了每个顶级项的位置,同时也形成了从视图发送到场景的所有场景事件的基准。场景上的每个项都有一个场景位置和边界矩形(scenePos()
, sceneBoundingRect()
),除了其局部项位置和边界矩形。场景位置描述了项在场景坐标中的位置,其场景边界矩形是QGraphicsScene
确定场景哪些区域已改变的基础。场景的更改通过changed()
信号进行通信,参数是一系列场景矩形。
视图坐标#
视图坐标是小部件的坐标。视图坐标中的每个单位对应一个像素。这个坐标系特别之处在于它是相对于小部件或视口的,不受观察场景的影响。QGraphicsView
视口的左上角始终为(0, 0),右下角始终为(视口宽度, 视口高度)。所有鼠标事件和拖放事件最初都以视图坐标接收,你需要将这些坐标映射到场景中才能与项交互。
坐标映射#
在处理场景中的项时,映射场景到项、项到项或视口到场景的坐标和任意形状往往非常有用。例如,当你在QGraphicsView
的视口中单击鼠标时,你可以通过调用mapToScene()
然后调用itemAt()
来询问场景光标下是什么项。如果你想知道项在视口中的位置,可以在项上调用mapToScene()
,然后对视图调用mapFromScene()
。最后,如果你想找出视图椭圆内包含哪些项,可以将QPainterPath传递给mapToScene(),然后将映射后的路径传递给items()
。
您可以调用 mapToScene()
和 mapFromScene()
对象的场景坐标和形状进行映射。您还可以通过调用 mapToParent()
和 mapFromParent()
,或将它们之间的项目映射到父级项目。您还可以通过调用 mapToItem()
和 mapFromItem()
来在项目之间进行映射。所有映射函数都可以映射点、矩形、多边形和路径。
视图中也提供了相同的映射函数,用于场景和视图之间的映射。 mapFromScene()
和 mapToScene()
。要从视图映射到项目,您首先映射到场景,然后从场景映射到项目。
主要功能#
缩放和旋转#
QGraphicsView
通过 QGraphicsView::setMatrix() 支持与 QPainter 相同的仿射变换。通过应用视图上的变换,您可以轻松地添加支持缩放和旋转等常见导航功能。
以下是在 QGraphicsView
的子类中实现缩放和旋转槽的示例
class View(QGraphicsView): Q_OBJECT ... # public slots def zoomIn(scale(1.2, 1.2): def zoomOut(1.2, 1.2): def rotateLeft(rotate(-10): def rotateRight(rotate(10): ...
这些槽可以连接到具有 QToolButtons
和 autoRepeat
启用。
QGraphicsView
在变换视图时保持视图中心的对齐。
有关实现基本缩放功能的代码示例,请参阅 弹性节点 例子。
打印#
图形视图通过其渲染函数提供了单行打印,包括 render()
和 render()
。这两个函数提供了相同的API:您可以通过传递 QPainter 对象到任一渲染函数,让场景或视图将全部或部分内容渲染到任何绘图设备中。以下示例展示了如何使用 QPrinter 将整个场景打印到全页。
scene = QGraphicsScene() printer = QPrinter() scene.addRect(QRectF(0, 0, 100, 200), QPen(Qt.black), QBrush(Qt.green)) if QPrintDialog(printer).exec() == QDialog.Accepted: painter = QPainter(printer) painter.setRenderHint(QPainter.Antialiasing) scene.render(painter)
场景与视图渲染函数之间的区别在于,一个在场景坐标下工作,另一个在视图坐标下工作。通常,render()
更适合用于打印场景中未经变换的整个部分,例如用于绘图几何数据或打印文本文档。另一方面,render()
适合用于截图;其默认行为是使用提供的画家来渲染视口中确切的内容。
scene = QGraphicsScene() scene.addRect(QRectF(0, 0, 100, 200), QPen(Qt.black), QBrush(Qt.green)) pixmap = QPixmap() painter = QPainter(pixmap) painter.setRenderHint(QPainter.Antialiasing) scene.render(painter) painter.end() pixmap.save("scene.png")
当源区域和目标区域的大小不匹配时,源内容会被拉伸以适应目标区域。通过将 Qt::AspectRatioMode 传递到您正在使用的渲染函数,您可以选择在内容拉伸时保持或忽略场景的纵横比。
拖放#
由于 QGraphicsView
间接继承自 QWidget
,它已经提供了与 QWidget
相同的拖放功能。此外,作为便利,图形视图框架为场景以及每个单独的项目提供了拖放支持。当视图接收到拖放事件时,它会将拖放和放下事件转换为 QGraphicsSceneDragDropEvent
,然后该事件被转发到场景。场景接管该事件的调度,并将它发送到鼠标光标下的第一个接受放下的项目。
要从项目开始拖放,创建一个 QDrag 对象,并传递一个指向开始拖放的窗口小部件的指针。项目可以被许多视图同时观察,但只有一个视图可以开始拖放。在大多数情况下,拖放会因为按下或移动鼠标而启动,因此在 mousePressEvent() 或 mouseMoveEvent() 中,您可以从中得到事件的起始窗口小部件指针。例如
def mousePressEvent(self, event): data = QMimeData() drag = QDrag(event.widget()) drag.setMimeData(data) drag.exec()
为了拦截场景的拖放事件,你需要在 dragEnterEvent()
和特定场景所需的任何事件处理程序中重新实现,在一个 QGraphicsItem
子类中。你可以在 QGraphicsScene
的事件处理程序文档中了解更多关于图形视图中的拖放信息。
物品可以通过调用 setAcceptDrops()
来启用拖放支持。为了处理传入的拖放事件,你需要重新实现 dragEnterEvent()
、dragMoveEvent()
、dragLeaveEvent()
,以及 dropEvent()
。
请参阅 Drag and Drop Robot 示例,以演示图形视图对拖放操作的支持。
光标和工具提示#
类似于 QWidget
, QGraphicsItem
也可以支持光标( setCursor()
)和工具提示( setToolTip()
)。光标和工具提示由 QGraphicsView
在鼠标光标进入物品区域时激活(通过调用 contains()
检测)。
您还可以通过调用 setCursor()
来直接在视图中设置默认光标。
请参阅 Drag and Drop Robot 示例以了解更多关于实现工具提示和光标形状处理的代码。
动画#
图形视图在多个层面上支持动画。你可以通过使用动画框架轻松组装动画。为此,你需要让你的物品继承自 QGraphicsObject
并将 QPropertyAnimation 与它们关联。QPropertyAnimation 允许动画化任何 QObject 属性。
另一种方法是创建一个继承自 QObject 和 QGraphicsItem
的自定义项目。该项目可以设置自己的计时器,并在 QObject::timerEvent() 中通过增量步骤控制动画。
第三种选项(主要是为了与 Qt 3 中的 QCanvas 兼容),是通过调用 advance()
来 推进 场景的,这又会调用 advance()
方法。
OpenGL 渲染#
要启用 OpenGL 渲染,只需通过调用 setViewport()
将新的 QOpenGLWidget 设置为 QGraphicsView
的视口。如果您想要带有抗锯齿的 OpenGL,需要设置一个含有所需样本数的 QSurfaceFormat(见 QSurfaceFormat::setSamples())。
示例
view = QGraphicsView(scene) gl = QOpenGLWidget() format = QSurfaceFormat() format.setSamples(4) gl.setFormat(format) view.setViewport(gl)
项目组#
通过将一个项目作为另一个项目的子项目,您可以实现项目分组的最基本功能:项目将一起移动,所有转换都会从父项传递到子项。
此外,QGraphicsItemGroup
是一个特殊项目,它结合了子事件处理,并提供了用于向组和从组添加和删除项目的有用接口。将项目添加到 QGraphicsItemGroup
将保持该项目的原始位置和转换,而通常重新定位项目会导致子项目与其新父项相对于重新定位。为了方便起见,您可以通过调用 createItemGroup()
来通过场景创建 QGraphicsItemGroup
。
小部件和布局#
Qt 4.4 引入了通过 QGraphicsWidget
对象对几何形状和布局感知项的支持。这个特殊的基类项目类似于 QWidget
,但与 QWidget
不同,它不是从 QPaintDevice 继承,而是从 QGraphicsItem
继承。这允许您编写具有事件、信号 & 插槽、大小提示和策略的完整小部件,您还可以通过 QGraphicsLinearLayout
和 QGraphicsGridLayout
在布局中管理您的部件几何形状。
QGraphicsWidget#
在 QGraphicsItem
的功能和轻量级的基础上,QGraphicsWidget
提供了最佳的双赢:从 QWidget
获得额外的功能,如样式、字体、调色板、布局方向及其几何形状,以及从 QGraphicsItem
得到的分辨率无关性和变换支持。由于图形视图使用真实坐标而不是整数,QGraphicsWidget
的几何函数也操作在 QRectF 和 QPointF 上。这也适用于边框矩形、边距和间距。使用 QGraphicsWidget
时,指定 (0.5, 0.5, 0.5, 0.5) 的内容边距并不罕见。您可以创建子部件和“顶级”窗口;在某些情况下,现在可以使用图形视图进行高级 MDI 应用。
部分QWidget的属性被支持,包括窗口标志和属性,但并非全部。您应参阅QGraphicsWidget的文档,以获取对支持和不支持功能的全面了解。例如,您可以通过将Qt::Window窗口标志传递给QGraphicsWidget的构造函数来创建装饰窗口,但图形视图当前不支持在macOS上常用的Qt::Sheet和Qt::Drawer标志。
QGraphicsLayout#
QGraphicsLayout
是为QGraphicsWidget设计的第二代布局框架的一部分。它的API与QLayout非常相似。您可以在QGraphicsLinearLayout和QGraphicsGridLayout内部管理小工具和子布局。您还可以通过扩展QGraphicsLayout自己轻松编写布局,或在布局中添加自己的QGraphicsItem项,通过编写QGraphicsLayoutItem的适配器子类。
嵌入式小工具支持#
图形视图无缝支持将任何小工具嵌入到场景中。您可以嵌入简单的工具,如QLineEdit或QPushButton,复杂的小工具,如QTabWidget,甚至完整的主窗口。要将您的工具嵌入场景,只需调用addWidget()或创建一个QGraphicsProxyWidget的实例以手动嵌入您的工具。
通过QGraphicsProxyWidget
,图形视图能够深度整合客户端小部件的功能,包括其光标、工具提示、鼠标、平板和键盘事件、子小部件、动画、弹出窗口(如QComboBox
或QCompleter
),以及小部件的输入焦点和激活。甚至QGraphicsProxyWidget
还整合了嵌入小部件的标签顺序,这样您可以在嵌入小部件之间使用标签导航。您甚至可以将一个新的QGraphicsView
嵌入到场景中,以提供复杂嵌套的场景。
当转换嵌入小部件时,图形视图确保小部件在独立于分辨率的转换下,允许字体和样式在放大时保持清晰。(注意,分辨率独立的效果取决于样式。)
性能#
浮点指令#
为了准确快速地将变换和效果应用到项目上,图形视图假定用户的硬件能够提供足够的性能来处理浮点指令。
许多工作站和台式计算机配备了合适的硬件来加速此类计算,但一些嵌入式设备可能只在库中提供处理数学操作或软件模拟浮点指令的能力。
因此,某些类型的效应在某些设备上的速度可能低于预期。通过在其他区域进行优化,例如使用OpenGL渲染场景,可能可以弥补这种性能损失。然而,任何此类优化如果在它们自己也依赖于浮点硬件的情况下可能本身就会减少性能。