Qt之图形视图框架

时间:2023-02-01 18:24:28

图形视图(Graphics View)提供了一个平台,用于大量自定义2D图形项(Item)的管理与交互,并提供了一个视图部件(view widget)来显示可以缩放和旋转的视图项(items)。

框架包括一个事件传播架构,支持scene中的items进行精确的双精度交互功能。Items(图形元素)可以处理键盘事件、鼠标按下、移动、释放和双击事件,同时也能跟踪鼠标移动。

图形视图使用一个BSP(Binary Space Partitioning - 二叉空间分割)树,以提供对图形元素的快速查找,可以想像,即使在包含数以百万计items的超大场景中,也能够实时显示。

图形视图架构

图形视图提供了一个item-based方式来实现model-view编程,这一点很像例程InterView中的便利类 QTableView、QTreeView和QListView。多个views可以观察一个单独的scene,场景则包含了不同的几何形状的items 。

Scene

QGraphicsScene提供了图形视图场景(scene)。scene有以下职责:

  • 提供一个用于管理大量items的快速接口
  • 传递事件到每个item
  • 管理item的状态,例如:选择和焦点处理
  • 提供未进行坐标变换的渲染功能,主要用于打印

scene serves是QGraphicsItem对象的容器。调用QGraphicsScene::addItem()将items添加到scene中后,你就可以通过调用场景管理器中的不同的查找函数来查找其中的图形对象。QGraphicsScene::items()函数及其重载函数可以返回所有通过点、矩形、多边形、或路径等不同方式选中的所有items。 QGraphicsScene::itemAt()返回在指定点位置上最上面的对象。所有找到的items按照层叠递减的排列顺序(即:第一个返回的item是最顶层的,最后一个则是最底层的)。

QGraphicsScene scene;
QGraphicsRectItem *rect = scene.addRect(QRectF(0, 0, 100, 100));

QGraphicsItem *item = scene.itemAt(50, 50);
// item == rect
  • 1
  • 2
  • 3
  • 4
  • 5

QGraphicsScene的事件传递机制负责将scene事件传递给items,同时也管理items之间的传递。如果场景在某个位置得到一个鼠标按下事件,就将该事件传递给这个位置上的item。

QGraphicsScene同时还管理某些items的状态,例如:item的选中状态和焦点状态。可以通过调用 QGraphicsScene::setSelectionArea(),传递一个任意形状,就可以选中scene中的items。此功能也被用于QGraphicsView中rubberband选中的基础。通过调用QGraphicsScene::selectedItems()可以获取当前选中的items列表。另外一种通过QGraphicsScene来管理的状态是:一个item是否有键盘输入焦点。你可以调用QGraphicsScene::setFocusItem()或QGraphicsItem::setFocus()为一个item设置焦点,或者通过QGraphicsScene::focusItem()获取当前的焦点item。

最后, QGraphicsScene允许你通过QGraphicsScene::render()将部分scene绘制到paint device上。你可以在本文关于“打印”章节了解更多细节。

View

QGraphicsView提供了视图部件,将一个scene中的内容显示出来。你可以附加几个views到同一个场景,从而提供一些viewports到同一数据集。该view部件是一个scroll area,为大场景浏览提供滚动条。如果要启用OpenGL支持,可通过调用QGraphicsView::setViewport(),将一个QGLWidget设置为viewport。

QGraphicsScene scene;
myPopulateScene(&scene);

QGraphicsView view(&scene);
view.show();
  • 1
  • 2
  • 3
  • 4
  • 5

view通过键盘和鼠标接接收输入事件,并将它们转换成scene事件(同时将使用的坐标转换为场景坐标),然后将事件发送给可视化的scene。

利用其变换矩阵QGraphicsView::transform(),view可以转变scene的坐标系,从而实现缩放、旋转等高级查看功能。为了方便起见,QGraphicsView同时也提供了用于view和scene坐标之间转变的函数:QGraphicsView::mapToScene() and QGraphicsView::mapFromScene()。

Qt之图形视图框架

Item

QGraphicsItem是一个scene中图形项的基类。图形视图提供了一些标准形状的items,例如:矩形 ( QGraphicsRectItem )、椭圆 ( QGraphicsEllipseItem ) 和文本项 ( QGraphicsTextItem )。但 QGraphicsItem最强大的特点是支持自定义图形项。除了其它事项外,QGraphicsItem支持以下特性:

  • 鼠标按下、移动、释放和双击事件,同时还支持鼠标悬浮事件、滚轮事件和上下文菜单事件。
  • 键盘输入焦点和键盘事件。
  • 拖放。
  • 组合:通过父子关系,或通过QGraphicsItemGroup。
  • 碰撞检测。

