Graphics View框架理论综述
- 一、概述
- 二、Graphics View 体系结构
- 1. The Scene
- 2. The View
- 3. 图元 Item
- 三、图形视图坐标系统
- 1. 图元Item的坐标
- 2. Scene Scene坐标
- 3. View 视图坐标
- 4. 坐标映射
- 四、关键特性
- 1. 缩放和旋转
- 2. 打印
- 3. 拖放
- 4. 鼠标指针和 提示
- 5. 动画
- 6. OpenGL渲染
- 7. Item group
- 8. 部件和布局
- 1. QGraphicsWidget
- 2. QGraphicsLayout
- 9. 对嵌入Widget的支持
- 五、性能
一、概述
Graphics View 提供了一个用于管理大量定制 2D 图形项并与之交互的表面,以及一个用于可视化这些项的视图部件,支持缩放和旋转。
该框架包括一个事件传播架构,允许Scene中的 Item 具有精确的双精度交互能力。元素可以处理按键事件、鼠标按下、移动、释放和双击事件,它们还可以跟踪鼠标移动。
Graphics View 使用BSP(二叉空间划分)树来提供非常快速的 Item 发现,因此,它可以实时可视化大型Scene,即使有数百万个 Item 。
Graphics View 在Qt 4.2中引入,取代了它的前身QCanvas。
二、Graphics View 体系结构
Graphics View 提供了一种基于项的模型视图编程方法,很像InterView的便利类QTableView, QTreeView和QListView。多个视图可以观察同一个Scene,Scene中包含不同几何形状的物品。
1. The Scene
QGraphicsScene提供Graphics View的 Scene。Scene职责如下:
- 提供管理大量 Item 的快速界面
- 将事件传播到每个 Item
- 管理 Item 状态,例如选择和焦点处理
- 提供未转换的渲染功能;主要用于 Print 功能
Scene作为QGraphicsItem对象的容器。通过调用QGraphicsScene::addItem()将物品添加到Scene中,然后通过调用众多物品发现函数中的一个来检索物品。QGraphicsScene::items()及其重载函数返回由 点、矩形、多边形或一般向量路径包含或与之相交的所有元素。QGraphicsScene::itemAt() 返回指定位置的最顶层元素所有物品发现函数都以降序堆叠的方式返回物品(即,第一个返回的物品在最上面,最后一个返回的物品在最下面)。
QGraphicsScene scene;
QGraphicsRectItem *rect = scene.addRect(QRectF(0, 0, 100, 100));
QGraphicsItem *item = scene.itemAt(50, 50);
// item == rect
QGraphicsScene的事件传播架构调度Scene的事件以交付到 Item ,并管理 Item 之间的传播。如果Scene在某个位置接收到鼠标按下事件,则Scene会将事件传递给位于该位置的任何元素。
QGraphicsScene还管理某些 Item 状态,例如 Item 选择和焦点。你可以通过调用QGraphicsScene::setSelectionArea()来选择Scene中的元素,传入一个任意形状的参数。此功能也用作QGraphicsView中橡皮筋选择的基础。要取得当前所有选中项的列表,调用QGraphicsScene::selectedItems()。QGraphicsScene处理的另一个状态是 Item 是否具有键盘输入焦点。我们可以通过调用QGraphicsScene::setFocusItem()或QGraphicsItem::setFocus()来设置项上的焦点,或者通过调用QGraphicsScene::focusItem()来获取当前的焦点项。
最后,QGraphicsScene允许我们通过QGraphicsScene::render()函数将Scene的部分渲染到绘图设备中。
这就给我们提供了输出成图片提供了依据。
2. The View
QGraphicsView 提供了view控件,它可以可视化Scene的内容。你可以为同一个Scene添加多个视图,从而为同一个数据集提供多个视口。view部件是一个滚动区域,提供了在大型Scene中导航的滚动条。要启用OpenGL支持,可以调用QGraphicsView::setViewport()将QGLWidget设置为视图。
QGraphicsScene scene;
myPopulateScene(&scene);
QGraphicsView view(&scene);
view.show();
View 接收来自键盘和鼠标的输入事件,并将这些事件转换为Scene事件(将使用的坐标转换为Scene坐标),然后将事件发送到可视化Scene。
使用它的转换矩阵 QGraphicsView::transform(),视图可以转换Scene的坐标系统。视图还允许高级导航功能,如缩放和旋转。为了方便,QGraphicsView 还提供了在视图和Scene坐标之间进行转换的函数:QGraphicsView::mapToScene() 和 QGraphicsView::mapFromScene()。
3. 图元 Item
QGraphicsItem 是Scene中图形元素的基类。图形视图为常用的形状提供了一些标准项,例如矩形(QGraphicsItem)、椭圆(QGraphicsEllipseItem) 和文本项 (QGraphicsTextItem),但在编写自定义项时,我们可以使用最强大的 QGraphicsItem 功能。就是继承这个功能。
QGraphicsItem 支持以下特性:
- 鼠标按下、移动、释放和双击事件,以及鼠标悬停事件、滚轮事件和上下文菜单事件。
- 键盘输入焦点和按键事件
- 拖放 Drop 和 Drag
- 分组管理,有父子关系,也有QGraphicsItemGroup 分组方式
- 碰撞检测
Items 存在于一个局部坐标系中,像 QGraphicsView 一样,它也提供了许多函数来映射物品和Scene之间的坐标,以及物品到物品之间的坐标。而且,像 QGraphicsView 一样,它可以使用矩阵 QGraphicsItem::transform() 转换其坐标系统。这对于旋转和缩放单个元素非常有用。
Items 可以包含其他Item(子Item)。父项的变换会被其所有的子项继承。然而,不管元素的累积变换如何,它的所有函数(例如QGraphicsItem::contains()、QGraphicsItem::boundingRect()、QGraphicsItem::collidesWith())仍然在局部坐标中操作。
QGraphicsItem通过QGraphicsItem::shape()函数和QGraphicsItem::collidesWith()函数支持碰撞检测,这两个函数都是虚函数。通过从QGraphicsItem::shape()返回 Item 的形状作为局部坐标QPainterPath, QGraphicsItem将为你处理所有碰撞检测。但是,如果你想提供自己的碰撞检测,可以重新实现QGraphicsItem::collidesWith()。
三、图形视图坐标系统
图形视图基于笛卡儿坐标系: Item 在Scene中的位置和几何形状由两组数字表示:x坐标和y坐标。当使用未变换的视图观察Scene时,Scene中的一个单位由屏幕上的一个像素表示。
注意:由于图形视图使用Qt的坐标系统,因此不支持反向y轴坐标系统(y向上增长)。
在图形视图中有三个有效的坐标系统:Item 坐标、Scene坐标和视图坐标。为了简化实现,Graphics View提供了方便的函数,允许你在三个坐标系统之间进行映射。
渲染时,图形视图的Scene坐标对应于QPainter的逻辑坐标,视图坐标与设备坐标相同。在坐标系统文档中,我们可以阅读有关逻辑坐标和设备坐标之间的关系。
1. 图元Item的坐标
元素存在于自己的局部坐标系统中。它们的坐标通常以中心点(0,0)为中心,这也是所有变换的中心。物品坐标系中的几何基元通常被称为物品点、物品线或物品矩形。
创建自定义item时,item的坐标是你需要担心的;QGraphicsScene和QGraphicsView将为我们执行所有转换。这使得实现自定义项非常容易。例如,如果你收到了一个鼠标按下或拖动进入事件,事件的位置会在元素的坐标中给出。
QGraphicsItem::contains()虚函数,如果某个点在 Item 内部,则返回true,否则返回false,它接受 Item 坐标中的point参数。类似地, Item 的边界矩形和形状是在 Item 的坐标中。
At item的位置是item在其父坐标系中的中心点的坐标;有时也被称为父坐标。在这个意义上,Scene被视为所有无父项的“父”。顶级物品的位置在Scene坐标中。
子元素坐标是相对于父元素坐标的。如果子元素未进行转换,则子元素坐标与父元素坐标之间的差值与父元素坐标中 Item 之间的距离相同。例如:如果一个未变换的子项精确地定位在父项的中心点上,那么两个子项的坐标系统将是相同的。如果子节点的位置是(10,0),那么子节点的(0,10)点将对应于父节点的(10,10)点。
因为元素的位置和变换是相对于父元素的,所以子元素的坐标不受父元素变换的影响,尽管父元素的变换会隐式地变换子元素。在上面的例子中,即使父元素被旋转和缩放,子元素的(0,10)点仍然对应于父元素的(10,10)点。然而,相对于Scene,子元素会跟随父元素的变换和位置。如果父元素被缩放(2x, 2x),子元素的位置将在Scene坐标(20,0)处,而它的(10,0)点将对应于Scene中的(40,0)点。
QGraphicsItem::pos()是少数例外之一,QGraphicsItem的函数在物品坐标中操作,无论物品是什么,也不管它的父元素是什么变换。例如,一个 Item 的边界矩形(即QGraphicsItem::boundingRect())总是以 Item 的坐标给出。
2. Scene Scene坐标
Scene代表所有 Item 的基础坐标系统。Scene坐标系统描述了每个顶层 Item 的位置,同时也构成了从视图发送到Scene的所有Scene事件的基础。Scene中的每个 Item 都有一个Scene位置和边界矩形(QGraphicsItem::scenePos(), QGraphicsItem::sceneBoundingRect()),除了它的局部 Item 位置和边界矩形。Scene位置描述了 Item 在Scene坐标中的位置,它的Scene边界矩形构成了QGraphicsScene如何确定Scene的哪些区域发生了变化的基础。Scene的变化通过QGraphicsScene::changed()信号传达,参数是Scene矩形的列表。
3. View 视图坐标
视图坐标是控件的坐标。视图坐标中的每个单位对应一个像素。这个坐标系统的特别之处在于它是相对于widget(或视口)的,不受观察到的Scene的影响。QGraphicsView的视口左上角始终是(0,0),右下角始终是(视口宽度,视口高度)。所有的鼠标事件和拖放事件最初都是以视图坐标的形式接收的,你需要将这些坐标映射到Scene中才能与元素进行交互。
4. 坐标映射
通常在处理Scene中的物品时,将坐标和任意形状从Scene映射到一个物品,从一个物品映射到另一个物品,或者从视图映射到Scene,都是很有用的。例如,当你在QGraphicsView的视口中点击鼠标时,你可以通过调用QGraphicsView::mapToScene()来询问Scene光标下方是什么 Item ,接下来是QGraphicsScene::itemAt()。如果你想知道 Item 在视口中的位置,可以在 Item 上调用QGraphicsItem::mapToScene(),然后在视图上调用QGraphicsView::mapFromScene()。最后,如果你想找到视图椭圆内的 Item ,可以将QPainterPath传递给mapToScene(),然后将映射的路径传递给QGraphicsScene::items()。
通过调用QGraphicsItem::mapToScene()和QGraphicsItem::mapFromScene(),可以将坐标和形状映射到物品的Scene中。你也可以通过调用QGraphicsItem::mapToParent()和QGraphicsItem::mapFromParent()映射到一个 Item 的父 Item ,或者通过调用QGraphicsItem::mapToItem()和QGraphicsItem::mapFromItem()在 Item 之间映射。所有的映射函数都可以同时映射点、矩形、多边形和路径。
在视图中也有相同的映射函数,用于与Scene的映射。QGraphicsView::mapFromScene()和QGraphicsView::mapToScene()。要从视图映射到物品,首先要映射到Scene,然后再从Scene映射到物品。
四、关键特性
1. 缩放和旋转
QGraphicsView通过QGraphicsView::setMatrix()支持与QPainter相同的仿射变换。通过对视图应用转换,我们可以轻松地添加对常见导航特性的支持,例如缩放和旋转。
下面是一个如何在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保持视图中心对齐。
2. 打印
图形视图通过其渲染函数QGraphicsScene::render()和QGraphicsView::render()提供单行打印。这些函数提供了相同的API:你可以通过将QPainter传递给任意一个渲染函数,让Scene或视图将它们的全部或部分内容渲染到任何绘制设备中。这个例子展示了如何使用QPrinter将整个Scene打印到一个完整的页面中。
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);
}
Scene和视图渲染函数之间的区别是一个在Scene坐标中操作,另一个在视图坐标中操作。QGraphicsScene::render()通常用于未变换地打印Scene的整个片段,例如绘制几何数据或打印文本文档。另一方面,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,你可以选择在内容拉伸时保持或忽略Scene的纵横比。
3. 拖放
因为QGraphicsView间接地继承了QWidget,它已经提供了QWidget提供的相同的拖放功能。此外,为了方便,图形视图框架提供了对Scene和每个 Item 的拖放支持。当视图接收到拖动时,它将拖放事件转换为QGraphicsSceneDragDropEvent,然后将其转发到Scene。Scene接管了这个事件的调度,并将其发送给鼠标光标下接受drop的第一个 Item 。
要从一个元素开始拖动,创建一个QDrag对象,向开始拖动的部件传递一个指针。多个视图可以同时观察 Item ,但只有一个视图可以启动拖动。在大多数情况下,拖动是从按下或移动鼠标开始的,因此在mousePressEvent()或mouseMoveEvent()中,可以从事件中获得widget的初始指针。例如:
void CustomItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QMimeData *data = new QMimeData;
data->setColor(Qt::green);
QDrag *drag = new QDrag(event->widget());
drag->setMimeData(data);
drag->start();
}
为了拦截Scene的拖放事件,我们需要在QGraphicsItem子类中重新实现QGraphicsScene::dragEnterEvent()和特定Scene需要的任何事件处理程序。我们可以在每个QGraphicsScene的事件处理程序的文档中阅读有关拖放图形视图的更多信息。
Item 可以通过调用QGraphicsItem::setAcceptDrops()来启用拖放支持。要处理传入的拖动,请重新实现QGraphicsItem::dragEnterEvent()、QGraphicsItem::dragMoveEvent()、QGraphicsItem::dragLeaveEvent()和QGraphicsItem::dropEvent()。
4. 鼠标指针和 提示
与QWidget类似,QGraphicsItem也支持鼠标(QGraphicsItem::setCursor())和ToolTip (QGraphicsItem::setToolTip())。当鼠标光标进入物品区域(通过调用QGraphicsItem::contains()检测到)时,QGraphicsView会激活鼠标指针和ToolTip。
你也可以通过调用QGraphicsView::setCursor()直接在视图上设置默认鼠标指针。
5. 动画
图形视图支持多个级别的动画。你可以通过使用动画框架轻松地组装动画。为此,我们需要我们的 Item 继承QGraphicsObject并将QPropertyAnimation与它们关联。QPropertyAnimation允许对任何QObject属性进行动画。
另一种选择是创建一个自定义项,它继承自QObject和QGraphicsItem。该 Item 可以设置自己的定时器,并在QObject::timerEvent()中以增量步骤控制动画。
第三种选择是通过调用QGraphicsScene::advance()来推进Scene,它又会调用QGraphicsItem::advance(),这主要是为了在Qt 3中与QCanvas兼容。
6. OpenGL渲染
要启用OpenGL渲染,只需通过调用QGraphicsView::setViewport()设置一个新的QGLWidget作为QGraphicsView的视口。如果你想要OpenGL抗锯齿,你需要OpenGL样本缓冲支持(参见QGLFormat::sampleBuffers())。
QGraphicsView view(&scene);
view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
7. Item group
通过将一项作为另一项的子元素,可以实现项分组最基本的特性:所有项都在一起移动,所有变换都可以从父元素传播到子元素。
此外,QGraphicsItemGroup是一个特殊项,它将子事件处理与用于向组中添加和从组中删除项的有用接口结合在一起。将一个元素添加到QGraphicsItemGroup将保持元素的原始位置和变换,而重新排列元素通常会导致子元素相对于新的父元素重新定位。为了方便,你可以通过Scene调用QGraphicsScene::createItemGroup()来创建QGraphicsItemGroups。
8. 部件和布局
Qt 4.4通过qgraphicwidget引入了对几何和布局感知 Item 的支持。这个特殊的基础项类似于QWidget,但与QWidget不同的是,它不是继承自QPaintDevice;而不是来自QGraphicsItem。这允许我们编写具有事件、信号和槽函数、大小提示和策略的完整窗口组件,我们还可以通过QGraphicsLinearLayout和QGraphicsGridLayout在布局中管理窗口组件的几何形状。
1. QGraphicsWidget
基于QGraphicsItem的功能和精简功能,QGraphicsWidget提供了两者的精华:来自QWidget的额外功能,如样式、字体、调色板、布局方向及其几何形状,以及来自QGraphicsItem的分辨率独立性和转换支持。因为图形视图使用真实坐标而不是整数,所以QGraphicsWidget的几何函数也可以操作QRectF和QPointF。这也适用于frame rects、margin和spacing。例如,使用QGraphicsWidget时,将内容边距指定为(0.5,0.5,0.5,0.5)是很常见的。既可以创建子窗口组件,也可以创建“顶级”窗口;在某些情况下,我们现在可以为高级MDI应用程序使用图形视图。
支持QWidget的一些属性,包括窗口标志和属性,但不是全部。我们应该参考qgraphicwidget的类文档,以获得支持什么和不支持什么的完整概述。例如,你可以通过传递Qt::Window窗口标志给qgraphicwidget的构造函数来创建装饰窗口,但是图形视图目前不支持macOS上常见的Qt::Sheet和Qt::Drawer标志。
2. QGraphicsLayout
QGraphicsLayout是专门为qgraphicwidget设计的第二代布局框架的一部分。它的API与QLayout非常相似。你可以在QGraphicsLinearLayout和QGraphicsGridLayout中管理部件和子布局。你也可以通过自己继承QGraphicsLaytItem来轻松编写自己的布局,或者通过编写QGraphicsLayoutItem的适配器子类来将自己的QGraphicsItem Item 添加到布局中。
9. 对嵌入Widget的支持
图形视图为将任何控件嵌入Scene提供无缝支持。可以嵌入简单的控件(如QLineEdit或QPushButton),复杂的控件(如QTabWidget),甚至是完整的主窗口。要将控件嵌入到Scene中,只需调用QGraphicsScene::addWidget(),或创建一个QGraphicsProxyWidget 实例来手动嵌入控件。
通过QGraphicsProxyWidget, Graphics View能够深度集成客户端widget功能,包括其光标,ToolTip,鼠标,平板电脑和键盘事件,子widget,动画,弹出窗口(例如,QComboBox或QCompleter),以及widget的输入焦点和激活。QGraphicsProxyWidget甚至集成了嵌入式widget的选项卡顺序,以便我们可以用选项卡进出嵌入式widget。我们甚至可以将新的QGraphicsView嵌入到我们的Scene中,以提供复杂的嵌套Scene。
在转换嵌入式控件时,Graphics View确保控件独立转换分辨率,允许字体和样式在放大时保持清晰。(注意,分辨率独立性的效果取决于样式。)
五、性能
为了准确、快速地为元素应用变换和特效,图形视图的构建假设用户的硬件能够为浮点指令提供合理的性能。
许多工作站和桌面计算机都配备了适当的硬件来加速这种计算,但一些嵌入式设备可能只提供处理数学运算或在软件中模拟浮点指令的库。
因此,在某些设备上,某些类型的效果可能比预期的要慢。可以通过在其他方面进行优化来弥补这种性能损失;例如,通过使用OpenGL渲染一个Scene。但是,如果这种优化本身也依赖于浮点硬件,那么性能就会下降。