[翻译] Qt 图形视图框架 – Graphics View Framework

目录 Content
[hide]

翻译

参见:https://doc.qt.io/qt-5/graphicsview.html

Qt 的图形视图,提供了一个界面用来管理大量2D图形项目;一个视图部件用来可视化这些图像项目,支持缩放和旋转。这个框架包括一个事件传播体系结构,该体系结构对场景中的项目进行精度和双精度交互功能。项目可以处理按键事件。鼠标按下、移动、释放和双击事件,他们还可以耿总鼠标的移动。图形视图使用BSP树提供了非常快速的项目检索功能,因此它可以实时可视化大型场景,甚至包括数百万的项目。

The Graphics View Architecture

The Scene

QGraphicsScene 具有以下职责:

  • 提供用于管理大量项目的快速界面
  • 将事件传播到每个项目
  • 管理项目(Item)状态,例如选择和焦点处理
  • 提供未变换的渲染功能,主要用于打印。

场景充当 QGraphicsItem 对象的容器。

  • QGraphicsScene::addItem() 将 Item 添加到 scene 中,并可以通过调用发现功能来检索。
  • QGraphicsScene::items()  及其重载返回由 点、矩形、多边形或常规矢量路径包含或者相交的所有 Items。
  • QGraphicsScene::itemAt()  返回特定点最上层的 Item。所有项目发现功能以降序堆叠方式返回(例如 首先返回最上层的,最后的项目是最底部)。
  QGraphicsScene scene;
  QGraphicsRectItem *rect = scene.addRect(QRectF(0, 0, 100, 100));

  QGraphicsItem *item = scene.itemAt(50, 50);
  // item == rect

QGraphicsScene 的事件传播架构可安排 scene 事件以传递给 Items,并管理 Items 之间的传播。如果 scene 在某个位置接收到鼠标按下事件,scene 会将事件传递到该位置上任何 Item。

QGraphicsScene 还管理某些 Item 的状态,例如 选择 和 焦点。

  • QGraphicsScene::setSelectionArea() 传递任意形状来选择 scene 中的 items。此功能还用作 QGraphicsView 中橡皮筋选择功能的基础。
  • QGraphicsScene::selectedItems() 返回所有当前选择的 Items 列表。
  • QGraphicsScene::setFocusItem() or QGraphicsItem::setFocus() 用来设置 Item 焦点
  • QGraphicsScene::focusItem() 返回当前的焦点 Item
  • QGraphicsScene::render() 可将场景的一部分渲染到打印设备中。

The View

QGraphicsView 提供了 view 部件,可将 scene 内容可视化。你可以将多个 views 附加到同一个 scene,以在同一个数据集中提供多个  viewports 。view 部件使一个滚动区域,并提供在大型场景中导航的滚动条。如果要启用 OpenGL 支持,可以通过调用 QGraphicsView::setViewport() 将一个 QGLWidget 设置为 viewport。

view 从键盘和鼠标接收输入事件,然后将其转换成 scene 事件(在适当的情况下将其转换为 scene 坐标),然后再将事件发送到已视化的场景。

  • QGraphicsView::transform() 变换矩阵,view 可变换(transform) scene 的坐标系统。这允许高级导航特性,例如缩放和旋转。
  • QGraphicsView::mapToScene() 将 view 中的坐标(QPoint 类型) 转换成 scene 的坐标 (QPointF 类型)
  • QGraphicsView::mapFromScene() 将 scene 中的坐标 (QPointF 类型) 转换成 view 中的坐标 (QPoint 类型)

The Item

QGraphicsItem 是 scene 中各种 图形 Items 的基类。图形视图为典型形状提供了几种标准 Item,例如 矩形(QGraphicsRectItem)、椭圆 (QGraphicsEllipseItem)文本 (QGraphicsTextItem),但当你写一个自定义 Item 时,最强大的 QGraphicsItem 特性是可用的。除此之外,QGraphicsItem  支持以下特性:

  • 鼠标按下、移动、释放和双击事件,以及鼠标悬停(hover)事件、滚轮事件和上下文菜单(context menu)事件
  • 键盘输入焦点和按键事件
  • 拖和放
  • 分组,通过父子关系以及 QGraphicsItemGroup 进行分组
  • 碰撞检测

