文章目录
Graphics View Framework
图形视图提供了一个用于管理大量定制的二维图形项目并与之交互的界面,以及一个用于可视化项目的视图小部件,并支持缩放和旋转。
该框架包括一个事件传播体系结构,允许对场景中的项目提供精确的双精度交互功能。项目可以处理按键事件、鼠标按下、移动、释放和双击事件,还可以跟踪鼠标移动。
Graphics View使用BSP(二进制空间分区) 树来提供非常快速的项目发现,因此,它可以实时地可视化大型场景,即使有数百万个项目。
图形视图是在Qt 4.2中引入的,取代了它的前身QCanvas。
图形视图体系结构
Graphics View 为模型视图编程提供了一种基于项目的方法,很像InterView的便利类QTableView、QTreeView和QListView。多个视图可以观察单个场景,并且场景包含不同几何形状的项。
场景
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);
// item == rect
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() 返回项目的形状作为局部坐标QPainterPath,QGraphicsItem将为您处理所有碰撞检测。但是,如果要提供自己的冲突检测,则可以重新实现QGraphicsItem::collidesWith() 。
图形视图框架中的类
这些类提供了用于创建交互式应用程序的框架。
class | Description |
---|---|
QAbstractGraphicsShapeItem | 所有路径项目的共同基础 |
QGraphicsAnchor | 表示QGraphicsAnchorLayout中两个项目之间的锚点 |
QGraphicsAnchorLayout | 可以在“图形视图”中将小部件固定在一起的布局 |
QGraphicsEffect | 所有图形效果的基类 |
QGraphicsEllipseItem | 可以添加到QGraphicsScene的椭圆项 |
QGraphicsGridLayout | 用于在“图形视图”中管理小部件的网格布局 |
QGraphicsItem | QGraphicsScene中所有图形项的基类 |
QGraphicsItemGroup | 将一组项目视为单个项目的容器 |
QGraphicsLayout | 图形视图中所有布局的基类 |
QGraphicsLayoutItem | 可以继承以允许您的自定义项目由布局管理 |
QGraphicsLineItem | 您可以添加到QGraphicsScene的订单项 |
QGraphicsLinearLayout | 用于在“图形视图”中管理小部件的水平或垂直布局 |
QGraphicsObject | 所有需要信号,插槽和属性的图形项的基类 |
QGraphicsPathItem | 可以添加到QGraphicsScene的路径项 |
QGraphicsPixmapItem | 可以添加到QGraphicsScene的Pixmap项 |
QGraphicsPolygonItem | 您可以添加到QGraphicsScene的多边形项目 |
QGraphicsProxyWidget | 用于将QWidget嵌入QGraphicsScene的代理层 |
QGraphicsRectItem | 可以添加到QGraphicsScene的矩形项目 |
QGraphicsScene | 用于管理大量2D图形项目的表面 |
QGraphicsSceneContextMenuEvent | 图形视图框架中的上下文菜单事件 |
QGraphicsSceneDragDropEvent | 图形视图框架中的拖放事件 |
QGraphicsSceneEvent | 所有图形视图相关事件的基类 |
QGraphicsSceneHelpEvent | 请求工具提示时的事件 |
QGraphicsSceneHoverEvent | 在图形视图框架中悬停事件 |
QGraphicsSceneMouseEvent | 图形视图框架中的鼠标事件 |
QGraphicsSceneMoveEvent | 在图形视图框架中移动小部件的事件 |
QGraphicsSceneResizeEvent | 在图形视图框架中调整窗口小部件大小的事件 |
QGraphicsSceneWheelEvent | 图形视图框架中的Wheel事件 |
QGraphicsSimpleTextItem | 您可以添加到QGraphicsScene的简单文本路径项 |
QGraphicsSvgItem | 可用于呈现SVG文件内容的QGraphicsItem |
QGraphicsTextItem | 可以添加到QGraphicsScene以显示格式化文本的文本项 |
QGraphicsTransform | 用于在QGraphicsItems上构建高级转换的抽象基类 |
QGraphicsView | 用于显示QGraphicsScene内容的小部件 |
QGraphicsWidget | QGraphicsScene中所有小部件项目的基类 |
QStyleOptionGraphicsItem | 用于描述绘制QGraphicsItem所需的参数 |
图形视图的坐标系
图形视图基于笛卡尔坐标系; 项目在场景中的位置和几何形状由两个数字表示: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() ) 始终在项目坐标中给出。
场景坐标
场景代表所有项目的基本坐标系。 场景坐标系描述了每个顶级项目的位置,并且还构成了从视图传递到场景的所有场景事件的基础。 场景中的每个项目除具有本地项目pos和边界矩形之外,还具有场景位置和边界矩形(QGraphicsItem::scenePos() ,QGraphicsItem::sceneBoundingRect() ) 。 场景位置描述了项目在场景坐标中的位置,其场景边界rect构成了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); }
...
};
插槽可以在启用了autoRepeat的情况下连接到QToolButtons。
变换视图时,QGraphicsView可使视图的中心对齐。
有关显示如何实现基本缩放功能的代码,另请参见“Elastic Nodes Example ”。
打印
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() 适合拍摄屏幕截图; 它的默认行为是使用提供的painter渲染视口的确切内容。
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;
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() 。
另请参见“拖放机器人”示例,以了解Graphics View对拖放操作的支持。
光标和工具提示
与QWidget一样,QGraphicsItem也支持游标(QGraphicsItem::setCursor() ) 和工具提示(QGraphicsItem::setToolTip() ) 。 当鼠标光标进入项目区域时(通过调用QGraphicsItem::contains() 进行检测) ,QGraphicsView会激活光标和工具提示。
您也可以通过调用QGraphicsView::setCursor() 直接在视图上设置默认光标。
有关实现工具提示和光标形状处理的代码,另请参见“拖放机器人”示例。
动画
Graphics View支持多个级别的动画。 您可以使用动画框架轻松地组装动画。 为此,您将需要从QGraphicsObject继承项并将QPropertyAnimation与它们关联。 QPropertyAnimation允许设置任何QObject属性的动画。
另一个选择是创建一个继承自QObject和QGraphicsItem的自定义项目。 该项目可以设置自己的计时器,并通过QObject::timerEvent() 中的增量步骤控制动画。
第三个选项(主要用于与Qt 3中的QCanvas兼容) 是通过调用QGraphicsScene::advance() 来推进场景的,QGraphicsScene::advance() 依次调用QGraphicsItem::advance() 。
OpenGL渲染
要启用OpenGL渲染,只需调用QGraphicsView::setViewport() 即可将新的QOpenGLWidget设置为QGraphicsView的视口。 如果要使用抗锯齿的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() 在整个场景中创建QGraphicsItemGroups。
小部件和布局
Qt 4.4通过QGraphicsWidget引入了对几何和布局感知项的支持。 这个特殊的基础项目类似于QWidget,但是与QWidget不同,它不是从QPaintDevice继承而来的。 而不是QGraphicsItem。 这使您可以编写带有事件,信号和插槽,大小提示和策略的完整窗口小部件,还可以通过QGraphicsLinearLayout和QGraphicsGridLayout管理布局中的窗口小部件几何形状。
QGraphicsWidget
基于QGraphicsItem的功能和精简版图,QGraphicsWidget提供了两全其美的功能:QWidget的其他功能,例如样式,字体,调色板,布局方向及其几何形状,以及QGraphicsItem的分辨率独立性和转换支持。由于Graphics View使用实数坐标而不是整数,因此QGraphicsWidget的几何函数也可以在QRectF和QPointF上运行。这也适用于框架矩形,边距和间距。例如,使用QGraphicsWidget指定内容边距(0.5、0.5、0.5、0.5) 并不少见。您可以创建子小部件和“顶层”窗口。在某些情况下,您现在可以将Graphics View用于高级MDI应用程序。
支持QWidget的某些属性,包括窗口标志和属性,但不是全部。您应该参考QGraphicsWidget的类文档,以全面了解什么是不支持的内容。例如,您可以通过将Qt::Window窗口标志传递给QGraphicsWidget的构造函数来创建装饰窗口,但是Graphics View当前不支持macOS上常见的Qt::Sheet和Qt::Drawer标志。
QGraphicsLayout
QGraphicsLayout是专门为QGraphicsWidget设计的第二代布局框架的一部分。 它的API与QLayout非常相似。 您可以在QGraphicsLinearLayout和QGraphicsGridLayout中管理小部件和子布局。 您也可以通过自己子类化QGraphicsLayout轻松地编写自己的布局,或者通过编写QGraphicsLayoutItem的适配器子类将自己的QGraphicsItem项目添加到布局中。
嵌入式小部件支持
Graphics View提供了无缝支持,可将任何小部件嵌入到场景中。您可以嵌入简单的小部件(例如QLineEdit或QPushButton) ,复杂的小部件(例如QTabWidget) ,甚至完整的主窗口。要将小部件嵌入场景,只需调用QGraphicsScene::addWidget() ,或创建QGraphicsProxyWidget的实例即可手动嵌入小部件。
通过QGraphicsProxyWidget,Graphics View能够深度集成客户端小部件功能,包括其光标,工具提示,鼠标,平板电脑和键盘事件,子小部件,动画,弹出窗口(例如QComboBox或QCompleter) 以及小部件的输入焦点和激活。
QGraphicsProxyWidget甚至集成了嵌入式窗口小部件的选项卡顺序,以便您可以在嵌入式窗口小部件中切入和切出。您甚至可以将新的QGraphicsView嵌入到场景中以提供复杂的嵌套场景。
在转换嵌入式窗口小部件时,“图形视图”可确保对窗口小部件进行独立的分辨率转换,从而在放大时使字体和样式保持清晰。(请注意,分辨率独立性的影响取决于样式。)
性能
浮点指令
为了准确、快速地对项目应用转换和效果,图形视图是在假定用户的硬件能够为浮点指令提供合理性能的前提下构建的。
许多工作站和桌面计算机都配备了适当的硬件来加速这类计算,但是一些嵌入式设备可能只提供处理数学操作或在软件中模拟浮点指令的库。
因此,某些类型的效果在某些设备上可能比预期的要慢。也许可以通过在其他方面进行优化来弥补这种性能损失;例如,通过使用OpenGL渲染一个场景。但是,如果这些优化同时依赖于浮点硬件的存在,那么它们本身可能会导致性能降低。
参考
- Elastic Nodes Example
- Drag and Drop Robot example