一、GraphicsView框架简介
QT4.2开始引入了Graphics View框架用来取代QT3中的Canvas模块,并作出了改进,Graphics View框架实现了模型-视图结构的图形管理,能对大量图元进行管理,支持碰撞检测,坐标变换和图元组等多种方便的功能。
二、QGraphicsScene场景
QGraphicsScene场景是QGraphicsItem对象的容器,主要功能如下:
三、QGraphicsView视图
QGraphicsView是视图窗口部件,使场景内容可视化,可以连接多个视图到一个场景,也可以为相同数据源的数据集提供不同的视图。QGraphicsView是可滚动的窗口部件,可以提供滚动条来浏览大的场景。如果需要使用OpenGL,可以使用QGraphicsView::setViewport()将视图设置为QGLWidget组件。
四、QGraphicsItem图元
QGraphicsItem是图元的基类。QGraphics View框架提供了多种标准的图元:
QGraphicsLineItem 直线图元
Contains()函数可以调用,用来决定一个图元是否包含一个点。Contains函数可以重写,contains()函数默认的方法是通过调用shape()来完成的。
图元中也可以包含其他的图元,也可以被别的图元包含,所有的图元可以有一个父类图元和多个子类图元,除非一个图元没有父类,否则图元的位置是在父类坐标中,子类图元将会继承父类图元的位置和转换。
五、GraphicsView坐标系统
Graphics View坐标系基于笛卡尔坐标系,图元的场景中的位置和几何形状通过x坐标和y坐标表示。当使用没有变换的视图观察场景时,场景中的一个单位对应屏幕上的一个像素。
1、图元坐标
图元存在于自己的本地坐标上,图元的坐标系统通常以图元中心为原点,图元中心也是所有坐标变换的原点,图元坐标方向是x轴正方向向右,y轴正方向向下。创建自定义图元时,只需要注意图元的坐标,QGraphicsScene和QGraphicsView会完成所有的变换。 例如,如果接受到一个鼠标按下或拖入事件,所给的事件位置是基于图元坐标系的。如果某个点位于图元内部,使用图元上的点作为QGraphicsItem::contains()虚函数的参数,函数会返回true。类似,图元的边界矩形和形状也是基于图元坐标系。
子图元的坐标与父图元的坐标相关。如果子图元无变换,子图元坐标和父图元坐标之间的区别与他们的父图元的坐标相同。例如,如果一个无变换的子图元精确的位于父图元的中心点,父子图元的坐标系统是相同的。如果子图元的位置是(10,0),子图元上的点(0,10)就是父图元上的点(10,10)。
由于图元的位置和变换与父图元相关,但子图元的坐标并不会被父图元的变换影响,虽然父图元的变换会隐式地变换子图元。在上例中,即使父图元被翻转和缩放,子图元上的点(0,10)仍旧是父图元上的点(10,10)。
2、场景坐标
场景坐标是所有图元的基础坐标系统。场景坐标系统描述了顶层图元的位置,并且构成从视图传播到场景的所有场景事件的基础。每个图元在场景上都有场景坐标和边界矩形。场景坐标的原点在场景中心,坐标原点是X轴正方向向右,Y轴正方向向下。
3、视图坐标
视图坐标是窗口部件的坐标,视图坐标的单位是像素,QGraphicsView的左上角是(0,0)。所有鼠标事件、拖拽事件最开始都使用视图坐标,为了和图元交互,需要转换坐标为场景坐标。
4、坐标变换
在Graphics View框架中,经常需要将多种坐标变换,从场景到图元,从图元到图元,从视图到场景 。QGraphics View框架坐标变换函数如下:
QGraphicsView::mapToScene()视图到场景
QGraphicsView::mapFromScene() 场景到视图
QGraphicsItem::mapFromScene() 场景到图元
QGraphicsItem::mapToScene() 图元到场景
QGraphicsItem::mapToParent() 子图元到父图元
QGraphicsItem::mapFromParent() 父图元到子图元
QGraphicsItem::mapToItem()本图元到其他图元
QGraphicsItem::mapFromItem()其他图元到本图元
六、GraphicsView框架特性
1、缩放与旋转
QGraphicsView通过QGraphicsView::setMatrix()支持同QPainter一样的坐标变换,通过对一个视图应用变换,可以很容易地支持普通的导航特性如缩放与旋转。
2、打印
图形视图架构通过渲染函数QGraphicsScene::render()和QGraphicsView::render()支持单行打印
场景和视图的渲染函数的不同在于QGraphicsScene::render()使用场景坐标,QGraphicsView::render()使用视图坐标。QGraphicsScene::render()经常用于打印未变换场景中的整块,例如一块图形数据或是打印一个文本文档。 QGraphicsView::render()适合用于截屏,默认会使用绘图设备精确渲染视口的内容。
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”);
3、拖拽
由于QGraphicsView继承自QWidget,GraphicsView同样提供了拖拽功能。此外,为了方便,GraphicsView框架也为场景、图元提供拖拽支持。当视图接收到拖拽事件,GraphicsView框架会将拖拽事件翻译为QGraphicsSceneDragDropEvent事件,再发送到场景,场景接管事件,再把事件发送到光标下接受拖拽的第一个图元。
为了开启图元拖拽,创建一个QDrag对象,传递启动拖拽的QWidget的指针。图元可以同时被多个视图观察,但只有一个视图可以拖拽图元。通常,拖拽是从按下鼠标或是移动鼠标开始的,在mousePressEvent()或mouseMoveEvent()中,可以从事件中得到原始的QWidget指针。
void CustomItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
}
4、光标与工具提示
QGraphicsItem支持光标(QgraphicsItem::setCursor)与工具提示(QGraphicsItem::setToolTip())。当光标进入到图元的区域,光标与工具提示被QGraphicsView激活(通过调用QGraphicsItem::contains()检测),也可以直接在视图上设置一个缺省光标(QGraphicsView::setCursor)。
5、动画
GraphicsView框架支持多种层次的动画。使用动画框架可以很容易制作出动画。
GraphicsView框架支持的动画实现种类如下:
6、OpenGL渲染
为了使用OpenGL渲染,需要设置一个新的QGLWidget作为QGraphicsView的视口:QGraphicsView::setViewPort()。如果需要OpenGL提供反锯齿功能,则需要OpenGL采样缓冲支持。
QGraphicsView view(&scene);
view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
7、图元组
通过把一个图元做为另一个图元的孩子,可以得到图元组的大多数本质特性:所有图元会一起移动,所有变换会从父到子传递。
8、图形组件和布局
QT4.4通过QGraphicsWidget支持图形和图元布局。QGraphicsWidget类似于QWidget,但QGraphicsWidget并不从QPaintDevice继承,而是继承自QGraphicsItem。QGraphicsWidget支持事件、信号与槽、大小和策略。通过QGraphicsLinearLayout、QGraphicsGridLayout可以对图形组件进行布局管理。
9、嵌入组件
图形视图框架为嵌入任何组件到场景提供了无缝支持。可以嵌入简单的组件,如QLineEdit、QPushButton,或是复杂的组件如QTableWidget,甚至是主窗口。
要嵌入组件到场景,只需要调用QGraphicsScene::addWidget(),或是创建一个QGraphicsProxyWidget实例,手动嵌入组件。
一、QGraphicsScene
1、QGraphicsScene
QGraphicsScene继承自QObject,是一个管理图元的容器,与QGraphicsView合用可以在2D屏幕上显示如线、三角形、文本、自定义图元等图元。
QGraphicsScene是不可见的,只用于管理图元。为了查看场景,需要创建一个视图组件。
2、事件处理与传播
QGraphicsScene的责任之一是传播来自视图的事件。要发送一个事件到场景,需要构造一个继承自QEvent的事件,使用QApplication::sendEvent()函数发送事件。event()函数负责派发事件到各个图元。常用的事件会被便利事件处理函数处理,如鼠标按下事件会被mousePressEvent()函数处理。
场景可以传递来自视图的事件,将事件传递给该点最顶层的图元。如果一个图元要接收键盘事件,那么它必须获得焦点。而且,如果在场景中重写了事件处理函数,那么在该函数的最后必须调用场景默认的事件处理函数,只有这样,图元才能接收到该事件。
A、拖拽事件
[virtual protected] void dragEnterEvent(QGraphicsSceneDragDropEvent *event)
拖入事件处理函数
[virtual protected] void dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
拖离事件梳理函数
[virtual protected] void dragMoveEvent(QGraphicsSceneDragDropEvent *event)
拖动事件处理函数
[virtual protected] void dropEvent(QGraphicsSceneDragDropEvent *event)
Drop事件处理函数
在以上拖拽事件处理函数中的末尾需要调用QGraphicsScene类相应的事件处理函数。
QGraphicsScene::dragEnterEvent(event);
QGraphicsScene::dragLeaveEvent(event);
QGraphicsScene::dragMoveEvent(event);
QGraphicsScene::dropEvent(event);
B、鼠标事件
[virtual protected] void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
鼠标移动处理函数
[virtual protected] void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
鼠标按下处理函数
[virtual protected] void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent)
鼠标释放处理函数
在以上鼠标事件处理函数中的末尾需要调用QGraphicsScene类相应的事件处理函数。
QGraphicsScene::mouseMoveEvent(event);
QGraphicsScene::mousePressEvent(event);
QGraphicsScene::mouseReleaseEvent(event);
3、索引算法
索引算法,是指在场景中进行图元查找的算法。QGraphicsScene中提供了两种选择,在一个枚举变量QGraphicsScene::ItemIndexMethod中定义,分别是:
4、边界矩形
图元可以放到场景的任何位置,场景的大小默认是没有限制的。而场景的边界矩形仅用于场景内部进行索引的维护。因为如果没有边界矩形,场景就要搜索所有的图元,然后确定出其边界,这是十分费时的。所以如果要操作一个较大的场景,应该给出它的边界矩形。
5、图元的查找
场景最大的优势之一就是可以快速的锁定图元的位置,即使有上百万个图元,items()函数也能在数毫秒的时间内锁定一个图元的位置。items()函数有几个重载函数来方便的进行图元的查找。如果在场景的一个点可能重叠着几个图元,可以使用itemAt()函数返回最上面的一个图元。
二、QGraphicsItem
1、自定义QGraphicsItem
QGraphicsItem是图元的基类。
2、绘制
paint()函数被QgrapicsView类调用来绘制图元的内容,图元默认是没有背景或者填充颜色的。在函数中没有被绘制的所有区域都将会发亮,可以调用update()来重绘图元,可以选择传递需要重绘的矩形区域(不是必须的)。取决于图元在视图中是否可见,图元可能会也可能不会重绘,QgraphicsItem里面没有和 Qwidget::repaint()函数等价的图元通过视图来绘制,从父类图元开始,然后是图元自身,以上升的栈的顺序,可以通过调用setZValue()设置图元的栈顺序,通过zValue()来测试,具有低z-values的图元比具有高z-value 的图元先绘制,栈顺序应用于兄弟图元,父类图元总是比子类图元更早绘制。
3、排序
所有的图元都按照一个已经声明的稳定的顺序来绘制,声明的顺序决定了当在场景中点击鼠标时候,哪个图元最先接受鼠标的输入。通常情况下,不需要担心图元排序的问题,因为所有的图元都按照一个在场景中声明的自然的顺序。
在一个栈中,子类图元在父类图元的上面,兄弟图元按照插入场景的顺序来入栈,如果你先添加图元A ,然后是图元B,然后是图元C ,栈中的顺序从下往上就是A、B、C。可以调用setZvalue()来设置一个图元的相对于另一个图元向上、向下或者兄弟栈顺序。默认的Z值是0,具有同样的Z值的图元会按照插入的顺序来入栈。可以调用stackBefore()来备份子类图元的列表,直接更正图元的顺序。
如果想让子类图元在父类图元的后面,也就是先绘制子类图元,然后再绘制父类图元。可以利用函数setFlag()设置ItemStacksBehindParent属性给图元。
4、事件处理
QgraphicsItem从场景中通过sceneEvent()函数来接受事件,sceneEvent()函数通过一些方便的操作分散大部分事件。
A、拖拽事件
dropEvent()、dragLeaveEvent() 。
[virtual protected] void dragEnterEvent(QGraphicsSceneDragDropEvent *event)
[virtual protected] void dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
[virtual protected] void dragMoveEvent(QGraphicsSceneDragDropEvent *event)
[virtual protected] void dropEvent(QGraphicsSceneDragDropEvent *event)
B、鼠标事件
[virtual protected] void mouseMoveEvent(QGraphicsSceneMouseEvent *event)
[virtual protected] void mousePressEvent(QGraphicsSceneMouseEvent *event)
[virtual protected] void mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
5、动画效果
实现图元的动画效果,也可以在不同的层面进行。如果只想控制一两个图元的动画,一般在场景或视图中实现。但是要是想让一个图元类的多个对象都进行同样的动画,那么我们就可以在图元类的构造函数中进行实现。
6、移动
图元的移动,有多种方法实现,可以在视图或场景上控制,但对于不同类型的大量图元,怎样能一起控制呢?在图形视图框架中提供了advance()槽函数,advance()函数在QGraphicsScene和QGraphicsItem中都有定义,在图元类中的原型是advance(int phase)。实现流程是,利用QGraphicsScene类的对象调用QGraphicsScene的advance()函数,会执行两次场景中所有图元的advance(int phase)函数,第一次phase为0,告诉所有图形项即将要移动;第二次phase的值为1,执行移动。
setFlag(QGraphicsItem::ItemIsMovable);
7、图元的坐标转换
QgraphicsItem支持坐标转换,对于简单的转换,可以调用函数setRotation()或者setScale(),可以传递一个转换矩阵给函数setTransform(),对于一些更复杂的转换,可以通过调用函数setTransformations()来设置一系列组合的转换。
图元转换从父类到子类进行聚集,因此如果一个父类图元和子类图元都旋转90度,那么子类图元就旋转了180度;如果父类图元和子类图元都放大了2X倍,那么子类图元就被放大4X倍,图元的转换不影响图元的外观,所有和外观有关的函数(例如contains(),update()和所有的映射mapping函数)将会在本地坐标中操作,QgraphicsItem提供函数sceneTransform(),将会返回图元所有的转换矩阵,scenePos()将会返回图元在场景坐标中的位置,重新设置图元的矩阵,调用函数resetTransform()。
一般的转换回产生一个不同的结果,取决于转换应用的顺序,转换顺序不同得到结果将不同。
8、主要成员函数
QVariant itemChange(GraphicsItemChange change, const QVariant & value)
三、QGraphicsView
1、QGraphicsView简介
QGraphicsView继承自QAbstractScrollArea,继承了QWidget的特性。
2、事件处理
在图形视图框架中,鼠标键盘等事件是从视图进入的,视图将事件传递给场景,场景再将事件传递给该点的图元,如果该点有多个图元,那么就传给最上面的图元。为了使事件能进一步传播到场景,需要在重新实现事件处理函数时,在其最后将event参数传给默认的事件处理函数。比如重写了视图的鼠标按下事件处理函数,那么就在该函数的最后写上QGraphicsView::mousePressEvent(event);
A、拖拽事件
[virtual protected] void dragEnterEvent(QDragEnterEvent *event)
[virtual protected] void dragLeaveEvent(QDragLeaveEvent *event)
[virtual protected] void dragMoveEvent(QDragMoveEvent *event)
[virtual protected] void dropEvent(QDropEvent *event)
在以上拖拽事件处理函数中的末尾需要调用QGraphicsView类相应的事件处理函数。
QGraphicsView::dragEnterEvent(event);
QGraphicsView::dragLeaveEvent(event);
QGraphicsView::dragMoveEvent(event);
QGraphicsView::dropEvent(event);
B、鼠标事件
[virtual protected] void mouseMoveEvent(QMouseEvent *event)
[virtual protected] void mousePressEvent(QMouseEvent *event)
[virtual protected] void mouseReleaseEvent(QMouseEvent *event)
void setMouseTracking(bool enable)
在以上鼠标事件处理函数中的末尾需要调用QGraphicsView类相应的事件处理函数。
QGraphicsView::mouseMoveEvent(event);
QGraphicsView::mousePressEvent(event);
QGraphicsView::mouseReleaseEvent(event);