Items 位于局部坐标系统,像 QGraphicsView ,它提供了许多功能,用于 item 和 scene 之间 和 Item 和 Item 之间映射坐标,同样,它可以使用 QGraphicsItem::transform() 矩阵变换其坐标系。这用于旋转和和缩放单个 Item 很有用。

Item 可以包含其他 Items(子项)。父项的变换由其所有子项继承。不过,无论一个Item的累加变换如何,它的所有功能(例如 QGraphicsItem::contains(), QGraphicsItem::boundingRect(), QGraphicsItem::collidesWith()  )仍在局部坐标系下运行。

QGraphicsItem 支持通过 QGraphicsItem::shape()   和 QGraphicsItem::collidesWith() 这两个虚函数进行碰撞检测。通过从 QGraphicsItem::shape()  返回 Item的形状作为局部坐标 QPainterPath , QGraphicsItem  将处理所有的碰撞检测。然而,如果要提供自己的碰撞检测,可以重写 QGraphicsItem::collidesWith()

The Graphics View Coordinate System

图形视图是基于笛卡尔坐标系;Item 在 scene 中的位置和几何形状由两个数字表示: x坐标 和 y坐标。使用未变换的视图观看场景时,场景中的单元由屏幕上的像素表示。

注意:由于“图形视图”使用Qt的坐标系,因此不支持反转的Y轴坐标系(其中y向上增长)。

图形视图中由三个有效的坐标系统:Item 坐标, scene 坐标和 view 坐标。为简化实现,“图形视图” 提供了便捷功能可以让你在三个坐标系之间进行映射。

渲染时,图形视图的 scene 坐标对应 QPainter 的 logical 坐标,并且 view 坐标与 device 坐标相同。在文档 “Coordinate System ” 中,可以阅读 logical 坐标和 device 坐标之间的关系。

Item Coordinates

Items 位于它们自己的本地坐标系中。它们的坐标通常以其中心点(0,0)为中心,这也是所有变换的中心。Item坐标系中的几何图元通常称为 Item points,Item lines,或 Item rectangles。

当创建一个自定义 Item 时,你只需要关注 Item 坐标。QGraphicsScene 和 QGraphicsView 将为你执行所有转换。这使得实现自定义项非常容易。例如,如果收到鼠标按下或者拖动输入事件,则事件位置以Item坐标给出。 QGraphicsItem::contains()  虚函数,参数为 Item坐标系中的点,如果位于 Item内,则返回 True,否则 False。类似的,Item 的 bounding rect  和 shape  也是 Item 坐标。

在 Item 的 position,是Item的中心点在父级坐标系中的坐标;有时称为 parent 坐标。从这个意义上说,scene 被视为所有无父级Item的 “父级” 。顶级 Item 的 position 是  scene 坐标。

子坐标是相对于父坐标的。如果子对象没有变换,则子坐标和父坐标之间的偏差与父坐标中各 Items之间的距离相同(If the child is untransformed, the difference between a child coordinate and a parent coordinate is the same as the distance between the items in parent coordinates. )。例如:如果未变换的子Item正好位于父 Item 的中心点,则两个Item的坐标系将相同。然而,如果子项位置是 (10,0),则子项 的(0,10)点将对应于父项的 (10,10)点。

因为 Item 的位置和变换是相对于父项,所以子Item的坐标不受父Item的变换的影响,尽管父项的变换会隐式地变换子项。在上述示例中,即使父项旋转和缩放,子项的(0,10)的点将仍对应父项的(10,10)点。但是,相对于 scene,子项 将跟随父项的变换和位置。如果父项缩放为(2x,2x),子项在scene坐标(20,0)处,并且(10,0)点将于scene上的(40,0)相对应。

