这个框架是一个相对成熟的渲染引擎的上层框架,通常也可以会叫做 Scene - View。
在QT中,GraphicsView是一个与QWdiget系列一点点不一样的系统。这个系统主要由下面几个框架类构成:QGraphicsView, QGraphicsScene, QGraphicsItem
QGrahpicsView是从原生的QWidget继承过来,QGraphics-View系统中,他承担的也是视口的指责,Viewport,Viewport相当于显示设备的一个矩形区域。
QGraphicsScene 是一个管理器,用来管理所有的QGraphicsItem,包括根据坐标查询Item,排序Item,绘制Item等。
QGraphicsItem 是所有可见的元件。一个完整的UI界面,由各种QGraphicsItem组合起来。这些QGraphicsItem之间由一棵多叉树组织。
要架构一个基于GraphicsView的UI库,需要做如下的三件事情:
· 我们需要先由一个 QGraphicsView, 这个是UI显示的地方,也就是装满可见原色的Scene,
· 然后需要一个QGraphicsScene 用来管理所有可见的界面元素,
· 要实现UI功能,我们需要用各种从QGraphicsItem拼装成UI控件,并赋予他控件的逻辑。
对应到Duifw,我们的DuiFrameWindow 就是从 QGraphicsView 继承过来的,也就是说它是一个独立的视口,相当于操作系统的一个窗口.
在DuiFrameWindow 中 void DuiFrameWindow::_initScene(), 函数中,我们为每一个 DuiFrameWindow 创建了一个标准的 QGraphicsScene, 并且在 scene 上我们 add 了一个 DuiRootItem,然后我们就在 rootitem上构建我们的所有UI控件, 从 DuiRootItem: m_rootItem 上 我们add 三个子Item ,分别是 DuiBackgroundItem:m_backgroundItem, DuiTitlebar:m_titlebar, DuiSpacerItem:m_contentItem, 然后我们定义了一个rootWidget() 函数 用来返回 DuiSpacerItem:m_contentItem, 后面通过xml文件配置的一个窗口,就通过访问rootWidget(), 把所有DuiFrameWindow的孩子节点都以 这个rootWidget为 父节点,如下代码:
createChild(child->child(index), frame->rootWidget());
这样就构建了一个QGraphicsItem的多叉树。前面有一个细节,没有列出,就是我们的DuiWidget, 是一个什么, 我们的DuiWidget是从QGraphicsWidget,继承而来的,也就是说它本身是一个符合 QGraphicsScene - QGraphicsItem体系的可视元件。
了解了的GraphicsView的构成框架后,对于QGraphicsItem的消息来源,以及这个框架中的消息走向能够大概有了猜测了。
QGraphicsView 会把 原来的QWidget中的各种QHoverEvent, QInputEvent,......等等一些列命令,转换成各种 QGraphicsSceneEvent ,QGraphicsSceneMouseEvent,QGraphicsSceneWheelEvent......,,,然后传递给当前View绑定的QGraphicsScene。然后通过Scene传递给 QGraphicsitem,QGraphicsScene 有如下的一堆消息处理接口:
通过上述的分发,最后消息会通过下面的接口传递到具体的item,所有的scene消息都是走接口:
这样就完成了从windows消息到 QGraphicsItem 的消息处理的流程。
这几天做迷彩设计相关编程用了很多QPainter相关的操作,写了不少Qt代码让我对Qt又有了进一步的认识。最近翻看Qt 的 Demos and Examples 发现在Graphics view里面有一个elastic node的示例,程序仅仅定义了背景和小球绘制操作便完成了复杂的鼠标,键盘等交互。在让我大为惊叹Qt 强大的同时,也决定学一学。查找了Qt 帮助文档,这一系列操作都用到了Qt Graphics View Framework。
Graphics View 框架提供了一个界面,用于管理和交互大量的用户自定义的2D图形物件,并且有一个view widget(QGraphicsView)用户可视化这些物件,支持zoom和rotate。这个框架包含了一个事件传播构架,允许了double精度的场景中物体交互能力。物件的键盘,鼠标的按下,移动,松开,双击事件都已经定义好了,并且可以跟踪鼠标移动。
Graphics view 构架
所有的item都被绘制到了一个场景中用于显示,这个场景就是QGraphicsScene,这个场景有如下功能:
1、 为管理大量item提供了一个快速交互界面。
2、 能够将键盘,鼠标等事件传递到每个item。
3、 可以管理item的状态,例如选择,焦点等。
4、 提供了不变形的绘制,主要用于打印绘画结果。
场景就像一个容易囊括了所有item,可以通过addItem()添加物体,可以通过item()查找物体,itemAt()返回最上面的item,所有item按照降序堆栈排列。第一个加入的在栈顶,最后一个加入的在栈底。
QGraphicsScene的事件传递架构非常给力,能够将场景获得的事件精确传递到相应item,例如鼠标在某点点击了一下,Scene能够将这个事件传递给在这个点上的item(最上面的那个item),于是这个item被选中或者执行别的动作,具体取决于item的mousePressEvent函数的处理。
QGraphicsScene中,你可以用setSelectionArea()选择区域中的许多个item,仅仅需要调用这个函数你就能获得选中item的结果,真是太强大了,如果要自己写函数鼠标选择的区域选了哪些item,你需要遍历每个item,对于每个item又要判断是否全部在区域内,于是item要包围核,要范围面积等等变量来表示。想想就觉得真心麻烦。这个函数确实为开发者节约了不少时间。
View
QGraphicsView提供了用于显示的widget,用于显示scene,你可以将多个view联系到同一个scene,给相同的数据提供多个视口,视口支持openGL,甚至可以将QGLWidget作为视口,只要调用一下QGraphicsView::setViewport()。View接收交互事件,进行坐标转换后变成scene event交给场景。QGraphicsView::mapToScene(), QGraphicsView::mapFromScene()等多个函数可以在view和scene之间转换坐标系。
[cpp] view plaincopyprint?
1. QGraphicsScene scene;
2. myPopulateScene(&scene);
3. QGraphicsView view(&scene);
4. view.show();
Item
QGraphicsItem是场景中所有物件的基类,GraphicsView也提供了一些标准item,例如矩形QGraphicsRectItem,椭圆QGraphicsEllipseItem,还有文本,QGraphicsTextItem,最给力的当然是你自定义的item,item支持以下操作:
1、 鼠标点击,移动,松开,双击,飞越,滚轮,菜单事件。
2、 键盘输入。
3、 拖拽
4、 Group,可以通过父子关系或者QGraphicsItemGroup
5、 碰撞检测
每个Item都有一个局部坐标系,可以通过这个坐标系进行选择,移动等操作,transform()函数通过控制矩阵可以轻松实现这些操作。
有一个很NB的功能就是item支持碰撞检测,怎么检测呢,首先需要为每一类item定义好它的bounding box,同过boundingRect()函数可以设置这个item外围矩形包围盒,而更精确的是用shape()函数定义item外边框路径,这个路径是QPainterPath,这个路径可以是直接,贝塞尔曲线等任何线条,collidesWith()是一个虚函数,定义完成后便能完成碰撞检测功能了。
其他的Graphics类
QGraphicsAnchorLayout是用于将几个widget以锚点的形式随意排列的layout。
QGraphicsEffect提供了一些图形特效。
主要有以下4个标准特效:
Qt provides the following standardeffects:
· QGraphicsBlurEffect - blurs the item by a given radius
· QGraphicsDropShadowEffect - renders a dropshadow behind the item
· QGraphicsColorizeEffect - renders the item in shades of any given color
· QGraphicsOpacityEffect - renders the item with an opacity
Graphics view 坐标系
坐标系由(x,y)表示,一个单位就是屏幕上一个像素。在这个框架内一共有3个坐标系,item局部坐标系,场景坐标系,view坐标系,有函数用于在这些坐标系之间进行映射。
Item coordinates
Item的坐标建立在它的中心点(0,0)附近,这也是所有变形的中心位置,几何图元常常由局部坐标系中的点,线,矩形来构建。
构建自定义的item时,仅仅需要考虑局部坐标就行了,QGraphicsView和QGraphicsScene会进行其他所有的变形。鼠标事件用于item时,会自动将点转换到item坐标系中。Item的bounding rect 和 shape都在局部坐标系中定义。
子item的中心位置将会保存到父item的坐标系中用于自动查找判断,最上层item的中心坐标将会保存到scene坐标系中。
父item的旋转缩放操作将会影响子item跟着一起变,但不会影响子item与父Item的坐标系,例如,有父子2个item,在父坐标系中,子的位置在(10,0)。于是,子坐标系中位置为(0,10)的点在父坐标系下的坐标是(10,10)。然后,我对父item进行了旋转和缩放,子坐标系下(0,10)的点在父坐标系下仍然是(10,10),但是在scene坐标系下就会产生变化了,子item会随着父产生形变,如果父进行了缩放为(2x,2x),在scene坐标系下,子的位置为(20,0),子坐标系中(10,0)的点在scene下的坐标为(40,0)。QGraphicsItem::pos()是少数几个例外的,它返回item在父坐标系下的坐标.
旋转和缩放操作相当简单,在QGraphicsView中调用ratate()和scale()函数。
————————————————————————————————————————————————————————
光说不练嘴把式,必须随便搞个test project。
GraphicWidget继承自QGraphicsView,重载了一个函数,drawBackground().
EllipseItem继承自QGraphicsItem,重载了若干函数:
[cpp] view plaincopyprint?
1. QRectF boundingRect() const;
2. void paint(QPainter *painter, const QStyleOptionGraphicsItem*option, QWidget *widget);
3. protected:
4. QVariant itemChange(GraphicsItemChange change, const QVariant &value);
5. void mousePressEvent(QGraphicsSceneMouseEvent *event);
6. void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
boundingRect必须得重载,不然编译器说这个是抽象类,不能够实例化。
在mousePressEvent()中调用QGraphicsItem::mousePressEvent(event);便万事大吉,方便shi了。
其他雷同。
在EllipseItem中保存了父节点指针。
在graphicWidget构造函数中new一个scene,设置一下,然后new一个EllipseItem出来,放入scene,指定位置,就可以了。
出来的结果是,你可以任意移动出现的小圆球。
待续...
图形视图框架
The Graphics View Framework
关键词翻译对照表:
Graphics View:图形视图。
Scene:场景/场景管理器(Scene同时担负着管理场景中的对象,建立索引等工作)。
Item:这里翻译为对象,Graphics View Framework下的GraphicsItem是场景中可以被显示的元素。这里翻译成对象便于理解。
Graphics Item:图形对象。
Event:事件,等同于Windows下的消息。
正文:
图形视图(Graphics View)提供了支持大量自定义的二维图形对象(Item,这里译为“对象”,方便大家理解)交互(Interaction)的管理器,以及一个支持缩放和旋转操作的视图widget用于显示这些元素。
该框架包含了事件(Event,在Windows下可以理解为“消息”)传播的框架,支持场景管理器中精确的交互能力,以双精度浮点数表示对象位置、大小等属性的变化。图形元素还能处理键盘事件、鼠标按下/移动/释放和双击的时间,同时也能跟踪鼠标移动。
图形视图使用BSP树(Binary Space Partitioning,二叉空间分割)提供对图形对象的快速查找,可以想像,即使是包含数以百万计对象的超大场景,也能够进行实时显示。
图形查看Qt中引入4.2,取代其前身QCanvas。如果您要从QCanvas中移植过来,见移植到图形视图。
主题:
图形视图架构
场景
视图
对象
图形视图框架中的类
图形视图坐标系
对象坐标
场景坐标
视图坐标
坐标映射
主要特点
缩放和旋转
打印
拖放
鼠标指针和tooltip
动画
OpenGL渲染
元素组
widgets和布局
QGraphicsWidget
QGraphicsLayout
嵌入式widget支持
性能
浮点运算指令
图形视图架构
图形视图提供基于图像对象的方式来实现model-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 中拉框选择(rubber band)的基础。通过调用QGraphicsScene::selectedItems()可以获取当前选择集中的所有对象。另外一种通过 QGraphicsScene 来管理的状态是:一个图形对象是否能够相应键盘的焦点切换.你可以调用QGraphicsScene::setFocusItem()或者 QGraphicsItem::setFocus()将键盘焦点切换到对于的图形对象上,或者通过QGraphicsScene::focusItem()获取当前的焦点对象。
最后,QGraphicsScene允许你通过QGraphicsScene::render()将部分场景绘制到paint device上。你可以在本文档中关于“打印”的章节了解更多关于这一点的更多细节。
视图
QGraphicsView提供了视图widget,将场景中的内容显示出来。你可以用几个不同的视图来观察同一个场景,从而实现对于同一数据集的不同viewport。该Widget同时也是scroll area,为大场景提供滚动条。如果要启用OpenGL支持,可调用QGraphicsView::setViewport将QGLWidget设置为其viewport。
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::collidesWith()仍然会在局部坐标系下进行计算。
QGraphicsItem通过QGraphicsItem::shape和QGraphicsItem::collidesWith来实现碰撞检测,这两个函数都是虚函数。QGraphicsItem通过QGraphicsItem::shape获取局部坐标系下的QPainterPath对象,来完成碰撞检测。不过如果你想提供你自己的碰撞检测机制,你可以通过自定义QGraphicsItem::collidesWith函数来实现。
图形视图架构中的类
下面的类提供了创建交互式应用程序的框架:
QAbstractGraphicsShapeItem
所有路径对象的共同基类
QGraphicsAnchor
代表了QGraphicsAnchorLayout中两个项目之间的anchor
QGraphicsAnchorLayout
如何将widgets anchor到图形视图中
QGraphicsEffect
所有图形特效的基类
QGraphicsEllipseItem
椭圆对象,可以直接添加到QGraphicsScene
QGraphicsGridLayout
管理widgets在图形视图中的布局
QGraphicsItem
在QGraphicsScene中所有图形对象的基类
QGraphicsItemAnimation
为QGraphicsItem提供简单的动画支持
QGraphicsItemGroup
将多个图形对象组合成一个对象
QGraphicsLayout
图形视图中所有布局类的基类
QGraphicsLayoutItem
允许布局类管理的自定义对象
QGraphicsLineItem
直线对象,可以直接添加到QGraphicsScene
QGraphicsLinearLayout
管理widgets在图形视图中的的水平或者垂直方向上的布局
QGraphicsObject
所有需要处理信号/槽/属性的图形对象。
QGraphicsPathItem
路径对象,可以直接添加到QGraphicsScene
QGraphicsPixmapItem
位图对象,可以直接添加到QGraphicsScene
QGraphicsPolygonItem
多边形对象,可以直接添加到QGraphicsScene
QGraphicsProxyWidget
widget代理,用于将一个QWidget对象嵌入一个QGraphicsScene中
QGraphicsRectItem
矩形对象,可以直接添加到QGraphicsScene
QGraphicsScene
管理大量二维图形对象的管理器
QGraphicsSceneContextMenuEvent
在图形视图框架中的上下文菜单事件
QGraphicsSceneDragDropEvent
图形视图框架中的拖放拖放事件
QGraphicsSceneEvent
图形视图框架中所有事件的基类
QGraphicsSceneHelpEvent
Tooltip显示时发出的事件
QGraphicsSceneHoverEvent
图形视图框架中的悬停事件
QGraphicsSceneMouseEvent
图形视图框架中的鼠标事件
QGraphicsSceneMoveEvent
图形视图框架中的widget移动事件
QGraphicsSceneResizeEvent
图形视图框架中的widget大小改变的事件视
QGraphicsSceneWheelEvent
图形视图框架中的鼠标滚轮时间
QGraphicsSimpleTextItem
简单的文本对象,可以直接添加到QGraphicsScene中
QGraphicsSvgItem
可以用来呈现的SVG文件内容的QGraphicsItem对象
QGraphicsTextItem
文本对象,可以直接添加到QGraphicsScene,用于显示带格式的文本
QGraphicsTransform
创建QGraphicsItems高级矩阵变换的抽象类
QGraphicsView
显示QGraphicsScene内容的widget
QGraphicsWidget
QGraphicsScene中所有widget的基类
QStyleOptionGraphicsItem
用于描述绘制QGraphicsItem所需的参数
图形视图坐标系
图形视图建立在直角坐标系基础上,图形对象的位置和几何形状由两组数据来表示:x坐标和y坐标。如果使用未变换的视图来观察场景,场景中的一个单元将会表现为屏幕上的一个像素。
注意:图形视图使用了Qt的坐标系,不支持反转的y轴坐标系统(即y向上为正方向)。
图形视图使用了三种有效的坐标系:对象坐标,场景坐标和视图坐标。为了简化你的实现工作,图形视图提供了一系列非常方便的函数来进行三个坐标系下的坐标变换。
绘制的时候,图形视图的场景坐标对应于QPainter的逻辑坐标,视图坐标与设备坐标一直。在The Coordinate System一文中你可以阅读更多关于逻辑坐标和设备坐标关系的内容。
对象坐标系
图形对象建立其自身的局部坐标系下。该坐标系通常以中心点(0,0)为中心,同时该中心点也是各种矩阵你变换的中心。对象坐标系下的几何元素通常用点、线或者矩形来表示。
创建自定义图形对象的时候,你只需关注对象坐标系即可。QGraphicsScene和QGraphicsView会执行所有相关的变换,这样一来我们实现自定义对象就容易多了。比如说,当你接受鼠标按下或者拖拽事件的时候,事件位置已经被转换到了对象坐标系下。类似的,对象的包围盒和形状都是基于对象坐标系的。
对象的位置是对象坐标系下的中心点在其父对象坐标系下的位置。对于所有没有父对象的对象来说,场景就是其父对象。因此最顶层对象的位置就是其在场景中的位置。
子对象坐标系是相对于父对象坐标系来说的一个概念。如果子节点没有进行矩阵变换,那么在子对象坐标系和父对象坐标系的差异就和这些对象在父对象中的偏移。比如说,如果一个未经变换的子对象精确的位于父对象的中心点,那么这两个对象的坐标系就是完全一致的。如果子对象的位置是(10,0),那么子对象的(0,10)点就位于父对象的(10,10)点的位置。
由于对象的位置和变换是相对于父对象来说的,因此虽然父对象的变换隐式地变换了子对象,子对象的坐标系不会因父对象坐标系改变而改变。在上面的例子中,即使父对象经过了旋转和缩放,子对象的(0,10)点依然相对于父对象是(10,10)点。不过相对于场景来说,子对象将随着父对象进行变换和便宜。如果父对象缩放了(2x,2x),那么子对象在场景坐标系下将会位于(20,0)的位置,同时其(10,0)点将会对应于场景中的(40,0)点。
这里有一些例外,比如不管对象自身或者父对象进行了什么样的变化,QGraphicsItem::pos()总是表示在对象坐标系下的位置。比如,一个对象的包围盒(QGraphicsItem::boundingRect())总是在对象坐标系下给出的。
场景坐标系
场景表示了其中所有对象的基础坐标系。场景坐标系描述了每一个顶层对象的位置,同时也是所有从视图传递到场景的事件的变化基础。场景中的每一个对象都有其在场景中的位置和包围盒(QGraphicsItem::scenePos(), QGraphicsItem::sceneBoundingRect()),另外,也有其自身的位置和包围盒。场景位置描述了对象在场景坐标系下的位置,场景包围盒则提供给QGraphicsScene来决定场景中的哪一块区域已经被改变了。场景中的变化通过QGraphicsScene::changed()信号发出,参数是场景坐标系下的矩形列表。
视图坐标系
视图坐标系是widget的坐标系。视图坐标系下的每个单位对应于一个像素。对于该坐标系来说比较特殊的一点是,它是相对于widget或者viewport的坐标系,不会受到被显示的场景的影响。QGraphicsView的viewport左上角坐标总是(0,0),右下角坐标总是(viewport宽度,viewport高度)。所有的鼠标事件和拖拽事件都在视图坐标系下接收,你需要将这些坐标映射到场景中后才能与场景中的图形对象进行交互。
坐标映射
通常当我们处理场景中的对象的时候,坐标变换将会非常有用,我们可以把坐标或者任意形状从场景变换到对象坐标系下,从一个对象坐标系变换到另一个对象坐标系,或者从视图变换到场景。例如当你用鼠标点击了QGraphicsView的viewport,你可以向场景管理器查询当前鼠标下方的对象(调用QGraphicsView::mapToScene()变换坐标,然后通过 QGraphicsScene::itemAt()查询对象)。如果你想知道一个对象处于viewport中的位置,你可以调用对象的函数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); }
...
};
槽(Slots)可以关联到启用了“autoRepeat”属性的QToolButtons。在变换视图的过程中,QgraphicsView始终保持与视图中心对齐。参阅例程Elastic Nodes了解如何实现这种基本的缩放操作。
打印
图形视图通过其绘制函数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);
}
场景和视图绘制函数的区别在于,前者操作的是场景坐标,后者操作的则是视图坐标。QGraphicsScene::render多用于打印未经变换的整个场景,比如将几何数据图表打印出来,或者打印文本文档。QGraphicsView::render则比较适合用于抓取屏幕截图,其缺省行为是将viewport中确切的内容绘制到所提供的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一样的拖放功能。另外,为了方便起见,图形视图框架为场景中的每个对象提供了拖放支持。当view对象接收到一个拖拽消息的时候,它将其转换为拖放事件QGraphicsSceneDragDropEvent,然后将其转发给场景管理器。场景管理器则会接管该事件,并将其发送给鼠标下面第一个接受拖拽消息的对象。
要拖拽一个对象,只需要创建一个QDrag对象,将指针传给开始拖拽的widget。对象可以同时被多个视图观察,但是只有一个视图可以进行拖拽。在大多数情况下,拖拽都从鼠标按下或者移动开始,因此在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();
}
如果你想拦截发送给场景的拖放消息,只需要实现QGraphicsScene::dragEnterEvent()事件,选择你需要处理的事件,然后在QGraphicsItem::dragEnterEvent中进行相应处理即可。你可以到QGraphicsScene的文章中查看更多关于拖放的内容。对象可以通过调用QGraphicsItem::setAcceptDrops来允许或者禁止对拖放的支持。可以通过实现QGraphicsItem::dragEnterEvent(), QGraphicsItem::dragMoveEvent(), QGraphicsItem::dragLeaveEvent(), 和 QGraphicsItem::dropEvent()这几个事件来处理拖动。
鼠标指针和tooltip
和QWidget一样,QGraphicsItem也支持设置鼠标和tooltip。当鼠标进入对象区域(由QGraphicsItem::contains检测)时,通过QGraphicsView来设置鼠标和tooltip。
你也调用QGraphicsView::setCursor设置视图的确实鼠标指针。在Drag and Drop Robot这里例子中有实现tooltip和鼠标指针的代码。
动画
图形视图在几个层面上提供了对动画的支持。你可以用Animation Framework轻松地设置动画:只需要让你的对象从QGraphicsObject继承,然后将QPropertyAnimation绑定到上面。QPropertyAnimation允许你通过对对象的属性进行操作来实现动画效果。
另外一个选择是创建一个从QObject和QGraphicsItem继承的对象。该对象设置自己的时钟,然后在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)));
对象编组
通过将一个对象设置为另一个对象的子对象,你就可以得到对象编组最重要的功能:对象会一起移动,所有变换都会从父对象传播到子对象中。
另外,QGraphicsItemGroup是一个特殊的对象,它提供了对子对象事件的支持,同时还提供了用于添加和删除子对象的接口。将一个对象添加到QGraphicsItemGroup将保持对象原始的位置和坐标变换,不过重新设置对象的父对象则会导致对象重新定位到相对于父对象的位置。为了方便起见,你可以调用QGraphicsScene::createItemGroup()来创建QGraphicsItemGroup对象。
Widgets和布局
Qt4.4引入了对几何体和对布局敏感的对象的支持,具体实现在QGraphicsWidget中。这一特殊的基类对象和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属性,包括窗口标志位和属性,但是并非全部支持。你需要通过QGraphicsWidget文档获取哪些支持以及哪些不支持的完整列表。例如,你可以在创建QGraphicsWidget时赋予Qt::Window标志,从而得到封装的窗口,但是图形视图目标并不支持Qt::Sheet和Qt::Drawer标志,这两者在Mac Os X上非常常见。
QGraphicsWidget的功能将会逐步丰富起来,这一点由社区的反馈决定。
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到你的场景中,从而提供复杂的嵌套的视图。
性能
精度浮点数指令
为了精确和快速的将坐标变换和特效应用到图形对象上,图形视图在编译的时候默认用户的硬件能够为浮点指令提供合理的性能。
很多工作站和桌面电脑都配备了适当的硬件来加速这种类型的计算,但是一些嵌入式设备可能仅仅提供了处理数学运算的库,或者需要用软件来模拟浮点指令。
这样,在某些设备上,某些类型的特效可能要比预期的慢。有可能可以在其他方面进行优化来弥补性能上的损失,比如说用OpenGL来绘制场景。不过,如果优化本身是依赖于浮点计算硬件的话,可能都会带来性能上的损失。