一、GraphicsView框架简介
QT4.2开始引入了Graphics View框架用来取代QT3中的Canvas模块,并作出了改进,Graphics View框架实现了模型-视图结构的图形管理,能对大量图元进行管理,支持碰撞检测,坐标变换和图元组等多种方便的功能。

GraphicsView框架结构主要包含三个主要的类QGraphicsScene(场景)、QGraphicsView(视图)、QGraphicsItem(图元)。QGraphicsScene本身不可见,是一个存储图元的容器,必须通过与之相连的QGraphicsView视图来显示及与外界进行交互,主要提供图元的操作接口、传递事件和管理各个图元状态,提供无变换的绘制功能(如打印);QGraphicsView提供一个可视的窗口,用于显示场景中的图元,一个场景中可以有多个视图。QGraphicsItem是场景中各个图元的基础类,QT提供了常用图形图元的标准类,如矩形(QGraphicsRectItem)、椭(QGraphicsEllipseItem)、文本(QGraphicsTextItem)。

GraphicsView是一个基于图元的Model/View架构的框架,每一个组件都是一个独立的元素。QPainter采用面向过程的描述方式绘图;GraphicsView采用面向对象的描述方式绘图。GraphicsView绘图时首先创建一个场景,然后创建图元对象(如一个直线对象、一个多边形对象),再使用场景的add()函数,将图元对象添加到场景中,最后通过视图进行显示。对于复杂的图像来说,如果图像包含大量的直线、曲线、多边形等图元对象,管理图元对象比管理QPainter的绘制过程语句要容易,并且图元对象更符合面向对象的思想,图形的可复用性更好。

二、QGraphicsScene场景
QGraphicsScene场景是QGraphicsItem对象的容器,主要功能如下:

A、提供管理大量图元的快速接口 

B、传播鼠标、键盘等事件给场景中的每个图元

C、管理图元状态,如图元选择和焦点处理

D、提供无变换的渲染功能,如打印

通过函数QGraphicsScene::addItem()可以加入一个图元到场景中。图元可以通过多个函数进行检索:QGraphicsScene::items()及重载函数可以返回和点、矩形、多边形或向量路径相交的所有图元。QGraphicsScene::itemAt()返回指定点的最顶层图元。所有图元查找函数按照递减栈顺序返回图元,第一个返回的图元位置最顶层,最后一个返回的图元位于最底层。

QGraphicsScene的事件传播体系将场景事件发送给图元,同时也管理图元之间的事件传播。如果场景收到了在某一点的鼠标单击事件,场景会把事件传给在这一点的最顶层图元。QGraphicsScene负责管理一些图元的状态,如图元选择和焦点。通过QGraphicsScene::setSeletionArea()函数选择多个图元,选择区域可以是任意的形状,使用 QPainterPath表示;要得到当前选择的图元列表可以使用 QGraphicsScene::selectedItems()函数;QGraphicsScene还管理图元的键盘输入焦点状态,可以通过QGraphicsScene::setFocusItem()函数或者QGraphicsItem::setFoucs()函数来设置图元的焦点;获得当前具有焦点的图元使用函数QGraphicsScene::foucsItem()。可以使用 QGraphicsScene::render()函数在绘图设备上绘制场景。

三、QGraphicsView视图
QGraphicsView是视图窗口部件,使场景内容可视化,可以连接多个视图到一个场景,也可以为相同数据源的数据集提供不同的视图。QGraphicsView是可滚动的窗口部件,可以提供滚动条来浏览大的场景。如果需要使用OpenGL,可以使用QGraphicsView::setViewport()将视图设置为QGLWidget组件。

视图接收键盘和鼠标的输入事件,并把事件翻译为场景事件(将坐标转换为场景的坐标),再发送到场景。使用变换矩阵函数QGraphicsView::martix()可以变换场景的坐标系统,通过变换场景的坐标系统可以实现场景的缩放和旋转。为了方便,QGraphicsView提供了视图和场景的坐标转换函数:QGraphicsView::mapToScene()和QGraphicsView::mapFromScene()。 

四、QGraphicsItem图元
QGraphicsItem是图元的基类。QGraphics View框架提供了多种标准的图元:

QGraphicsEllipseItem 椭圆图元