由于 QGraphicsItem::pos() 是少数例外之一,因此 QGraphicsItem 的函数是在 Item 坐标中操作,与 Item 或其父项无关。例如:Item 的边界矩形(:boundingRect())始终是给出Item坐标。

Scene Coordinates

scene 代表它的所有Items的基本坐标系。scene 坐标系描述了每个顶层 Item 的位置,并且还构成了从 view 传递到 scene 所有事件的基础。scene中每个item 除了它的本地 item 位置和边界矩形之外,还具有场景位置边界矩形(QGraphicsItem::scenePos(), QGraphicsItem::sceneBoundingRect())。场景位置描述了Item在scene坐标系中的位置,场景边界矩形构成了 QGraphicsScene 如何确定 scene 的哪些区域已更改的基础。scene 中的更改通过 (QGraphicsScene::changed())信号传达,参数是一个 场景矩形 列表。

View Coordinates

View 坐标是 widget 的坐标。view  坐标中的每个单元对应一个像素。此坐标系的特殊之处在与它相对于 widget 或者 viewport ,并且不受所观察 scene 影响。QGraphicsView 的 viewport 的左上角始终为 (0,0),而其右下角始终为(viewport 宽带,viewport高度)。所有鼠标事件和拖放事件最初都是作为view坐标接收的,并且你需要将这些坐标映射到 scene 以便与 Item 进行交互。

Coordinate Mapping

通常,在处理 scene 中的 Items 时,将 scene 中的坐标和任意形状映射到 Item,Item 到 Item ,或者 view 到 scene ,会很有用。例如:

  • 当你在 QGraphicsView的 viewport 中单击鼠标左键时,可以通过调用 QGraphicsView::mapToScene() ,紧跟着调用 QGraphicsScene::itemAt() 来查询 scene 光标下是什么 Item。
  • 如果你想知道某个 Item 在 viewport 中的位置,你可以在该 Item 上调用 QGraphicsItem::mapToScene() ,然后在 view 上调用 QGraphicsView::mapFromScene() 。
  • 如果你想查找一个 椭圆 view 区域有什么 Item ,则可以将 QPainterPath  传递给 mapToScene(),然后将映射路径传递给 QGraphicsScene::items()。

你可以:

  • QGraphicsItem::mapToScene() 和 QGraphicsItem::mapFromScene() :在 Item 和 scene 之间来回映射坐标和形状。
  • QGraphicsItem::mapToParent() 和 QGraphicsItem::mapFromParent():在Item 和 parent 之间来回映射。
  • QGraphicsItem::mapToItem() :在 Item 之间映射。
  • 所有映射功能都可以映射点,矩形,多边形和路径。

同样的映射功能也存在 view 中,QGraphicsView::mapFromScene() 和 QGraphicsView::mapToScene() 用于与 scene 之间的映射。要从 view 映射到 item ,要先映射到 scene,然后从 scene 映射到 Item.

Key Features

Zooming and rotating

QGraphicsView 支持通过 QGraphicsView::setMatrix() 进行与 QPainter 同样功能的仿射变换。通过对视图应用变换,你可以轻松添加对常用的导航特性(例如缩放和旋转)的支持。

  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); }
      ...
  };

这些 Slots 可以连接到 QToolButtons 并启用 autoRepaet 。QGraphicsView 可在变换视图时,使视图的中心对齐。

参见 “弹性节点”例子(Elastic Nodes)代码展示如何实现基本缩放功能

Printing

图形视图通过其渲染功能 QGraphicsScene::render() QGraphicsView::render() 提供单行(single-line )打印。这些函数提供相同的 API : 通过将 QPainter 传递给任一渲染功能,可使 scene 或者  view 全部或者部分内容渲染到 paint device 中。下面示例演示如何使用 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 和 view 渲染功能的不同之处在于一个是在 scene坐标中操作,另一个是在 view坐标中操作。QGraphicsScene::render() 通常用于打印未变换的scene的整个片段,例如绘制几何数据或打印文本文档。另一方面, QGraphicsView::render() 适合拍摄屏幕截图;它的默认行为是使用提供的 painter 渲染 viewport 的确切内容。

  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 的纵横比。