与QGraphicsView类似,处于局部坐标系下的Items,也提供了tem和scene之间、以及item到item的坐标系映射函数。此外,和QGraphicsView一样,他还可以通过一个matrix来转换其自身的坐标系统,这一点对于旋转和缩放单个item非常有用。

Qt之图形视图框架

图形视图框架中的类

这些类提供了一种创建交互式应用程序的框架。

描述
QAbstractGraphicsShapeItem 所有路径items的共同基类
QGraphicsAnchor 表示一个QGraphicsAnchorLayout中两个items之间的anchor
QGraphicsAnchorLayout 布局可以anchor widgets到图形视图中
QGraphicsEffect 所有图形特效的基类
QGraphicsEllipseItem 可以添加到QGraphicsScene的椭圆item
QGraphicsGridLayout 图形视图中管理部件的网格布局
QGraphicsItem QGraphicsScene中所有图形项的基类
QGraphicsItemGroup 一个将items组当做单个item来看待的容器
QGraphicsLayout 图形视图中所有布局类的基类
QGraphicsLayoutItem 可以被继承,允许布局类管理的自定义items
QGraphicsLineItem 可以添加到QGraphicsScene的直线item
QGraphicsLinearLayout 图形视图中管理部件的水平或垂直布局
QGraphicsObject 所有需要信号、槽、属性的图形项的基类
QGraphicsPathItem 可以添加到QGraphicsScene的路径item
QGraphicsPixmapItem 可以添加到QGraphicsScene的图形item
QGraphicsPolygonItem 可以添加到QGraphicsScene的多边形item
QGraphicsProxyWidget 代理,用于将一个QWidget对象嵌入到QGraphicsScene中
QGraphicsRectItem 可以添加到QGraphicsScene的矩形item
QGraphicsScene 管理大量2D图形项的管理器
QGraphicsSceneContextMenuEvent 图形视图框架中的上下文菜单事件
QGraphicsSceneDragDropEvent 图形视图框架中的拖放事件
QGraphicsSceneEvent 所有图形视图相关事件的基类
QGraphicsSceneHelpEvent Tooltip请求时的事件
QGraphicsSceneHoverEvent 图形视图框架中的悬停事件
QGraphicsSceneMouseEvent 图形视图框架中的鼠标事件
QGraphicsSceneMoveEvent 图形视图框架中的widget移动事件
QGraphicsSceneResizeEvent 图形视图框架中的widget大小改变事件
QGraphicsSceneWheelEvent 图形视图框架中的鼠标滚轮事件
QGraphicsSimpleTextItem 可以添加到QGraphicsScene的简单文本item
QGraphicsSvgItem 可以用来呈现SVG文件内容的QGraphicsItem
QGraphicsTextItem 文本item ,可以添加到QGraphicsScene,用于显示格式化文本
QGraphicsTransform 创建QGraphicsItems高级矩阵变换的抽象基类
QGraphicsView 显示一个QGraphicsScene内容的widget
QGraphicsWidget QGraphicsScene中所有widget items的基类
QStyleOptionGraphicsItem 用于描述绘制一个QGraphicsItem所需的参数

图形视图坐标系统

图形视图基于笛卡儿坐标系,items的位置和几何形状由两组数据来表示:x坐标和y坐标。如果使用一个未转换的view来观察一个scene,scene中的一个单元将会表现为screen上的一个像素。

注意 :图形视图使用了Qt的坐标系,不支持反转的y轴坐标系(即y向上为正方向)。

图形视图中使用了三种有效的坐标系:Item坐标、scene 坐标、view坐标。为了简化实现工作,图形视图提供了一系列非常方便的函数,来进行三个坐标系下的坐标映射。

渲染时,图形视图的scene 坐标对应于QPainter的逻辑坐标,view坐标与设备坐标一致。在Coordinate System(见助手)一文中,可以参考更多关于逻辑坐标和设备坐标关系的内容。

Qt之图形视图框架

Item坐标系

Items生活在自己的局部坐标系。它们的坐标通常以中心点(0, 0)为中心,同时这也是所有转换的中心。item 坐标系下的几何元素通常用点、线或矩形来表示。

