Qt之图形视图框架

简述

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

框架包括一个事件传播架构,支持场景(Scene)中的图元(Item)进行精确的双精度交互功能。图元能够处理键盘事件、鼠标按下、移动、释放和双击事件,同时也能跟踪鼠标移动。sql

图形视图使用一个BSP(Binary Space Partitioning - 二叉空间分割)树,以提供对图形元素的快速查找,正由于如此,它可使超大的场景实时地可视化,即便包含数百万的图元。编程

图形视图架构

图形视图提供了一个基于图元的方式来实现模型视图(model-view)编程,很像InterView中的便利类:QTableView、QTreeView和QListView。多个视图能够观察一个单独的场景,场景则包含了不一样的几何形状图元 。markdown

场景

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中橡皮筋(rubberband)选中的基础。经过调用QGraphicsScene::selectedItems()能够获取当前选中的图元列表。另一种由QGraphicsScene处理的状态是:一个图元是否有键盘输入焦点。你能够调用QGraphicsScene::setFocusItem()或QGraphicsItem::setFocus()为一个图元设置焦点,或经过QGraphicsScene::focusItem()获取当前的焦点图元。字体

最后,QGraphicsScene容许经过QGraphicsScene::render()将部分场景绘制到绘图设备(paint device - 例如:QImage、QPrinter、QWidget)上。能够在本文关于“打印”部分了解更多细节。

视图

QGraphicsView提供了视图部件,将一个场景中的内容显示出来。你能够附加多个视图到同一个场景,从而针对同一数据集提供几个视口(viewport)。该视图部件是一个滚动区域(scroll area),为大型场景浏览提供滚动条。若是要启用OpenGL支持,可经过调用QGraphicsView::setViewport(),将一个QGLWidget设置为视口。

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同样,它能够经过一个矩阵(matrix):QGraphicsItem::transform()来转换其自身的坐标系,这对于旋转和缩放单个图元很是有用。

这里写图片描述

图形视图框架中的类

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

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

图形视图坐标系

图形视图基于笛卡儿坐标系,场景中图元的位置和几何形状由两组数据来表示:X坐标和Y坐标。当使用一个未转换的视图来观察一个场景,场景中的一个单元将会由场景上的一个像素表示。

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

图形视图中使用了三种有效的坐标系:图元坐标、场景坐标、视图坐标。为了简化实现,图形视图提供了很是方便的函数来进行三个坐标系的映射。

渲染时,图形视图的场景坐标对应于QPainter的逻辑坐标,视图坐标与设备坐标一致。参考:Coordinate System,了解更多关于逻辑坐标和设备坐标关系的内容。

这里写图片描述

图元坐标

图元生活在本身的局部坐标系。它们的坐标一般围绕它们的中心点(0, 0),而且这也是全部转换的中心。图元坐标系下的几何元素一般指点、线或矩形。

建立自定义图元时,只需考虑图元坐标便可。QGraphicsScene和QGraphicsView会为你实现全部相关的转换,这样一来,实现自定义图元就容易多了。例如:当你接收到鼠标按下或拖拽事件时,事件位置将由图元坐标给出。若是某一点(传递一个图元坐标做为参数)在图元中,那么GraphicsItem::contains()虚函数将会返回true;不然,返回false。一样的,项绑定的矩形或形状区域也是项坐标系统的。一样的,图元的矩形边界和形状都是基于图元坐标的。

图元的位置是图元的中心点在其父坐标系下的坐标,有时也被称为父坐标。场景从这个意义上说是全部无父图元的“parent”,顶层图元的位置在场景坐标中。

子坐标是相对于父坐标而言的。若是子坐标没有转换,那么子坐标和父坐标的差别就和图元在父坐标中的距离同样。例如:一个未经转换的子图元正好位于父图元的中心点,那么,这两个图元的坐标系是彻底同样的。若是子图元的位置是(10, 0),那么子图元的(0, 10)点就对应父图元的(10, 10)点位置。

因为图元的位置和转换是相对于父图元来讲的,所以,虽然父图元的转换隐式地转换了子图元,可是子图元的坐标不会因父图元的转换而受到影响。在上述示例中,即便父图元通过了旋转和缩放,子图元的(0, 10)点始终对应父图元的(10, 10)点。不过相对于场景来讲,子图元将随着父图元进行转换和偏移 。若是父图元缩放了(2x, 2x),那么子图元在场景中的坐标是(20, 0),而且其(10, 0)点将会对应于场景中的(40, 0)点。