QGraphicsLineItem 直线图元

QGraphicsPathItem     路径图元
QGraphicsPixmapItem 图像图元

QGraphicsPolygonItem 多边形图元
QGraphicsRectItem 矩形图元
QGraphicsSimpleTextItem 简单文本图元
QGraphicsTextItem 文本浏览图元

用户可以继承QGraphicsItem实现自定义的图元。

QGraphicsItem图元主要特性如下:

A、支持鼠标按下、移动、释放、双击、悬停、滚动和右键菜单事件。

B、支持键盘输入焦点和按键事件

C、支持拖拽事件

D、支持分组,使用父子关系和QGraphicsItemGroup

E、支持碰撞检测

图元存在于本地坐标系统上,场景提供了在图元和场景间、图元与图元间进行坐标变换的函数。QGraphicsItem::transform()函数可以使用矩阵转换坐标系统。这对于翻转和缩放图元是有用的。

图元可以包含其他图元,父图元的变换会被其所有的子图元继承。无论一个图元本身有多少变换,图元的所有函数(QGraphicsItem::contains(), QGraphicsItem::boundingRect(), QGraphicsItem::collidesWith())仍旧执行在本地坐标系上。

QGraphicsItem通过虚函数shape()和collideWith())来支持碰撞检测。从shape()返回图元的形状(以本地坐标QPainterPath表示),QGraphicsItem会处理所有的碰撞检测。如果要提供自己的碰撞检测,需要重新实现QGraphicsItem::collideWith()。

碰撞检测的方法:

a、重写shape()函数来返回图元的精准轮廓,依靠默认的collidesWithItem()来做外形交集。如果item轮廓和复杂时候,消耗是很大的。
b、重写collidesWithItem(),提供一个自己的图元和轮廓碰撞的算法

Contains()函数可以调用,用来决定一个图元是否包含一个点。Contains函数可以重写,contains()函数默认的方法是通过调用shape()来完成的。
图元中也可以包含其他的图元,也可以被别的图元包含,所有的图元可以有一个父类图元和多个子类图元,除非一个图元没有父类,否则图元的位置是在父类坐标中,子类图元将会继承父类图元的位置和转换。

通过调用setVisible(),可以设置图元是否可见,隐藏一个图元同时也隐藏了其子类,通过调用 setEnabled()来是指图元是否可用。如果禁用了图元,那么其所有的子类都不可用。图元默认都是可见和可用的。

五、GraphicsView坐标系统
Graphics View坐标系基于笛卡尔坐标系,图元的场景中的位置和几何形状通过x坐标和y坐标表示。当使用没有变换的视图观察场景时,场景中的一个单位对应屏幕上的一个像素。

Graphics View架构中有三个有效的坐标系统,图元坐标、场景坐标和视图坐标。Graphics View提供了三个坐标系统之间的转换函数。在绘制图形时,QGraphics View的场景坐标对应QPainter的逻辑坐标,QGraphics View的视图坐标对应QPainter的设备坐标。 

1、图元坐标
图元存在于自己的本地坐标上,图元的坐标系统通常以图元中心为原点,图元中心也是所有坐标变换的原点,图元坐标方向是x轴正方向向右,y轴正方向向下。创建自定义图元时,只需要注意图元的坐标,QGraphicsScene和QGraphicsView会完成所有的变换。 例如,如果接受到一个鼠标按下或拖入事件,所给的事件位置是基于图元坐标系的。如果某个点位于图元内部,使用图元上的点作为QGraphicsItem::contains()虚函数的参数,函数会返回true。类似,图元的边界矩形和形状也是基于图元坐标系。

图元的位置是图元的中心点在其父图元坐标系统的坐标。按这种说法,场景是所有无父图元的图元的父图元。顶层图元的位置是场景坐标。

子图元的坐标与父图元的坐标相关。如果子图元无变换,子图元坐标和父图元坐标之间的区别与他们的父图元的坐标相同。例如,如果一个无变换的子图元精确的位于父图元的中心点,父子图元的坐标系统是相同的。如果子图元的位置是(10,0),子图元上的点(0,10)就是父图元上的点(10,10)。