创建自定义item时,你只需关注item坐标系即可。QGraphicsScene和QGraphicsView会为你执行所有相关的转换,这样一来,实现自定义items就容易多了。比如:当你收到鼠标按下或拖拽事件时,事件位置已经被转换到了item坐标系下。类似的, item的矩形边界和形状都是基于item坐标系的。

一个item的位置是item坐标系下的中心点在其父对象坐标系下的位置,有时也被称为父坐标。对于所有没有父对象的对象来说,场景就是其父对象。因此最顶层items的位置就是其在scene中的位置。

子对象坐标系是相对于父对象坐标系来说的一个概念。如果子节点没有进行矩阵变换,那么在子对象坐标系和父对象坐标系的差异就和这些对象在父对象中的偏移一样。比如:如果一个未经变换的子对象精确的位于父对象的中心点,那么这两个对象的坐标系就是完全一致的。如果子对象的位置是(10, 0),那么子对象的(0, 10)点就位于父对象的(10, 10)点的位置。

由于items的位置和转换是相对于父对象来说的,因此虽然父对象的变换隐式地变换了子对象,子对象的坐标系不会因父对象坐标系改变而改变。在上面的例子中,即使父对象经过了旋转和缩放,子对象的(0, 10)点依然相对于父对象是(10, 10)点。不过相对于scene来说,子对象将随着父对象进行变换和偏移 。如果父对象缩放了(2x, 2x),那么子对象在场景坐标系下将会位于(20, 0)的位置,同时其(10, 0) 点将会对应于场景中的(40, 0)点。

不管item或父对象进行了什么样的变化, QGraphicsItem的函数一般总是表示在item坐标系下的位置,其操作也作用于item坐标系内。比如:一个对象的矩形边界(QGraphicsItem::boundingRect())总是在item坐标系下给出的。但是QGraphicsItem::pos()是例外之一,该函数表示其在父对象中的位置 。

Scene坐标系

场景表示了其中所有items的基础坐标系。场景坐标系描述了每一个顶层对象的位置,同时构成了所有从视图传递到场景的事件的变化基础。场景中的每一个对象都有其在场景中的位置和矩形边界( QGraphicsItem::scenePos()、QGraphicsItem::sceneBoundingRect());另外,也有其自身的位置和矩形边界。场景位置描述了对象在场景坐标系下的位置,场景矩形边界则提供给QGraphicsScene来决定场景中的哪一块区域已经被改变了。场景中的变化通过QGraphicsScene::changed()信号发出,参数是场景坐标系下的矩形列表。

View坐标系

视图坐标系是widget的坐标系,视图坐标系下的每个单位对应于一个像素。对于该坐标系来说比较特殊的一点是,它是相对于widget或者viewport的坐标系,不会受被显示的场景所影响。QGraphicsView的viewport左上角坐标总是(0, 0),右下角坐标总是(viewport width, viewport height)。所有的鼠标事件和拖拽事件都在视图坐标系下接收,你需要将这些坐标映射到场景中后才能与场景中的图形对象进行交互。

坐标映射

通常处理场景中的对象时,坐标变换将会非常有用,我们可以把坐标或者任意形状从场景变换到对象坐标系下,从一个对象坐标系变换到另一个对象坐标系,或者从视图变换到场景。例如:当用鼠标点击了 QGraphicsView的viewport,你可以向scene询问当前鼠标下方的是什么item(调用 QGraphicsView::mapToScene()变换坐标,然后通过QGraphicsScene::itemAt()查询对象)。如果想知道一个item处于viewport中的位置,你可以调用item的函数QGraphicsItem::mapToScene(),然后再调用视图的函数QGraphicsView::mapFromScene()。最后,如果想查找位于一个椭圆区域内的对象items,你可以把一个QPainterPath对象传递给mapToScene()然后将变换之后的path传递给QGraphicsScene::items()。

你可以通过调用QGraphicsItem::mapToScene ()将坐标或者任意形状映射到对象的场景中去,同样也可以通过调用QGraphicsItem::mapFromScene()将其映射回来。你也可以调用QGraphicsItem::mapToParent ()将对象映射到其父对象的坐标系下,同样也可以通过调用QGraphicsItem::mapFromParent()将其映射回来。甚至可以用过调用QGraphicsItem::mapToItem()和QGraphicsItem::mapFromItem()在不同的对象的坐标系之间进行映射。所有的映射函数均支持点、矩形、多边形和路径。