无论图元或父图元进行了怎样的转换,QGraphicsItem的函数操做都在图元坐标内。例如:一个图元的矩形边界(QGraphicsItem::boundingRect())老是在图元坐标下给出。可是QGraphicsItem::pos()是例外之一,该函数表示其在父图元中的位置 。

场景坐标

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

视图坐标

视图坐标是部件的坐标,视图坐标中的每一个单位对应一个像素。对于该坐标系来讲比较特殊的一点是:它相对于部件或视口,不会受被观察的场景所影响。QGraphicsView的视口左上角老是(0, 0),右下角老是(viewport width, viewport height)。全部的鼠标事件和拖拽事件都以视图坐标接收到的,你须要将这些坐标映射到场景,以便于和图元进行交互。

坐标映射

一般处理场景中的图元时,从场景到图元、从图元到图元、从视图到场景的坐标或任意形状转换将会很是有用。例如:当在QGraphicsView视口中点击鼠标,你能够向场景询问当前鼠标下方的是什么图元(调用 QGraphicsView::mapToScene()转换坐标,而后经过QGraphicsScene::itemAt()查询图元)。若是想知道一个图元处于视口中的位置,能够调用图元的函数QGraphicsItem::mapToScene(),而后再调用视图的函数QGraphicsView::mapFromScene()。最后,若是想查找位于一个椭圆区域内的图元,你能够把一个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); }
    ...
};

槽能够关联到启用了“autoRepeat”属性的QToolButtons。

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

参考:Elastic Nodes Example,了解更多关于缩放的内容。

打印

图形视图经过其渲染函数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,所以QGraphicsView也提供了和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();
}

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

图元经过调用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(),直接为视图设置一个默认的光标。

参考:Drag and Drop Robot example,了解更多关于tooltip和光标形状操做的内容。

动画

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

另一个选择是:建立一个自定义图元,从QObject和QGraphicsItem继承。该图元能够设置本身的定时器,而后在QObject::timerEvent()中控制动画。

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

OpenGL渲染

要启用OpenGL渲染,只要简单地调用QGraphicsView::setViewport()来设置一个新的QGLWidget做为QGraphicsView的视口。若是你但愿OpenGL具备反锯齿,则须要OpenGL支持采样缓冲(参考:QGLFormat::sampleBuffers())。

示例:

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

图元组

经过将一个图元设置为另外一个图元的子图元,就能够获得图元组最重要的功能:图元会一块儿移动,全部转换都会从父图元传播到子图元中。

此外,QGraphicsItemGroup是一个特殊的图元,它提供了对子图元事件的支持,同时还提供了用于添加和删除子图元的接口。将一个图元添加到QGraphicsItemGroup将保持图元原始的位置和坐标转换,不太重新设置图元的父图元则会致使图元从新定位到相对于父图元的位置。为了方便起见,你能够调用QGraphicsScene::createItemGroup()来建立QGraphicsItemGroup图元。

部件和布局

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

QGraphicsWidget

QGraphicsWidget创建在QGraphicsItem之上,具备QGraphicsItem的全部功能,保持了较小的资源占用,同时提供了二者的优点:来自QWidget的额外的功能,例如:样式、字体、调色板、布局、几何形状,来自QGraphicsItem的分辨率独立性和坐标转换的支持。因为图形视图使用真实的坐标而不是整数,所以 QGraphicsWidget的几何形状函数能够同时操做QRectF和QPointF。同时也能应用到边框的大小、边距和间距上,例如:对于QGraphicsWidget,规定内容边距为(0.5, 0.5, 0.5, 0.5)是很是常见的。例如:你能够建立子部件,甚至是“顶级”窗口。在某些状况下,你甚至能够将图形视图用于高级多文档界面的应用程序。

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

QGraphicsLayout

QGraphicsLayout是第二代布局框架的内容之一,专门为QGraphicsWidget设计。其API和QLayout很是类似。你能够在QGraphicsLinearLayout或QGraphicsGridLayout中对部件或者子布局进行管理,也能够经过派生QGraphicsLayout实现你本身的布局类,还能够经过派生QGraphicsLayoutItem来实现你本身的适配器,从而将QGraphicsItem图元加入到布局中。

嵌入式部件支持

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

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

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

性能

浮点指令

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

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

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

更多参考

  • Graphics View Framework - 助手
相关文章
相关标签/搜索