由于图元的位置和变换与父图元相关,但子图元的坐标并不会被父图元的变换影响,虽然父图元的变换会隐式地变换子图元。在上例中,即使父图元被翻转和缩放,子图元上的点(0,10)仍旧是父图元上的点(10,10)。

如果调用QGraphicsItem类的paint()函数重绘图元时,则以图元坐标系为基准。

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()其他图元到本图元

在场景中处理图元时,从场景到图元、从图元到图元、从视图到场景进行坐标和图形变换是有用的。当在QGraphicsView的视口中点击鼠标时,应该通过调用QGraphicsView::mapToScence()与QGraphicsScene::itemAt()来获知光标下是场景中的哪个图元;如果想获知一个图元位于视口中的位置,应该先在图元上调用QGraphicsItem::mapToScene(),然后调用QGraphicsView::mapFromScene();如果想获知在一个视图椭圆中有哪些图元,应该把QPainterPath传递到mapToScene(),然后再把映射后的路径传递到QGraphicsScene::items()。 可以调用QGraphicsItem::mapToScene()与QGraphicsItem::mapFromScene()在图元与场景之间进行坐标与形状的映射,也可以在子图元与其父图元之间通过QGraphicsItem::mapToParent()与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)

{

  QMimeData *data = new QMimeData;

data->setColor(Qt::green);

QDrag *drag = new QDrag(event->widget());

drag->setMimeData(data);

drag->start();

}

要在场景中取拖拽事件,需要重新实现QGraphicsScene::dragEnterEvent()和QGraphicsItem子类里特定场景需要的事件处理器。

图元也可以通过调用QGraphicsItem::setAcceptDrops()获得拖拽支持,为了处理将要进行的拖拽,需要重新实现QGraphicsItem的dragEnterEvent()、dragMoveEvent()、dropEvent()、dragLeaveEvent() 。

4、光标与工具提示
QGraphicsItem支持光标(QgraphicsItem::setCursor)与工具提示(QGraphicsItem::setToolTip())。当光标进入到图元的区域,光标与工具提示被QGraphicsView激活(通过调用QGraphicsItem::contains()检测),也可以直接在视图上设置一个缺省光标(QGraphicsView::setCursor)。

5、动画
GraphicsView框架支持多种层次的动画。使用动画框架可以很容易制作出动画。

GraphicsView框架支持的动画实现种类如下:

A、图元需要继承自QGraphicsObject,并且需要联结QPropertyAnimation属性。

B、创建继承自QObject和QGraphicsItem的图元,图元可以设置自己的定时器,在QObject::timeEvent()中增加步进的方式来控制动画。
C、通过调用QGraphicsScene::advance()来推进场景,依次调用QGraphicsItem::advance()。

6、OpenGL渲染
为了使用OpenGL渲染,需要设置一个新的QGLWidget作为QGraphicsView的视口:QGraphicsView::setViewPort()。如果需要OpenGL提供反锯齿功能,则需要OpenGL采样缓冲支持。
QGraphicsView view(&scene);
view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));

7、图元组
通过把一个图元做为另一个图元的孩子,可以得到图元组的大多数本质特性:所有图元会一起移动,所有变换会从父到子传递。

另外,QGraphicsItemGroup是一个特殊的图元。为了增加和删除图元,它使用一个有用接口合并了子图元的事件处理。把一个图元加到QGraphicsItemGroup仍会保留图元的原始位置与变换,而给一个图元重新指定父图元则会让图元根据其新的父亲重新定位。可以用QGraphicsScene::createItemGroup()创建图元组。

8、图形组件和布局
QT4.4通过QGraphicsWidget支持图形和图元布局。QGraphicsWidget类似于QWidget,但QGraphicsWidget并不从QPaintDevice继承,而是继承自QGraphicsItem。QGraphicsWidget支持事件、信号与槽、大小和策略。通过QGraphicsLinearLayout、QGraphicsGridLayout可以对图形组件进行布局管理。

QGraphicsWidget继承了QWidget和QGraphicsItem的优点,如QWidget的样式、字体、调色板、布局方向以及QGraphicsItem的图形、独立精度和变换支持。

QGraphicsLayout是专为QGraphicsWidget特殊设计的第二代布局框架。QGraphicsLayout的API类似于QLayout。通过QGraphicsLinearLayout和QGraphicsGridLayout可以管理组件与子布局。