Drag and Drop

由于 QGraphicsView  间接继承 QWidget ,因此它已经提供了与 QWidget 提供相同的拖放功能。此外,为方便起见,图形视图框架为scene以及每个 Item 提供了缩放支持。当 view 接收到拖动时,它将拖放事件转换成 QGraphicsSceneDragDropEvent,然后将其转发到 scene 。scene 将接管此事件的调度,并将其发送到接受放置的鼠标光标下的第一个 Item。

要从 Item 开始拖动,创建一个 QDrag 对象,并将指针传递给开始拖动得 Widget 。Item 能够被多个 views 同时观察,但只有一个 view 可以开始拖动。大多数情况下,拖动是由于按下鼠标或移动鼠标而开始的,因此你可以在 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 需要的任何事件的处理程序。你可以在图形视图文档中阅读更多针对每个 QGraphicsSence 的事件处理程序的内容。

Item 可以通过调用 QGraphicsItem::setAcceptDrops() 来启用拖放支持。要处理传入的拖动,重新实现 :

  • QGraphicsItem::dragEnterEvent()
  • QGraphicsItem::dragMoveEvent()
  • QGraphicsItem::dragLeaveEvent()
  • QGraphicsItem::dropEvent()

参见示例 “拖放机器人” Drag and Drop Robot 以了解图形视图对拖动和放置操作的支持。

Cursors and Tooltips

像 QWidget 一样,QGraphivsItem 也支持光标 QGraphicsItem::setCursor() 和 工具提示 QGraphicsItem::setToolTip()。当鼠标光标进入  Item 区域(由调用 QGraphicsItem::contains() 检测),  QGraphicsView 将激活光标和工具提示。

你可以直接在在 view 中通过调用 QGraphicsView::setCursor() 设置默认光标。

参见示例 “拖放机器人” Drag and Drop Robot 以获取实现工具提示和光标形状处理的代码。

Animation

图形视图支持多个级别的动画,你可以使用动画框架轻松地组装动画。为此,你需要 Item 从 QGraphicsObject 继承,并将 QPropertyAnimation 与它们关联。QPropertyAnimation  允许设置任何 QObject 属性的动画。

另一个选择是创建一个自定义 Item,继承自  QObject 和 QGraphicsItem。Item 可以设置自己的计时器,并通过 QObject::timerEvent() 中的增量步骤控制动画。

第三个选项,主要用于与 Qt3 中的 QCanvas 兼容,是通过掉调用 QGraphicsScene::advance() 来推进 scene ,依次调用 QGraphicsItem::advance()。

OpenGL Rendering

要启用 OpenGL  渲染,只需要调用 QGraphicsView::setViewport() 即可将新的 QGLWidget 设置为 QGraphicsView 的 viewport。如果要使用抗锯齿的 OpenGL,则需要 OpenGL 样本缓冲支持。(参阅 QGLFormat::sampleBuffers())。

例如:

  QGraphicsView view(&scene);
  view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));

Item Groups

通过使一个 Item 成为其他的子项,可以实现 Item 分组的最基本特性:这些 Items 将一起移动,并且所有的变换都从父项传播到子项。

此外,QGraphicsItemGroup  是一个特殊的 Item,他将子项事件处理与一个有用的界面组合在一起,用于在组中添加或者删除项。

将 Item 添加到 QGraphicsItemGroup 将保留该 Item 的原始位置和变换,而通常重新父化将导致子项相对于新的父项重新定位。为了方便起见,可以通过调用 QGraphicsScene::createItemGroup() 在整个 scene 中创建 QGraphicsItemGroups。

Widgets and Layouts

