图形视图框架
Graphics View提供了一个用于管理和交互大量定制2D图形项的界面,以及一个用于可视化这些项的视图小部件,支持缩放和旋转。 该框架包括一个事件传播体系结构,该体系结构允许为场景中的图型项提供精确的双精度交互功能。图型项可以处理按键事件、鼠标按下、移动、释放和双击事件,还可以跟踪鼠标移动。 图形视图使用BSP(二进制空间划分)树来提供非常快速的图型项发现,因此,它可以实时可视化大型场景,即使有数百万个图型项。 图形视图是在Qt 4.2中引入的,取代了它的前身QCanvas。
图形视图体系结构
图形视图为模型视图编程提供了一种基于图型项的方法,很像InterView的方便类QTableView, QTreeView和QListView。多个视图可以观察单个场景,场景包含不同几何形状的图型项。
场景
QGraphicsScene提供图形视图场景。场景有以下职责:
-
提供管理大量图型项的快速界面
-
将事件传播到每个图型项
-
管理图型项状态,例如选择和焦点处理
-
提供未转换的渲染功能;主要用于打印
场景充当QGraphicsItem对象的容器。通过调用QGraphicsScene::addItem()将图型项添加到场景中,然后通过调用众多图型项发现函数中的一个来检索图型项。items()及其重载返回由点、矩形、多边形或一般矢量路径包含或与之相交的所有项。QGraphicsScene::itemAt()返回特定点上最上面的项。所有图型项发现函数都以降序堆叠的方式返回图型项(即,第一个返回的图型项是最上面的,最后一个图型项是最下面的)。
c++
QGraphicsScene scene;
QGraphicsRectItem *rect = scene.addRect(QRectF(0, 0, 100, 100));
QGraphicsItem *item = scene.itemAt(50, 50);
// item == rect
QGraphicsScene的事件传播架构调度场景事件以交付给图型项,并且还管理图型项之间的传播。如果场景在某个位置接收到鼠标按下事件,则场景将该事件传递给该位置的任何图型项。
QGraphicsScene还管理某些图型项状态,例如图型项选择和焦点。您可以通过调用QGraphicsScene::setSelectionArea()来选择场景中的图型项,并传递一个任意形状。这个功能也被用作QGraphicsView中橡皮筋选择的基础。要获取所有当前选中图型项的列表,请调用QGraphicsScene::selectedItems()。QGraphicsScene处理的另一个状态是图型项是否具有键盘输入焦点。您可以通过调用QGraphicsScene::setFocusItem()或QGraphicsItem::setFocus()来设置一个图型项的焦点,或者通过调用QGraphicsScene::focusItem()来获取当前的焦点图型项。
最后,QGraphicsScene允许您通过QGraphicsScene::render()函数将场景的部分渲染到绘画设备中。您可以在本文档后面的"打印"部分中了解更多相关信息。
视图
QGraphicsView提供了视图小部件,用于可视化场景的内容。您可以将多个视图附加到同一个场景,以在相同的数据集中提供多个视图。视图小部件是一个滚动区域,并提供滚动条用于在大型场景中导航。要启用OpenGL支持,您可以通过调用QGraphicsView::setViewport()将QGLWidget设置为视口。
c++
QGraphicsScene scene;
myPopulateScene(&scene);
QGraphicsView view(&scene);
view.show();
视图接收来自键盘和鼠标的输入事件,并将这些事件转换为场景事件(在适当的地方将使用的坐标转换为场景坐标),然后将事件发送到可视化场景。
使用它的变换矩阵QGraphicsView::transform(),视图可以变换场景的坐标系。这允许高级导航功能,如缩放和旋转。为了方便,QGraphicsView还提供了在视图和场景坐标之间转换的函数:QGraphicsView::mapToScene()和QGraphicsView::mapFromScene()。
图型项
QGraphicsItem是场景中图形项的基类。图形视图为典型形状提供了几个标准项,比如矩形(qgraphicsrectem)、椭圆(QGraphicsEllipseItem)和文本项(QGraphicsTextItem),但是最强大的QGraphicsItem特性是在编写自定义项时可用的。除此之外,QGraphicsItem还支持以下特性:
-
鼠标按下、移动、释放和双击事件,以及鼠标悬停事件、滚轮事件和上下文菜单事件。
-
键盘输入焦点和键事件
-
拖放
-
通过父子关系和QGraphicsItemGroup进行分组
-
碰撞检测
图型项位于本地坐标系统中,与QGraphicsView一样,它还提供了许多用于在图型项和场景之间以及图型项与图型项之间映射坐标的功能。此外,与QGraphicsView一样,它可以使用矩阵:QGraphicsItem::transform()来转换其坐标系统。这对于旋转和缩放单个图型项非常有用。
图型项可以包含其他项(子项)。父项的转换由它的所有子项继承。不管项的累积变换是什么,它的所有函数(例如,QGraphicsItem::contains(), QGraphicsItem::boundingRect(), QGraphicsItem::collidesWith())仍然在本地坐标中操作。
QGraphicsItem通过QGraphicsItem::shape()函数和QGraphicsItem::collidesWith()函数支持碰撞检测,这两个函数都是虚拟函数。通过从QGraphicsItem::shape()返回图型项的形状作为一个本地坐标QPainterPath, QGraphicsItem将为你处理所有的碰撞检测。但是,如果您想提供自己的碰撞检测,您可以重新实现QGraphicsItem::collidesWith()。
图形视图框架中的类
这些类为创建交互式应用程序提供了一个框架。
QGraphicsEffect | 所有图形效果的基类 |
QGraphicsAnchor | 表示QGraphicsAnchorLayout中两个图型项之间的锚点 |
QGraphicsAnchorLayout | 可以在图形视图中将小部件固定在一起的布局 |
QGraphicsGridLayout | 在图形视图中管理小部件的网格布局 |
QAbstractGraphicsShapeItem | 所有路径图形项的公共基类 |
QGraphicsEllipseItem | 可以添加到QGraphicsScene的椭圆项 |
QGraphicsItem | QGraphicsScene中所有图形项的基类 |
QGraphicsItemGroup | 将一组项视为单个项的容器 |
QGraphicsLineItem | 可以添加到QGraphicsScene的线项 |
QGraphicsObject | 所有需要信号、槽和属性的图形项的基类 |
QGraphicsPathItem | 可以添加到QGraphicsScene的路径项 |
QGraphicsPixmapItem | 可以添加到QGraphicsScene的像素图项 |
QGraphicsPolygonItem | 可以添加到QGraphicsScene的多边形项 |
QGraphicsRectItem | 可以添加到QGraphicsScene的矩形项 |
QGraphicsSimpleTextItem | 可以添加到QGraphicsScene的简单文本路径项 |
QGraphicsTextItem | 可以添加到QGraphicsScene以显示格式化文本的文本项 |
QGraphicsLayout | 图形视图中所有布局的基类 |
QGraphicsLayoutItem | 可以继承以允许您的自定义图形项由布局管理 |
QGraphicsLinearLayout | 用于管理图形视图中的小部件的水平或垂直布局 |
QGraphicsProxyWidget | 用于在QGraphicsScene中嵌入QWidget的代理层 |
QGraphicsScene | 用于管理大量2D图形项的表面 |
QGraphicsSceneContextMenuEvent | 图形视图框架中的上下文菜单事件 |
QGraphicsSceneDragDropEvent | 事件,用于在图形视图框架中拖放 |
QGraphicsSceneEvent | 事件,所有图形视图相关事件的基类 |
QGraphicsSceneHelpEvent | 事件,请求工具提示时的事件 |
QGraphicsSceneHoverEvent | 事件,图形视图框架中的悬停事件 |
QGraphicsSceneMouseEvent | 事件,图形视图框架中的鼠标事件 |
QGraphicsSceneMoveEvent | 事件,用于在图形视图框架中移动小部件的事件 |
QGraphicsSceneResizeEvent | 事件,用于在图形视图框架中调整小部件的大小 |
QGraphicsSceneWheelEvent | 事件,图形视图框架中的滚轮事件 |
QGraphicsTransform | 事件,用于在QGraphicsItems上构建高级转换的抽象基类 |
QGraphicsView | 用于显示QGraphicsScene内容的小部件 |
QGraphicsWidget | QGraphicsScene中所有小部件项的基类 |
QStyleOptionGraphicsItem | 用于描述绘制QGraphicsItem所需的参数 |
QGraphicsSvgItem | QGraphicsItem,可用于呈现SVG文件的内容 |
图形视图坐标系统
图形视图基于笛卡尔坐标系;物品在场景中的位置和几何形状由两个数字集合表示:x坐标和y坐标。当使用未转换视图观察场景时,场景中的一个单位由屏幕上的一个像素表示。
注意:当图形视图使用Qt的坐标系统时,不支持倒y轴坐标系统(y向上增长)。
在图形视图中有三种有效的坐标系统:图型项坐标、场景坐标和视图坐标。为了简化实现,图形视图提供了方便的函数,允许您在三个坐标系统之间进行映射。
渲染时,图形视图的场景坐标对应于QPainter的逻辑坐标,视图坐标与设备坐标相同。在坐标系统文档中,您可以了解逻辑坐标和设备坐标之间的关系。
图形项坐标
图形项生活在它们自己的本地坐标系统中。它们的坐标通常以它的中心点(0,0)为中心,这也是所有变换的中心。图形项坐标系统中的几何基元通常被称为图形项点、图形项线或图形项矩形。
当创建自定义图形项时,图形项坐标是你所需要担心的;QGraphicsScene和QGraphicsView将为你执行所有的转换。这使得实现自定义项变得非常容易。例如,如果收到鼠标按下或拖动进入事件,则以图形项坐标给出事件位置。QGraphicsItem::contains()虚拟函数,如果某个点在图形项中,则返回true,否则返回false,并接受图形项坐标中的point参数。类似地,项的边界矩形和形状在项坐标中。
在图形项的位置是图形项的中心点在其父坐标系统中的坐标;有时称为父坐标。在这个意义上,场景被视为所有没有父母的图形项的"父母"。顶层物品的位置以场景坐标表示。
子坐标相对于父坐标。如果子坐标未转换,则子坐标与父坐标之间的差值与父坐标中项之间的距离相同。例如:如果一个未转换的子项精确地定位在其父项的中心点上,那么这两个子项的坐标系统将是相同的。但是,如果子节点的位置为(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的viewport的左上角总是(0,0),右下角总是(viewport width, viewport height)。所有鼠标事件和拖放事件最初都是作为视图坐标接收的,您需要将这些坐标映射到场景中,以便与图形项进行交互。
坐标映射
通常在处理场景中的物品时,将坐标和任意形状从场景映射到物品,从物品映射到物品,或者从视图映射到场景是很有用的。例如,当您在QGraphicsView的视口中单击鼠标时,您可以通过调用QGraphicsView::mapToScene(),然后调用QGraphicsScene::itemAt()来询问场景光标下的图形项。如果你想知道一个图形项在视图中的位置,你可以在图形项上调用QGraphicsItem::mapToScene(),然后在视图上调用QGraphicsView::mapFromScene()。最后,如果您想要查找视图椭圆内的图形项,可以将QPainterPath传递给mapToScene(),然后将映射的路径传递给QGraphicsScene::items()。
你可以通过调用QGraphicsItem::mapToScene()和QGraphicsItem::mapFromScene()将坐标和形状映射到图形项的场景中。您还可以通过调用QGraphicsItem:: maptopparent()和QGraphicsItem::mapFromParent()映射到项的父项,或者通过调用QGraphicsItem::mapToItem()和QGraphicsItem::mapFromItem()映射到项之间。所有的映射函数都可以映射点、矩形、多边形和路径。
相同的映射功能在视图中可用,用于映射到场景和从场景映射。QGraphicsView::mapFromScene()和QGraphicsView::mapToScene()。要从视图映射到图形项,首先映射到场景,然后从场景映射到图形项。
关键特性
缩放和旋转
QGraphicsView通过QGraphicsView::setMatrix()支持与QPainter相同的仿射变换。通过对视图应用转换,您可以轻松地添加对常见导航特性的支持,例如缩放和旋转。 下面是一个例子,如何实现缩放和旋转槽在QGraphicsView的子类:
c++
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); }
...
};
插槽可以连接到QToolButtons与自动重复启用。
QGraphicsView在转换视图时保持视图的中心对齐。
另请参阅Elastic Nodes示例,了解如何实现基本缩放特性的代码。
打印
图形视图通过其渲染函数QGraphicsScene::render()和QGraphicsView::render()提供单行打印。这些函数提供相同的API:您可以通过将QPainter传递给其中一个渲染函数,让场景或视图将其全部或部分内容渲染到任何绘制设备中。这个例子展示了如何使用QPrinter将整个场景打印成一个完整的页面。
c++
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);
}
场景和视图渲染函数之间的区别在于一个在场景坐标中操作,而另一个在视图坐标中操作。渲染()通常更适合打印未转换的场景的整个片段,例如绘制几何数据或打印文本文档。另一方面,QGraphicsView::render()适用于截图;它的默认行为是使用提供的painter渲染viewport的确切内容。
c++
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()中,您可以从事件中获得原始小部件指针。例如:
c++
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子类中重新实现QGraphicsScene::dragEnterEvent()和特定场景所需的任何事件处理程序。您可以在QGraphicsScene的每个事件处理程序的文档中阅读有关在图形视图中拖放的更多信息。
图形项可以通过调用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属性。
另一个选择是从QObject和QGraphicsItem继承的自定义项。图形项可以设置自己的计时器,并在QObject::timerEvent()中使用增量步骤控制动画。
第三种选择是通过调用QGraphicsScene::advance()来推进场景,而QGraphicsScene::advance()又调用QGraphicsItem::advance(),这主要是为了与Qt 3中的QCanvas兼容。
OpenGL渲染
要启用OpenGL渲染,只需通过调用QGraphicsView::setViewport()将一个新的QGLWidget设置为QGraphicsView的视口。如果你想让OpenGL具有抗锯齿,你需要OpenGL样本缓冲区支持(参见QGLFormat::sampleBuffers())。
例子:
c++
QGraphicsView view(&scene);
view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
图形项组
通过使一项成为另一项的子项,您可以实现项分组的最基本特性:项将一起移动,并且所有转换都从父项传播到子项。
此外,QGraphicsItemGroup是一个特殊的项,它将子事件处理与用于向组添加和从组中删除项的有用接口相结合。将项添加到QGraphicsItemGroup将保持项的原始位置和转换,而重设项通常会导致子项相对于其新父项重新定位自己。为方便起见,您可以通过调用QGraphicsScene::createItemGroup()通过场景创建QGraphicsItemGroups。
小部件和布局
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的构造函数来创建装饰窗口,但是Graphics View目前不支持macOS上常见的Qt::Sheet和Qt::Drawer标志。
QGraphicsLayout
QGraphicsLayout是专门为QGraphicsWidget设计的第二代布局框架的一部分。它的API与QLayout非常相似。你可以在QGraphicsLinearLayout和QGraphicsGridLayout中管理小部件和子布局。您还可以通过创建QGraphicsLayout的子类来轻松编写自己的布局,或者通过编写QGraphicsLayoutItem的适配器子类来将自己的QGraphicsItem项添加到布局中。
嵌入式小部件支持
图形视图为在场景中嵌入任何小部件提供了无缝支持。您可以嵌入简单的小部件,如QLineEdit或QPushButton,复杂的小部件,如QTabWidget,甚至完整的主窗口。要将小部件嵌入到场景中,只需调用QGraphicsScene::addWidget(),或者创建QGraphicsProxyWidget的实例来手动嵌入小部件。
通过QGraphicsProxyWidget,图形视图能够深度集成客户端小部件功能,包括其光标,工具提示,鼠标,平板电脑和键盘事件,子小部件,动画,弹出窗口(例如,QComboBox或QCompleter),以及小部件的输入焦点和激活。QGraphicsProxyWidget甚至集成了嵌入式小部件的选项卡顺序,以便您可以在嵌入式小部件中添加和删除选项卡。您甚至可以在场景中嵌入一个新的QGraphicsView,以提供复杂的嵌套场景。
在转换嵌入式小部件时,图形视图确保小部件独立于分辨率进行转换,从而允许字体和样式在放大时保持清晰。(请注意,分辨率独立性的效果取决于样式。)
表现
浮点指令
为了准确、快速地将转换和效果应用到图形项中,图形视图的构建假定用户的硬件能够为浮点指令提供合理的性能。
许多工作站和台式计算机都配备了适当的硬件来加速这种计算,但是一些嵌入式设备可能只提供处理数学运算或在软件中模拟浮点指令的库
因此,在某些设备上,某些类型的效果可能比预期的要慢。也许可以通过在其他领域进行优化来弥补这种性能损失;例如,通过使用OpenGL渲染场景。但是,如果这些优化还依赖于浮点硬件,那么它们本身可能会导致性能下降。