9、嵌入组件
图形视图框架为嵌入任何组件到场景提供了无缝支持。可以嵌入简单的组件,如QLineEdit、QPushButton,或是复杂的组件如QTableWidget,甚至是主窗口。

要嵌入组件到场景,只需要调用QGraphicsScene::addWidget(),或是创建一个QGraphicsProxyWidget实例,手动嵌入组件。

通过QGraphicsProxyWidget,图形视图框架可以深度整合客户组件特性,如光标、工具提示、鼠标、平板和键盘事件、子组件、动画、弹拉框、组件输入焦点和激活。QGraphicsProxyWidget甚至整合了嵌入组件的tab顺序,可以通过tab选择嵌入的组件。甚至可以嵌入一个QGraphicsView到场景。

当变换和嵌入组件时,图形视图框架会确保组件会被独立变换。

一、QGraphicsScene
1、QGraphicsScene
QGraphicsScene继承自QObject,是一个管理图元的容器,与QGraphicsView合用可以在2D屏幕上显示如线、三角形、文本、自定义图元等图元。

QGraphicsScene是不可见的,只用于管理图元。为了查看场景,需要创建一个视图组件。

一个场景分为三个层:图元层、前景层和背景层。场景的绘制总是从背景层开始,然后是图形项层,最后是前景层。

2、事件处理与传播
QGraphicsScene的责任之一是传播来自视图的事件。要发送一个事件到场景,需要构造一个继承自QEvent的事件,使用QApplication::sendEvent()函数发送事件。event()函数负责派发事件到各个图元。常用的事件会被便利事件处理函数处理,如鼠标按下事件会被mousePressEvent()函数处理。

按键事件会被派发到焦点图元。为了设置焦点图元,可以调用setFocusItem()函数,或是图元自身调用QGraphicsItem::setFocus()函数。调用focusItem()函数可以获取当前的焦点图元。为了兼容图形组件,场景维护着自己的焦点信息。默认场景并没有焦点,并且所有的按键事件会别丢弃。如果setFocus()函数被调用,或是场景中一个图元获得了焦点,场景会自动获得焦点。如果场景有焦点,hasFocus()函数会返回true,按键事件会被发送到焦点图元。如果场景失去了焦点,而图元有焦点(如调用clearFocus()函数),场景会维护图元的焦点信息,一旦场景重新获得焦点,会确保最后一个有焦点的图元获得焦点。

对于悬停效果,QGraphicsScene会派发悬停事件,如果某个图元接受了悬停事件(调用QGraphicsItem::acceptHoverEvents()),当鼠标进入图元的区域时,图元会接收到一个GraphicsSceneHoverEnter事件。当鼠标继续在图元内部移动时,QGraphicsScene会发送GraphicsSceneHoverMove事件。当鼠标离开图元的区域时,图元会收到一个GraphicsSceneHoverLeave事件。

所有鼠标事件会被传播到当前鼠标获取的图元。如果一个图元接收了鼠标事件,并收到鼠标按下,图元就是场景的鼠标获取图元。这个图元会一直被鼠标获取,直到图元收到一个鼠标释放事件。调用mouseGrabberItem()函数可以知道当前鼠标获取的图元。

场景可以传递来自视图的事件,将事件传递给该点最顶层的图元。如果一个图元要接收键盘事件,那么它必须获得焦点。而且,如果在场景中重写了事件处理函数,那么在该函数的最后必须调用场景默认的事件处理函数,只有这样,图元才能接收到该事件。

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中定义,分别是:

QGraphicsSecne::BspTreeIndex :应用Binary Space Partition tree,适合于大量的静态图元,是默认值。

QGraphicsScene::NoIndex :不用索引,搜索场景中所有的图元,适合于经常进行图元的添加、移动和删除等操作的情况。

使用setItemIndexMethod()函数进行索引算法的更改。

4、边界矩形
图元可以放到场景的任何位置,场景的大小默认是没有限制的。而场景的边界矩形仅用于场景内部进行索引的维护。因为如果没有边界矩形,场景就要搜索所有的图元,然后确定出其边界,这是十分费时的。所以如果要操作一个较大的场景,应该给出它的边界矩形。