在视图和场景之间也存在着同样的映射函数:QGraphicsView::mapFromScene()和QGraphicsView::mapToScene()。要从视图映射到对象,第一步是映射到场景,然后才能从场景映射到对象坐标系下。

主要特点

缩放和旋转

和QPainter一样,QGraphicsView也可以通过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); }
...
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

槽可以关联到启用了“autoRepeat”属性的QToolButtons。

在变换视图的过程中,QGraphicsView始终保持与视图中心对齐。

参见:Elastic Nodes Example,了解如何实现这种基本的缩放操作。

打印

图形视图通过其渲染函数QGraphicsScene::render()和QGraphicsView::render(),提供了非常简单的打印功能。这两个函数提供了相同的 API:你可以让场景或视图将全部或部分的内容渲染到任何paint device(注:QImage、QPrinter、QWidget均属于paint device)上,只需要将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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

场景和视图绘制函数的区别在于:前者操作的是场景坐标,后者操作的则是视图坐标。 QGraphicsScene::render()多用于打印一个未转换的场景各部分,比如:打印几何数据图表,或文本文档。 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");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

当源区域和目标区域的大小不匹配时,源区域内容将会被缩放来适应目标区域。通过传递Qt::AspectRatioMode参数给你使用的渲染函数,在内容被缩放时,可以选择保持或者忽略scene的纵横比。

拖放

由于QGraphicsView间接地从QWidget继承,因此QGraphicsView也提供了和QWidget一样的拖放功能。此外,为方便起见,图形视图框架为场景中的每个对象提供了拖放支持。当view接收到一个drag,它将拖放事件转换为一个QGraphicsSceneDragDropEvent,然后将其转发给scene。scene则会接管该事件的调度,并将其发送给鼠标下面第一个接受drops的对象。

要拖拽一个对象,只需要创建一个QDrag对象,将指针传给开始拖拽的widget。Items可以同时被多个views观察,但是只有一个视图可以进行拖拽。在大多数情况下,拖拽都从鼠标按下或移动开始,因此在 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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

要拦截场景中的拖放事件,需要实现QGraphicsScene::dragEnterEvent(),选择你需要处理的事件,然后进行相应处理即可。你可以到 QGraphicsScene的文章中查看更多关于拖放的内容。

Items通过调用QGraphicsItem::setAcceptDrops()来启用对拖放的支持;如果要处理拖动,需要实现 QGraphicsItem::dragEnterEvent()、QGraphicsItem::dragMoveEvent()、QGraphicsItem::dragLeaveEvent()、QGraphicsItem::dropEvent(),这几个事件。

参见:Drag and Drop Robot example,了解关于图形视图拖拽的操作。

光标和tooltip

和QWidget一样,QGraphicsItem也支持设置光标(QGraphicsItem::setCursor())和tooltip(QGraphicsItem::setToolTip())。当鼠标光标进入item区域(由QGraphicsItem::contains()检测)时,光标和tooltip就会被QGraphicsView激活。

你也可以通过调用QGraphicsView::setCursor(),直接为view设置一个默认的光标。

参见:Drag and Drop Robot example,了解关于tooltip和光标形状的操作。

动画

图形视图在几个层面上提供了对动画的支持。你可以用Animation Framework轻松地设置动画:只需要让你的items从QGraphicsObject继承,然后将QPropertyAnimation绑定到上面。QPropertyAnimation可以为任何QObject属性实现动画效果。

另外一个选择是:创建一个从QObject和QGraphicsItem继承的对象。该item可以设置自己的timers,然后在QObject::timerEvent()中控制动画。

第三个选择仅限于与Qt3中的QCanvas兼容。调用QGraphicsScene::advance()从而会依次调用 QGraphicsItem::advance()。

OpenGL渲染

要启用OpenGL渲染,只需简单地创建一个新的QGLWidget对象,并调用QGraphicsView::setViewport()将其作为视口设置为视图即可。如果你希望使用 OpenGL 的反锯齿,则需要OpenGL支持采样缓存(参见:QGLFormat::sampleBuffers())。

示例:

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

对象组

通过将一个对象设置为另一个对象的子对象,就可以得到对象组最重要的功能:对象会一起移动,所有转换都会从父对象传播到子对象中。

