本文翻译自Qt文档,英文版地址为http://doc.qt.io/qt-5/graphicsview.html
图形视图框架提供了用于管理和交互大量自定义的2D图元的接口,以及一个用于显示的视图图元的窗口,支持图元的缩放和旋转。该框架包含一个事件传播体系结构,可以使图元在场景中的交互能力提高一倍。图元可以处理键盘事件,鼠标按下,移动,释放和双击事件,还可以跟踪鼠标的移动。在图形视图框架中,使用二元空间划分树(BSP)快速的查找图元,能够实时地显示包含上百万个图元的大场景。
1.图形视图的结构
1.1场景
QGraphicsScene提供了图形视图显示的场景。场景(scene)具有以下功能:
场景是QGraphicsItem对象的容器。通过调用QGraphicsScene::addItem()将图元添加到场景中,然后可以调用图元检索函数来查询。QGraphicsScene::items()和一些重载函数可以返回和点,矩形,多边形或一般矢量路径包含或相交的所有图元。QGraphicsScene::itemAt()返回特定点的最顶层图元。所有的图元检索函数都按一定的顺序返回图元(第一个返回的图元位于最顶层,最后一个图元位于最底层)。
QGraphicsScene的事件传播体系结构将场景事件传递到图元,同时还管理图元之间的事件传播。如果场景接收到某个位置处的鼠标按下事件,则场景将事件传递给该位置处的所有图元。
QGraphicsScene还管理图元状态,例如图元选择和焦点处理。可以通过调用QGraphicsScene::setSelectionArea()来选择场景中的图元,选择区域可以是任意的形状。该功函数还可用作QGraphicsView中橡皮擦的选择区域。如果要获取当前所选图元的列表,可以调用QGraphicsScene::selectedItems()函数。
QGraphicsScene处理的另一个状态是图元是否具有键盘输入焦点。可以调用QGraphicsScene::setFocusItem()或QGraphicsItem::setFocus()来设置焦点,或通过调用QGraphicsScene::focusItem()获取当前焦点图元。
最后,QGraphicsScene允许调用QGraphicsScene::render()函数将场景的某一部分绘制到显示设备中。
1.2视图
QGraphicsView提供了一个可以显示场景的内容的窗口。在同一个场景中可以有多个视图,也可以为同一数据集提供多个视图。视图是一个滚动区域,提供滚动条以浏览大型场景。要启用OpenGL支持,可以通过调用QGraphicsView:: setViewport()将视图设置为QGLWidget。
视图接收键盘和鼠标的输入事件,并在将事件发送到场景之前将这些事件转换为场景事件(将视图坐标转换为场景坐标)。使用变换矩阵函数QGraphicsView::transform()可以变换场景的坐标系,实现场景的缩放和旋转。为方便起见,QGraphicsView还提供了在视图和场景坐标之间进行转换的函数:QGraphicsView::mapToScene()和QGraphicsView::mapFromScene()。
1.3图元
QGraphicsItem是场景中所有图元的基类。Graphics View内置了几个典型的标准图元,例如矩形(QGraphicsRectItem),椭圆(QGraphicsEllipseItem)和文本(QGraphicsTextItem)。如果要编写自定义图元时,通常的做法就是继承 QGraphicsItem。除此之外,QGraphicsItem还支持以下功能:
图元使用本地坐标系,与QGraphicsView一样,QGraphicsItem提供了图元和场景之间以及图元之间的坐标转换函数。此外,与QGraphicsView一样,它可以使用矩阵QGraphicsItem::transform()变换其坐标系。这对于旋转和缩放单个图元很有帮助。
图元可以包含其他图元(子图元)。子图元继承了父图元的坐标转换。然而,无论图元的坐标变换如何,其所有函数(例如,QGraphicsItem::contain())QGraphicsItem::boundingRect(),QGraphicsItem::collidesWith())仍然采用本地坐标进行操作。
QGraphicsItem通过QGraphicsItem :: shape()函数和QGraphicsItem :: collidesWith()函数实现碰撞检测,它们都是虚函数。QGraphicsItem::shape()返回一个QPainterPath对象,这个对象表示图元的形状,这个过程中,QGraphicsItem会自动为你处理所有的碰撞检测。如果你想实现自己的碰撞检测,你可以重新实现QGraphicsItem::collidesWith()。
2.Graphics View Framework中的类
QGraphicsEffect |
所有图形效果的基类 |
QGraphicsAnchor |
表示QGraphicsAnchorLayout中两个项之间的锚点 |
QGraphicsAnchorLayout |
可以在图形视图中将窗口锚定在一起的布局 |
QGraphicsGridLayout |
用于在图形视图中管理部件的网格布局 |
QAbstractGraphicsShapeItem |
所有图元路径的基础类 |
QGraphicsEllipseItem |
椭圆 |
QGraphicsItem |
所有图元的基类 |
QGraphicsItemGroup |
将一组图元视为单个图元的容器 |
QGraphicsLineItem |
直线 |
QGraphicsObject |
所有需要信号,槽和属性的图形的基类 |
QGraphicsPathItem |
路径 |
QGraphicsPixmapItem |
图片 |
QGraphicsPolygonItem |
多边形 |
QGraphicsRectItem |
矩形 |
QGraphicsSimpleTextItem |
简单文本 |
QGraphicsTextItem |
文本 |
QGraphicsLayout |
布局的基类 |
QGraphicsLayoutItem |
可以继承以便通过布局管理自定义图元 |
QGraphicsLinearLayout |
水平或垂直布局 |
QGraphicsProxyWidget |
用于在QGraphicsScene中嵌入QWidget的代理层 |
QGraphicsScene |
场景 |
QGraphicsSceneContextMenuEvent |
图形视图框架中的上下文菜单事件 |
QGraphicsSceneDragDropEvent |
拖放事件 |
QGraphicsSceneEvent |
所有图形视图相关事件的基类 |
QGraphicsSceneHelpEvent |
请求工具提示时的事件 |
QGraphicsSceneHoverEvent |
鼠标悬停事件 |
QGraphicsSceneMouseEvent |
鼠标事件 |
QGraphicsSceneMoveEvent |
窗体移动事件 |
QGraphicsSceneResizeEvent |
窗体大小改变事件 |
QGraphicsSceneWheelEvent |
滚轮事件 |
QGraphicsTransform |
用于在QGraphicsItems上构建高级转换的抽象基类 |
QGraphicsView |
视图 |
QGraphicsWidget |
QGraphicsScene中所有窗口的基类 |
QStyleOptionGraphicsItem |
用于描述绘制QGraphicsItem所需的参数 |
QGraphicsSvgItem |
用于呈现SVG文件的内容 |
3.Graphics View坐标系
Graphics View基于笛卡尔坐标系,场景中图元的位置由两个数字组成:x坐标和y坐标。当使用未转换的视图观察场景时,场景上的一个单元对应屏幕上的一个像素。
图形视图中有三种坐标系:图元坐标,场景坐标和视图坐标。Graphics View提供了在三个坐标系之间进行转换的函数。绘制时,Graphics View的场景坐标对应于QPainter的逻辑坐标,视图坐标与设备坐标相同。
3.1图元坐标
图元使用自己的本地坐标系,这个坐标系通常以图元中心为原点,这也是所有变换的原点。创建自定义图元时,只需要注意图元坐标就可以了,
QGraphicsScene和QGraphicsView将完成所有的转换。例如,当接收到鼠标按下或拖动事件时,则事件位置坐标以图元坐标的形式给出。对图元坐标系的某个点来说,如果该点在图元内部,QGraphicsItem::contains()返回true,否则返回false。图元的边界矩形框和形状也都是使用图元坐标。
图元的位置是图元中心点在其父坐标系中的坐标。在这个意义上来说,场景指的是没有父图元的图元。顶层图元的位置就是在场景坐标中的位置。子坐标是相对于父坐标来描述的。如果子坐标未经过转换,子坐标和父坐标之间的差值等于在父坐标系下父图元与子图元之间的距离。例如:如果未经坐标转换的子图元精确定位在其父图元的中心点,则两个图元的坐标系统将完全相同。但是,如果子图元的位置是(10,0),则子图元坐标系中的(0,10)点将对应于其父图元坐标系的(10,10)点。
由于图元的位置和变换是相对于父图元来说的,因此子图元的坐标不受父图元转换的影响,尽管父图元的转换会隐式对子图元做了转换。在上面的示例中,即使父图元被旋转和缩放,子图元的(0,10)点仍将对应于父图元的(10,10)点。然而,相对于场景来说,子图元将会遵循父图元的变换。 如果父图元被缩放(2x,2x),则子图元在场景中的坐标是(20,0),并且其(10,0)点将对应于场景上的点(40,0)。除了QGraphicsItem::pos()等几个为数不多的函数外,QGraphicsItem的其他函数都使用图元坐标操作。例如一个图元的边界矩形总是以图元坐标的形式给出。
3.2场景坐标
场景是所有图元的基坐标系统。场景坐标系统描述了每个顶层图元的位置,也为从视图中传递到场景的所有场景事件奠定了基础。场景中的每个图元出了具有图元坐标和边界矩形框外,还有场景坐标和场景边界矩形框。图元的场景坐标描述了图元在场景坐标系下的位置,其边界区域是场景感知内容变化的基础。当场景内容变化时会发出QGraphicsScene::changed()信号,参数是变化的场景矩形列表。
3.3视图坐标
视图坐标就是窗口部件中的坐标,单位是像素。视图的坐标系的特殊之处在于它相对于窗口小部件或视口,并且不受观察场景的影响。QGraphicsView视图的左上角始终为(0,0),右下角始终为(视口宽度,视口高度)。所有的鼠标事件和拖放事件起初使用视图坐标,为了跟图元交互需要将这些坐标映射到场景坐标。
3.4坐标变换
通常在处理场景中的图元时,坐标和形状的映射是非常有必要的,包括从场景到图元的映射,从图元到图元的映射,或从视图到场景的映射。例如,当在Qgraphicsview的视图中单击鼠标时,可以调用QGraphicsView::mapToScene()和QGraphicsScene::itemAt()函数来确定鼠标点击的是哪个图元。如果想知道一个图元在视图中的位置,则可以通过分别调用QGraphicsItem::mapToScene()和QGraphicsView::mapFromScene()来得到。最后,如果你想查询位于视图中的一个椭圆区域内的所有图元,可以将一个QPainterPath对象传递给mapToScene()函数,然后将变换之后的path传递给QGraphicsScene::items()。
使用QGraphicsItem::mapToScene()和QGraphicsItem::mapFromScene()函数可以在图元和场景之间相互映射坐标和图形。类似地,也可以使用QGraphicsItem::mapToParent()和QGraphicsItem::mapFromParent()函数在子图元与父亲图元之间进行互相映射,或者通过QGraphicsItem::mapToItem()和QGraphicsItem::mapFromItem()函数在图元与图元之间相互映射。
对于视图也是一样来说,同样存在着场景和视图之间相互映射的函数QGraphicsView::mapFromScene()和QGraphicsView::mapToScene()。如果是要从视图映射到图元,你必须先映射到场景,再从场景映射到图元。
4.主要特点
4.1缩放和旋转
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); }
...
};
4.2打印
Graphics View通过其渲染函数QGraphicsScene::render()和QGraphicsView::render()实现单行打印。这些函数提供相同的API:您可以通过将QPainter传递给任一渲染函数,让场景或视图将其内容的全部或部分渲染到任何绘图设备中。此示例显示如何使用QPrinter将整个场景打印到整页。
QGraphicsScene scene;
scene.addRect(QRectF(0, 0, 100, 200), QPen(Qt::black), QBrush(Qt::green));
QPrinter printer;
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传递给您正在使用的渲染函数,您可以选择在拉伸内容时保持或忽略场景的宽高比。
4.3拖放
QGraphicsView间接地继承了QWidget类,因此它从QWidget继承了同样的拖放的功能。此外,为方便起见,Graphics View框架还为场景和每个图元提供拖放支持。当视图收到拖动事件时,它会将拖放事件转换为QGraphicsSceneDragDropEvent,然后将其转发到场景中。 场景接管此事件的调度,并将其发送到鼠标光标下接收放置操作的第一个图元。
拖拽一个图元,需要创建一个QDrag对象,同时要把发起拖拽操作的部件的指针传递给该对象。图元可以同时在多个视图中查看,但同一时刻仅仅有一个视图能够进行拖拽。在大多数情况下,拖动是由于按下或移动鼠标而启动的,因此在mousePressEvent()或mouseMoveEvent()中,你可以获取发起拖拽的部件指针。
void CustomItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QMimeData *data = new QMimeData;
data->setColor(Qt::green);
QDrag *drag = new QDrag(event->widget());
drag->setMimeData(data);
drag->start();
}
图元通过调用QGraphicsItem::setAcceptDrops()来启用对拖放的支持。处理拖放操作,请重新实现QGraphicsItem::dragEnterEvent(),QGraphicsItem::dragMoveEvent(),QGraphicsItem::dragLeaveEvent(),QgraphicsItem::dropEvent()。Drag and Drop Robot示例演示了图形视图对拖放操作的支持。
4.4光标和提示
同QWidget一样,QGraphicsItem也支持光标(QGraphicsItem::setCursor())设置和提示(QGraphicsItem::setToolTip())。当鼠标光标移动到图元时(通过QGraphicsItem::contains()判断),QGraphicsView会激活该图元的光标和窗口提示。你也可以直接通过QGraphicsView::setCursor()给视图设置一个默认的光标。
4.5动画
GraphicsViewFramework支持多种级别的动画。通过AnimationFramework,你可以很容易地添加动画。你需要将图元继承自QGraphicsObject,并将其关联到QPropertyAnimation。QPropertyAnimation能够通过QObject属性来实现动画效果。
另一种选择是创建一个继承自QObject和QGraphicsItem的自定义图元。 在图元中设置自己的计时器,并使用QObject::timerEvent()来现动画效果。
第三种方案主要用在Qt3中,跟QCanvas兼容。该方案通过移动场景和图元来实现动画效果。
4.6OpenGL渲染
若想使用OpenGL渲染,只需要调用QGraphicsView::setViewport()设置一个QGLWidget对象作为新的视图即可。要使用OpenGL的抗锯齿功能,你需要OpenGL samplebuffer的支持。
QGraphicsView view(&scene);
view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
4.7图元分组
通过使图元成为另一个图元的子项,您可以实现图元分组的最基本功能:图元将一起移动,并且所有转换都从父图元传播到子图元。另外,QGraphicsItemGroup是一个特殊的图元,它能够统一处理子图元的事件,它提供了添加和移除图元的接口。将一个图元添加到群组后,该图元的位置和坐标变换保持不变,而如果将一个图元设置新的父图元后,该图元的位置和坐标变换则会改变。通过调用 QGraphicsScene::createItemGroup()可以创建新的图元群组。
4.8窗口和布局
从Qt4.4开始引进QGraphicsWidget,通过它实现对几何形状和适用于布局的图元的支持。这个类和QWideget类似,但与它不同的是,QGraphcisWidget不是继承于QPaintDevice,而是QGraphicsItem。因此,你可以编写包含事件,信号和槽,大小提示和策略的完整窗体,还可以通过QGraphicsLinearLayout和QGraphicsGridLayout进行布局管理。
4.9 QGraphicsWidget
QGraphicsWidget同时继承了两者的优点:从QWidget中继承的丰富的功能,如样式(style),字体,调色板,布局和几何图形;以及从QGraphicsItem继承的功能,如独立的分辨率和坐标变换。因为图形视图框架使用实数而非整数来表示坐标,因此QGraphicsWidget的几何图形函数使用的是QRectF和QPointF,对于frame rect,margin,spacing也同样如此。举个例子,设置QGraphicsWidget的内容边界为(0.5,0.5,0.5,0.5)是非常常见的。你可以对其子类化,也可以创建“顶层”窗口;甚至有时你可以通过它来实现高级MDI程序。
QGraphicsWidget支持一部分QWidget的属性,包括窗口标志和属性,但不是全部。你应该参考QGraphicsWidget类的文档,以全面了解支持哪些和不支持哪些内容。 例如,你可以通过将Qt :: Window标志传递给QGraphicsWidget的构造函数来创建窗口,但Graphics View当前不支持macOS上常见的Qt:: Sheet和Qt::Drawer标志。
4.10 QGraphicsLayout
QGraphicsLayout是专为QGraphicsWidget设计的第二代布局框架的一部分。 它的API与QLayout的API非常相似。你可以在QGraphicsLinearLayout和QGraphicsGridLayout中管理部件和子布局。也可以通过创建QGraphicsLayout子类来轻松编写自己的布局,或者通过编写QGraphicsLayoutItem的子类将您自己的QGraphicsItem图元添加到布局中。
4.11嵌入式小部件支持
图形视图支持将任何小部件无缝地嵌入到场景中。你可以嵌入简单的小部件,例如QLineEdit或者QPushButton,诸如QTabWidget之类的复杂部件,甚至可以是完整的窗口。要将部件嵌入到场景中,只需调用QGraphicsScene::addWidget,或者通过创建QGraphicesProxyWidget对象来手动嵌入。
通过QGraphicesProxyWidget,图形视图框架可以深度整合部件的特性:光标,提示信息,鼠标事件,触摸平板和键盘事件,子部件,动画,弹出界面(例如QComboBox或QCompleter),输入焦点和激活等。QGraphicesProxyWidget甚至能够使用部件的tab顺序。甚至可以将一个QGraphicsView嵌入到场景中,从而提供复杂的嵌套场景。当对一个嵌入的部件进行坐标转换时,在转换嵌入式窗口小部件时,图形视图可确保窗口小部件独立地转换为分辨率,因此在缩小和放大视图时,仍能够使它的字体和风格保持清晰。