设置边界矩形,可以使用setSceneRect()函数。

5、图元的查找
场景最大的优势之一就是可以快速的锁定图元的位置,即使有上百万个图元,items()函数也能在数毫秒的时间内锁定一个图元的位置。items()函数有几个重载函数来方便的进行图元的查找。如果在场景的一个点可能重叠着几个图元,可以使用itemAt()函数返回最上面的一个图元。

二、QGraphicsItem
1、自定义QGraphicsItem
QGraphicsItem是图元的基类。

自定义图元,首先应该继承QGraphicsItem,然后重写他的两个纯虚公共函数boundingRect()和paint(),boundingRect()函数返回绘制图元大概的区域,paint()函数用来绘制图元内容。

boundingRect()函数有很多用处,场景在boundingRect()来建立它的图元的index,视图使用boundingRect来剪切可见的图元,在重新绘制图元时候,来决定相互重叠的部分,此外,图元的碰撞检测机制也使用的boundingRect()来提供一个高效的定点,在collidesWithItem()更好的碰撞算法建立在调用函数shape(),shape()函数以QpainterPath类型返回图元的精准的轮廓。
场景不希望图元的boundingRect()和shape()变化,除非该图元被通告,如果想通过一些方法改变图元的形状,首先应该调用QgraphicsScene()来允许场景QgraphicsScene来刷新它的图元记录。

图元没有获得焦点时,事件只能从视图传递到场景,不能传递到图元。清除图元的焦点函数为clearFocus()。

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()函数通过一些方便的操作分散大部分事件。

ContextMenuEvent()函数接受上下文菜单事件,

FocusInEvent()和focusOutEvent()函数接受焦点进出事件,
hoverEnterEvent()、hoverMoveEvent()、hoverLeaveEvent() 接受鼠标悬浮移动和离开事件。
inputMethodEvent()函数处理输入法事件,
keyPressEvent()和keyReleaseEvent()事件处理键盘按下和释放事件
mousePressEvent()、mouseMoveEvent()、mouseReleaseEvent()、 mouseDoubleClickEvent()处理鼠标按下、移动、释放、双击事件
通过安装过滤器,可以为图元过滤一些事件,与QT一般的事件过滤器不一样,一般的过滤器只工作在Qobject及其子类。通过调用 installSceneEventFilter()为图元安装事件过滤器后,被过滤的事件将会被虚函数sceneEventFilter()捕捉 到,可以通过调用函数removeSceneEventFilter()来去除掉事件过滤器。

A、拖拽事件

GraphicsView框架为视图、场景、图元提供拖拽支持。当视图接收到拖拽事件,GraphicsView框架会将拖拽事件翻译为QGraphicsSceneDragDropEvent事件,再发送到场景,场景接管事件,把事件发送到光标下接受拖拽的第一个图元。
从图元开始拖拽时,创建一个QDrag对象,传递开始拖拽的QWidget的指针。图元可以同时被多个视图观察,但只有一个视图可以开始拖拽。拖拽在多数情况下是从按下鼠标或是移动鼠标开始的,在mousePressEvent()或mouseMoveEvent()中,可以从事件中得到原始的QWidget指针。

要在场景中取拖拽事件,需要重新实现QGraphicsScene::dragEnterEvent()和QGraphicsItem子类里任何与特定场景需要的事件处理器。图元也可以通过调用QGraphicsItem::setAcceptDrops()获得拖拽支持,为了处理将要进行的拖拽,需要重新实现QGraphicsItem的dragEnterEvent()、dragMoveEvent()、

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、鼠标事件

要在自定义图元类中处理鼠标事件,需要重写QGraphicsItem类中鼠标按下、鼠标移动、鼠标释放的事件。

[virtual protected] void mouseMoveEvent(QGraphicsSceneMouseEvent *event)

[virtual protected] void mousePressEvent(QGraphicsSceneMouseEvent *event)

[virtual protected] void mouseReleaseEvent(QGraphicsSceneMouseEvent *event)

5、动画效果
实现图元的动画效果,也可以在不同的层面进行。如果只想控制一两个图元的动画,一般在场景或视图中实现。但是要是想让一个图元类的多个对象都进行同样的动画,那么我们就可以在图元类的构造函数中进行实现。