此外,QGraphicsItemGroup是一个特殊的item,它提供了对子对象事件的支持,同时还提供了用于添加和删除子对象的接口。将一个对象添加到QGraphicsItemGroup将保持对象原始的位置和坐标变换,不过重新设置对象的父对象则会导致对象重新定位到相对于父对象的位置。为了方便起见,你可以调用QGraphicsScene::createItemGroup()来创建QGraphicsItemGroup对象。

Widgets和Layouts

Qt4.4通过QGraphicsWidget引入了对几何体和对布局敏感的对象的支持。这一特殊的基类item和 QWidget类似,但是不像QWidget,它没有从QPaintDevice继承,而是从QGraphicsItem。这样就允许你完全实现能够处理事件、信号与槽、大小调整和策略的widget,同时你还可以通过QGraphicsLinearLayout和QGraphicsGridLayout来管理widget的几何元素。

QGraphicsWidget

QGraphicsWidget建立在QGraphicsItem之上,具有QGraphicsItem的所有功能,保持了较小的资源占用,同时提供了两者的优势:来自QWidget的额外的功能,比如样式、字体、调色板、布局、几何形状,来自QGraphicsItem的分辨率独立性和坐标变换的支持。由于图形视图使用真实的坐标而不是整数,因此 QGraphicsWidget的几何形状函数可以同时操作QRectF和QPointF。同时也能应用到边框的大小、边距和间距上,例如:在QGraphicsWidget上将对象的边距设置为(0.5, 0.5, 0.5, 0.5)是非常常见的。你也可以创建子widget对象,甚至是“*”窗口。在某些情况下,你甚至可以将图形视图用于高级多文档界面的应用程序。

QGraphicsWidget支持部分QWidget属性,包括窗口标志位(window flags)和属性,但是并非全部支持。可以参考QGraphicsWidget文档,以获取完整列表来判断哪些支持以及哪些不支持。例如:你可以在创建QGraphicsWidget时赋予Qt::Window标志,从而得到封装的窗口,但是图形视图目标并不支持 Qt::Sheet和Qt::Drawer标志,这两者在Mac Os X上非常常见。

QGraphicsLayout

QGraphicsLayout是第二代布局框架的内容之一,专门为QGraphicsWidget设计。其API和QLayout非常相似。你可以在QGraphicsLinearLayout或QGraphicsGridLayout中对widget或者子布局进行管理,也可以通过派生QGraphicsLayout实现你自己的布局类,还可以通过派生QGraphicsLayoutItem来实现你自己的 QGraphicsItem对象的适配器,从而将其加入到布局中。

嵌入式Widget支持

图形视图对将任何widget嵌入到场景中提供无缝的支持。你可以嵌入简单的widget,比如QLineEdit或QPushButton,也可以是复杂的widget,比如QTabWidget,甚至是完整的主窗口。要将widget嵌入场景中,只需要简单地调用QGraphicsScene::addWidget()或者创建一个QGraphicsProxyWidget对象并将 widget手工的嵌入其中。

通过QGraphicsProxyWidget图形视图能够完全继承客户端widget对象的功能,包括:其鼠标光标、 tooltip、鼠标事件、平板电脑相关事件、键盘事件、子窗口、动画、弹出(比如:QComboBox或QCompleter),以及widget输入焦点和激活状态。QGraphicsProxyWidget甚至集成了嵌入式widget的 tab切换顺序,这样你就可以通过tab键让焦点进入或者移出嵌入式widget。你甚至可以嵌入一个新的 QGraphicsView到你的场景中,从而提供复杂的嵌套的视图。

当改变一个嵌入式widget,图形视图可以确保widget转换时与分辨率无关,当放大时使字体和样式看起来干净利落。(注意:分辨率无关的效果取决于风格。)

性能

浮点指令

为了精确和快速的将坐标变换和特效应用到items上,图形视图在编译的时候默认用户的硬件能够为浮点指令提供合理的性能。

很多工作站和桌面电脑都配备了适当的硬件来加速这种类型的计算,但是一些嵌入式设备可能仅仅提供了处理数学运算的库,或者需要用软件来模拟浮点指令。

这样,在某些设备上,某些类型的特效可能要比预期的慢。有可能可以在其它方面进行优化来弥补性能上的损失,比如:用OpenGL来绘制场景。不过,如果优化本身是依赖于浮点计算硬件的话,可能都会带来性能上的损失。

更多参考

  • Graphics View Framework - 助手