Qt4.4  通过 QGraphicsWidget 引入了对几何和布局感知 Items 的支持。这个特殊基础 Item 类似 QWidget , 但它与 QWidget 不同,它不是从 QPaintDevice 继承而来,也不是从   QGraphicsItem。 这使你可以编写带有事件信号槽、大小提示和策略的完整的 widget,同时你也可以通过 QGraphicsLinearLayout 和 QGraphicsGridLayout.管理布局中的 widget 几何形状。

QGraphicsWidget

基于 QGraphicsItem 的功能和精简版图,QGraphicsWidget 提供了两全其美的功能:QWidget 的其他功能,例如 样式、字体、调色板、布局方向及其几何形状,以及 QGraphicsItem 分辨率独立性和变换支持。 由于图形视图使用实数坐标而不是整数坐标, QGraphicsWidgets的几何函数也可以在 QRectF 和 QRect 上操作。这也适用于 框架矩形 Frame rects,边距 margins 和 间距 spacing。例如,使用 QGraphicsWidget  指定内同边距为 (0.5, 0.5, 0.5, 0.5) 并不少见。你可以创建 subwideget 和 “top-level”  窗口;在某些情况下,你可以uong 图形视图 创建 MDI 应用。

某些 QWidgets 的属性是支持的,包括 窗口标志和属性,但不是全部。你应该参考 QGraphicsWidget 类的文档,以全面了解什么吃不支持的内容。例如:你可以通过将 Qt::Windows 窗口标志传递给 QGraphicsWidget 的构造函数来创建装饰窗口,但是文档视图当前不支持 macOS 上常见的 Qt::Sheet 和Qt:: Drawer 标识。

QGraphicsLayout

QGraphicsLayout 是专门为 QGraphicsWidget 设计的第二代布局框架的一部分。它的  API 与 QLayout 非常类似。你可以在 QGraphicsLinearLayout 和 QGraphicsGridLayout 中管理 widget 和 子布局。你还可以通过子类化QGraphicsLayout轻松地编写自己的布局,或者通过编写QGraphicsLayoutItem的适配器子类缧降自己的 QGraphicsItem Item 添加到布局中。

Embedded Widget Support

图形视图 提供了无缝支持可以将任何 widget 嵌入到 scene 。你可以嵌入简单的 widget , 像 QLineEdit 或者 QPushButton,复杂的 Widget ,如 QTabWidget ,甚至完整的主窗口。要将 wideget 嵌入到 scene ,只需要简单调用 QGraphicsScene::addWidget() ,或者创建 QGraphicsProxyWidget  的实例,即可手动嵌入 widget。

通过 QGraphicProxyWidget ,图形视图能够深度集成客户端widget特性,包括其光标、工具提示、鼠标、平板电脑和键盘事件、子widget、动画、弹窗(例如 QComboBox 或者 QCompleter )以及 widget 的输入焦点和激活。QGraphicsProxyWidget 甚至继承了嵌入 widget 选项卡属性,以便你可以在嵌入式widget切入和切出。你真是可以将新的 QGraphicsView 嵌入到你的 scene 以提供复杂的嵌套场景。

变换嵌入的 widget 时, 图形视图确保独立变换widget的分辨率,放大时允许字体和样式保持清晰。(请注意,分辨率独立性的效果取决于样式。)

Performance

Floating Point Instructions

为了准确快速在 Items 上应用变换和效果,图形视图的构建假设用户的硬件能够为浮点指令提供何合理的性能。

许多工作站和台式计算机都配备了合适的硬件来加速这种计算,但是某些嵌入式设备可能仅提供用于处理数学运算或在软件中模拟浮点指令的库。

因此,某些种类的效果可能会比某些设备上预期的要慢。通过其他方面的优化,可能会弥补这种性能的下降,例如,通过使用 OpenGL 渲染场景。但是,如果此类优化也依赖于浮点硬件的存在,他们本身可能会导致性能下降。

延伸阅读

  • QGraphicsItem中的碰撞检测描述
    https://blog.csdn.net/ykm0722/article/details/7053653
  • QGraphicsItem中的碰撞检测描述
    https://blog.csdn.net/Johnable/article/details/101775472

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.