//图元可获得焦点

setFlag(QGraphicsItem::ItemIsFocusable);

//图元可移动

setFlag(QGraphicsItem::ItemIsMovable);

QGraphicsItemAnimation *anim = new QGraphicsItemAnimation;

//将图元加入动画对象中

anim->setItem(this);

//创建长为1秒的时间线

QTimeLine *timeLine = new QTimeLine(1000);

//动画循环次数为0,表示无限循环

timeLine->setLoopCount(0);

//将时间线加入动画类对象中

anim->setTimeLine(timeLine);

//在动画时间的一半时图形项旋转180度

anim->setRotationAt(0.5,180);

//在动画执行完时图形项旋转360度

anim->setRotationAt(1,360);

//开始动画

timeLine->start();

6、移动
图元的移动,有多种方法实现,可以在视图或场景上控制,但对于不同类型的大量图元,怎样能一起控制呢?在图形视图框架中提供了advance()槽函数,advance()函数在QGraphicsScene和QGraphicsItem中都有定义,在图元类中的原型是advance(int phase)。实现流程是,利用QGraphicsScene类的对象调用QGraphicsScene的advance()函数,会执行两次场景中所有图元的advance(int phase)函数,第一次phase为0,告诉所有图形项即将要移动;第二次phase的值为1,执行移动。

QTimer timer;

QObject::connect(&timer, SIGNAL(timeout()),scene, SLOT(advance()));

timer.start(1000);

至于图元如何移动,需要重写图元类的advance()函数。

如果在自定义图元类的构造函数中设置为可移动,则图元可以直接使用鼠标拖拽。

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)

itemChange函数被QGraphicsItem调用用来标识图元的状态改变了,通过重载itemChange函数,可以对自己定义事件响应。参数change是改变的图元的改变状态参数,value是一个新的数据,类型取决于change,change是QGraphicsItem::GraphicsItemChange枚举变量。

在itemChange函数内部调用函数时候要谨慎,不能在itemChange函数里面调用setPos(),参数change是ItemPositionChange时,setPos()函数将会再次调用itemChange(ItemPositionChange),形成死循环。

void setFlag(GraphicsItemFlag flag, bool enabled = true)

void setFlags(GraphicsItemFlags flags)

flags设置为图元的属性,如果图元获得了光标,但flags没有使能ItemsFocusable,图元将会丢失光标,当图元被选择,但没有使能ItemsSelectable,图元会自动的失去选择。
QPainterPath shape () const

以QPainterPath返回图元在本地坐标中的形状,形状可以用来做很多事情,包括碰撞侦测,打击测试,还有用来 QGraphicsScene::items() 函数
默认的函数调用boundingRect()返回一个简单的矩形形状,子类可以重载boundingRect函数,为非矩形的图元返回一个更加精准的形状,例如一个圆形的图元可以选择返回一个椭圆形,用来获得更好的碰撞侦测效果。

三、QGraphicsView
1、QGraphicsView简介
QGraphicsView继承自QAbstractScrollArea,继承了QWidget的特性。

QGraphicsView提供了视图窗口部件,使场景的内容可视化。可以给一个场景关联多个视图,从而给一个数据集提供多个视口。视图部件是一个滚动区域,可以提供一个滚动条来显示大型的场景。

2、事件处理
在图形视图框架中,鼠标键盘等事件是从视图进入的,视图将事件传递给场景,场景再将事件传递给该点的图元,如果该点有多个图元,那么就传给最上面的图元。为了使事件能进一步传播到场景,需要在重新实现事件处理函数时,在其最后将event参数传给默认的事件处理函数。比如重写了视图的鼠标按下事件处理函数,那么就在该函数的最后写上QGraphicsView::mousePressEvent(event);

A、拖拽事件

在QGraphicView中提供了三种拖拽模式,分别是:

QGraphicsView::NoDrag :忽略鼠标事件,不可以拖动。

QGraphicsView::ScrollHandDrag :光标变为手型,可以拖动场景进行移动。

QGraphicsView::RubberBandDrag :使用橡皮筋效果,进行区域选择,可以选中一个区域内的所有图元。

可以利用setDragMode()函数进行相应设置。

[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);