前话
Qt的图形视图框架,最核心的三个类为:QGraphicsScene、QGraphicsItem与QGraphicsView。
高级白板软件框架Demo
QGraphicsScene
描述
QGraphicsScene类提供了一个用于管理大量二维图形项的面。
该类用作QGraphicsItems的容器。它与QGraphicsView一起用于在二维面上可视化图形项,例如线条、矩形、文本,甚至自定义项。QGraphicsScene是图形视图框架的一部分。
QGraphicScene还提供了一些功能,可以让有效地确定项目的位置,以及确定在场景中任意区域内哪些项目可见。使用QGraphicsView小部件,可以可视化整个场景,或者放大并只查看场景的部分。
示例:
QGraphicsScene *pScene = new QGraphicsScene(); pScene->addText("Hello, world!"); QGraphicsView *pView = new QGraphicsView(pScene, this);
请注意,QGraphicScene本身没有视觉外观;它只管理项目。需要创建一个QGraphicsView小部件来可视化场景(设置它的父类为可视窗口,如QWidget)。
要将项目添加到场景中,首先要构造一个QGraphicsScene对象。然后,有两个选项:要么通过调用addIitem()添加现有的QGraphicsItem对象(主要是自定义继承的QGraphicsItem),要么调用便利函数addEllipse()、addLine()、addPath()、addPixmap()、addPolygon()、addRect()或addText(),这些函数都返回指向新添加项的指针。使用这些函数添加的项目的尺寸是相对于项目的坐标系的,并且项目位置在场景中初始化为(0,0)。
然后可以使用QGraphicsView可视化场景。当场景更改时(例如,当项目移动或转换时),QGraphicScene会发出changed()信号。若要删除项,请调用RemoveItem()。
QGraphicScene使用索引算法来有效地管理项目的位置。默认情况下,使用BSP(二进制空间分区)树;该算法适用于大多数项目保持静态(即不移动)的大型场景。可以通过调用setItemIndexMethod()来选择禁用此索引。有关可用索引算法的详细信息,请参阅ItemIndexMethod属性。
场景的边界矩形是通过调用setSceneRect()设置的。项目可以放置在场景的任何位置,默认情况下,场景的大小是无限的。场景矩形仅用于内部记账,维护场景的项索引。如果场景rect未设置,QGraphicScene将使用itemsBoundingRect()返回的所有项的边界区域作为场景矩形。但是,itemsBoundingRect()是一个相对耗时的函数,因为它通过收集场景中每个项目的位置信息来操作。因此,在大型场景上操作时,应始终设置场景矩形。
QGraphicScene最大的优点之一是它能够有效地确定项目的位置。即使现场有数百万个项目,items()函数也可以在几毫秒内确定项目的位置。items()有几个重载:一个在特定位置查找项目,一个在多边形或矩形内或与多边形或矩形相交的项目,等等。返回项目的列表按堆叠顺序排序,最上面的项目是列表中的第一个项目。为了方便起见,还有一个itemAt()函数在给定位置返回最上面的项。
QGraphicScene维护场景的选择信息。要选择项,请调用setSelectionArea(),要清除当前选择,请调用clearSelection()。调用selectedItems()以获取所有选定项的列表。
事件处理和传播
QGraphicsScene的另一个职责是从QGraphicsView传播事件。要将事件发送到场景,可以构造继承QEvent的事件,然后使用QApplication::SendEvent()发送它。event()负责将事件分派给各个项目。一些常见事件由方便的事件处理程序处理。例如,按键事件由keypressEvent()处理,鼠标按键事件由mousePressEvent()处理。
关键事件传递到焦点项目。要设置焦点项,可以调用setFocusItem(),传递接受焦点的项,或者项本身可以调用QGraphicsItem::setFocus()。调用focusItem()以获取当前焦点项。为了与小部件兼容,场景还维护自己的焦点信息。默认情况下,场景没有焦点,所有关键事件都将被丢弃。如果调用了setFocus(),或者场景中的某个项目获得焦点,则场景将自动获得焦点。如果场景有焦点,hasFocus()将返回true,并且关键事件将转发到焦点项(如果有的话)。如果场景失去焦点(即有人调用clearFocus()),而某个项目有焦点,则该场景将保持其项目焦点信息,并且一旦场景恢复焦点,它将确保最后一个焦点项目恢复焦点。
对于鼠标悬停效果,QGraphicsScene会发送悬停事件。如果一个项接受悬停事件(请参见QGraphicsItem::acceptHoverEvents()),则当鼠标进入其区域时,它将收到一个QGaphicsSceneHoverCenter事件。当鼠标继续在项目区域内移动时,QGraphicsScene将向其发送GraphicsSceneHoverMove事件。当鼠标离开项目区域时,项目将收到一个GraphicsSceneHoverLeave事件。
所有鼠标事件都将传递到当前鼠标抓取器项。如果一个项目接受鼠标事件(请参见QGraphicsItem::acceptedMouseButtons())并接收到鼠标按下,它将成为场景的鼠标抓取器。它保持鼠标抓取器,直到在没有其他鼠标按钮被按下时收到鼠标释放为止。可以调用mouseGramberItem()来确定当前正在获取鼠标的项。
QGraphicsView
QGraphicsView类提供一个窗口,用于显示QGraphicsScene的内容。
在QGraphicsView将可视内容滚动的视口中。几何项创建场景的步骤参考,QGraphicsScene的文档,QGraphicsView图形视图框架的一部分。
可视化一个场景,通过建构QGraphicsView通过对象的地址,可看QGraphicsView的构造函数,或者也可以随后调用setScene()显示。在你调用show(),视窗将默认将滚动到视图中心并显示可见的所有项目。例如:
QGraphicsScene *pScene = new QGraphicsScene(); pScene->addText("Hello, world!"); QGraphicsView *pView = new QGraphicsView(pScene, this);
可以使用滚动条或调用centerOn()显式滚动到场景中的任何位置。通过将点传递给centerOn(),QGraphicsView将滚动其视区以确保该点在视图中居中。提供了一个用于滚动到QGraphicsItem的重载,在这种情况下,QGraphicsView将看到该项的中心在视图中居中。如果只想确保某个区域是可见的(但不一定是居中的),那么可以调用ensureVisible()。
QGraphicsView可以用来可视化整个场景,或者只显示其中的一部分。默认情况下,在首次显示视图时自动检测可视化区域(通过调用QGraphicsScene::itemsBoundingRect())。要自己设置可视化区域矩形,可以调用setScenRect()。这将适当调整滚动条的范围。请注意,尽管场景支持几乎不受限制的大小,但滚动条的范围永远不会超过整数的范围(INT_MIN,INT_MAX)。
QGraphicsView通过调用render()可视化场景。默认情况下,通过使用常规的QPainer和默认的渲染提示将项目绘制到视区上。要在绘制项时更改QGraphicsView传递给QPainter的默认渲染提示,可以调用setRenderHints()。
默认情况下,QGraphicsView为viewport小部件提供常规的QWidget。可以通过调用viewport()来访问这个小部件,也可以通过调用setViewport()来替换它。要使用OpenGL进行渲染,只需调用setViewPort(new QGLWidget)。QGraphicsView拥有viewport小部件的所有权。
QGraphicsView使用QTransform支持仿射转换。可以将矩阵传递给setTransform(),也可以调用便利函数rotate()、scale()、translate()或shear()。最常见的两种转换是缩放,用于实现缩放和旋转。QGraphicsView在转换期间保持视图中心不变。由于场景对齐(setAligment()),转换视图不会产生视觉影响。
可以使用鼠标和键盘与场景中的项目进行交互。QGraphicsView将鼠标和键事件转换为场景事件(继承QGraphicsSceneEvent的事件),并将它们转发到可视化的场景。最后,处理事件并对其做出反应的是单个项目。例如,如果单击一个可选择的项,该项通常会让场景知道它已被选中,并且它也会重新绘制自己以显示一个选择矩形。类似地,如果你点击并拖动鼠标来移动一个可移动的项目,它就是处理鼠标移动和移动自己的项目。项交互在默认情况下是启用的,可以通过调用setInteractive()来切换它。
还可以通过创建QGraphicsView的子类并重新实现鼠标和键事件处理程序来提供自己的自定义场景交互。为了简化如何以编程方式与视图中的项进行交互,QGraphicsView提供了映射函数mapToScene()和mapFromScene(),以及项访问器items()和itemAt()。这些函数允许在视图坐标和场景坐标之间映射点、矩形、多边形和路径,并使用视图坐标在场景中查找项目。
QGraphicsItem
详细介绍
QGraphicsItem类是QGraphicsScene中所有图形项的基类。
它为编写自己的自定义项目提供了轻量级的基础。这包括通过事件处理程序定义项的几何体、冲突检测、其绘制实现和项交互。QGraphicsItem是图形视图框架的一部分。
为了方便起见,Qt为最常见的形状提供了一组标准图形项。这些是:
- QGraphicsEllipseItem 提供椭圆项
- QGraphicsLineItem 提供直线项
- QGraphicsPathItem 提供任意路径项
- QGraphicsPixmapItem 提供pixmap项
- QGraphicsPolygonItem 提供多边形项
- QGraphicsRectItem 提供矩形项
- QGraphicsSimpleTextItem 提供简单的文本标签项
- QGraphicsTextItem 提供高级文本浏览器项
(补充:若是为了深度使用,建议全部重写一个公用积累,然后所有元素item自己写,不使用自带的。)
项目的所有几何信息都基于其局部坐标系。项的位置pos()是唯一不在本地坐标中操作的函数,因为它返回父坐标中的位置。图形视图坐标系详细描述了坐标系。
可以通过调用setVisible()来设置项是否应可见(即,绘制和接受事件)。隐藏项目也会隐藏其子项。同样,可以通过调用setEnabled()来启用或禁用项。如果禁用某个项目,它的所有子项也将被禁用。默认情况下,项目既可见又启用。若要切换是否选择项,请首先通过设置itemIsSelectable标志启用选择,然后调用setSelected()。通常,选择由场景切换,这是用户交互的结果。
要编写自己的图形项,首先要创建QGraphicsItem的子类,然后从实现它的两个纯虚拟公共函数开始:boundingRect(),它返回该项绘制的区域的估计值,paint()实现实际绘制。
示例:
class SimpleItem : public QGraphicsItem { public: QRectF boundingRect() const { qreal penWidth = 1; return QRectF(-10 - penWidth / 2, -10 - penWidth / 2, 20 + penWidth, 20 + penWidth); } void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { painter->drawRoundedRect(-10, -10, 20, 20, 5, 5); } };
boundingRect()函数有许多不同的用途。QGraphicsScene基于boundingRect()建立其项索引,QGraphicsView使用它来剔除不可见项,以及确定绘制重叠项时需要重新编译的区域。此外,QGraphicsItem的碰撞检测机制使用boundingRect()提供有效的截止点。collistsWithItem()中的细粒度碰撞算法基于调用shape(),它以QPainterPath的形式返回项目形状的准确轮廓。
QGraphicScene希望所有boundingRect()和shape()项保持不变,除非通知它。如果要以任何方式更改项的几何图形,必须首先调用prepareGeometryChange()以允许QgraphicsScene更新其记账。
碰撞检测有两种方式:
- 重载shape()返回项的准确形状,并依赖collapsWithItem()的默认实现来进行形状交集。如果形状复杂的话,这可能会很贵(指性能消耗)。
- 重载collistWithItem()提供自己的自定义项和形状冲突算法。
可以调用contains()函数来确定项是否包含点。此函数也可以由项重新实现。contains()的默认行为基于调用shape()。
项可以包含其他项,也可以包含在其他项中。所有项都可以有父项和子项列表。除非该项没有父项,否则它的位置在父坐标(即父项的局部坐标)中。父项将它们的位置及其转换传播到所有子项。
转换
除了基础位置pos()之外,QGraphicsItem还支持投影转换。有几种方法可以更改项的转换。对于简单的转换,可以调用方便函数setRotation()或setScale(),也可以将任何转换矩阵传递给setTransform()。对于高级转换控制,还可以通过调用setTransforms()来设置多个组合转换。
项目转换从父项到子项叠加,因此如果父项和子项都旋转90度,子项的总转换将为180度。类似的,如果项目的父级缩放到原始大小的2倍,则其子级也将是原始大小的两倍。项的转换不会影响其自身的局部几何图形;所有几何图形函数(例如,contains()、update()和所有映射函数)仍在局部坐标中操作。为了方便起见,QGraphicsItem提供函数sceneTransform(),它返回项的总转换矩阵(包括其位置以及所有父级位置和转换),scenePos()返回其在场景坐标中的位置。若要重置项的矩阵,请调用resetTransform()。
某些转换操作根据应用顺序产生不同的结果。例如,如果缩放一个变换,然后旋转它,可能会得到与先旋转变换不同的结果。但是,在QGraphicsItem上设置转换属性的顺序不会影响生成的转换;QGraphicsItem始终以固定的、定义的顺序应用这些属性:
- 应用项的基础转换(transform())
- 按顺序应用项的转换列表(transforms())
- 该项相对于其转换原点旋转(rotation(),transformOriginPoint())
- 该项相对于其转换原点进行缩放(scale(),transformOriginPoint())
绘制
QGraphicsView调用paint()函数来绘制项的内容。该项本身没有背景或默认填充;该项后面的内容将在该函数中未显式绘制的所有区域中发光。可以调用update()来计划重新绘制,也可以选择传递需要重新绘制的矩形。根据该项在视图中是否可见,可以重新绘制该项,也可以不重新绘制;在QGraphicsItem中没有与QWidget::repaint()等效的项。
项由视图绘制,从父项开始,然后按升序堆叠子项。可以通过调用setZValue()来设置项的堆栈顺序,并通过调用zValue()来测试它,其中z值较低的项在z值较高的项之前绘制。堆叠顺序适用于兄弟项;父项总是在其子项之前绘制。
排序
所有项目都以定义的、稳定的顺序绘制,并且这个顺序决定了当单击场景时,哪些项目将首先接收鼠标输入。通常,不必担心排序,因为项目遵循“自然顺序”,遵循场景的逻辑结构。
项目的子项堆叠在父项的顶部,兄弟项按插入顺序堆叠(即,以添加到场景或添加到同一父项的相同顺序)。如果添加了项目A,然后添加了B,那么B将位于A的顶部。如果添加了C,那么项目的堆叠顺序将是A,然后添加了B,然后添加了C。
这个例子显示了从拖放机器人的例子中机器人所有肢体的堆叠顺序。躯干是根项(所有其他项都是躯干的子项或子项),因此首先绘制躯干。接下来,画出头部,因为它是躯干列表中的第一个项目。然后画出左上臂。由于下臂是上臂的子对象,因此将绘制下臂,然后绘制上臂的下一个兄弟姐妹,即右上臂,依此类推。
对于高级用户,有一些方法可以更改项目的排序方式:
- 可以对一个项调用setZValue()来显式地将它堆叠在其他同级项的顶部或下方。项的默认Z值为0。具有相同Z值的项按插入顺序堆叠。
- 可以调用stackBefore()对子项列表重新排序。这将直接修改插入顺序。
- 可以将ItemStacksBhindParant标志设置为将子项堆叠在其父项之后。
两个同级项的堆叠顺序也计算每个项的子项和子项。因此,如果一个项目位于另一个项目之上,那么它的所有子项也将位于其他项目的所有子项之上。
事件
QGraphicsItem通过虚拟函数sceneEvent()从QGraphicsScene接收事件。此函数将最常见的事件分发给一组方便的事件处理程序:
- contextMenuEvent()处理上下文菜单事件。
- focusInEvent()和focusOutEvent()处理焦点进入和退出事件。
- hoverEnterEvent()、hoverMoveEvent()和hoverLeaveEvent()处理hoverEnter、move和leave事件。
- inputMethodEvent()处理输入事件,以获得辅助功能支持,如中文输入法获取输入内容。
- keyPressEvent()和keyReleaseEvent()处理按键和释放事件。
- mousePressEvent()、mouseMoveEvent()、mouseReleaseEvent()和mouseDoubleClickEvent()处理鼠标按下、移动、释放、单击和双击事件。
可以通过安装事件筛选器来筛选任何其他项目的事件。此功能与Qt的常规事件筛选器(请参阅QObject::InstallEventFilter())不同,后者只在QObject的子类上工作。通过调用InstallSceneEventFilter()将项目安装为另一个项目的事件筛选器后,虚拟函数sceneEventFilter()将接收筛选的事件。可以通过调用removeSceneEventFilter()删除项事件筛选器。
自定义数据
有时,将自定义数据注册到一个项、自定义项或标准项是很有用的。可以对任何项调用setData()以使用键值对(键为整数,值为QVariant)来存储其中的数据。要从项中获取自定义数据,请调用data()。此功能完全不受qt本身的影响;它是为方便用户而提供的。
枚举详解
enum QGraphicsItem::CacheMode:缓存模式
此枚举描述QGraphicsItem的缓存模式。缓存用于通过分配和渲染到非屏幕像素缓冲区来加快渲染速度,当项需要重新绘制时,可以重用该缓冲区。对于某些绘制设备,缓存直接存储在图形内存中,这使得渲染非常快速。
enum QGraphicsItem::GraphicsItemChange:通知状态更改
此枚举描述由QGraphicsItem::itemChange()通知的状态更改。通知将作为状态更改发送,在某些情况下,可以进行调整(有关详细信息,请参阅每个更改的文档)。
注意:在itemChange()内小心调用QGraphicsItem本身的函数,因为某些函数调用可能导致不需要的递归。例如,不能对itemPositionChange通知调用itemChange()中的setPos(),因为setPos()函数将再次调用itemChange(itemPositionChange)。相反,可以从itemChange()返回新的已调整位置。
enum QGraphicsItem::GraphicsItemFlag、flags QGraphicsItem::GraphicsItemFlags:表示功能项
此枚举描述可以在项上设置的不同标志,以切换项行为中的不同功能。默认情况下禁用所有标志。
注意:此标志类似于ItemContainChildrenInShape,但另外还通过剪切子项来强制包含。
注意:设置此标志后,仍然可以缩放项目本身,并且缩放转换将影响项目的子项。
注意:如果同时设置了此标志和ItemClipsChildrenToShape,则将强制执行剪辑。这相当于只设置ItemClipsChildrenToShape。
GRaphicsItemFlags类型是QFlags的typedef。它存储一个或多个GraphicsItemFlag值。
enum QGraphicsItem::PanelModality:面板行为
此枚举指定模式面板的行为。模态面板是阻止输入到其他面板的面板。请注意,属于模式面板子级的项不会被阻止。
可取值为:
成员函数(重要的几个)
bool QGraphicsItem::acceptDrops() const
如果item接受悬停事件(QGraphicsSceneHoverEvent)将会返回true;否则,返回false。默认情况下,图元项没有接受hover事件。
这个功能是在QT 4.4引入的。
bool QGraphicsItem::acceptTouchEvents() const
如果项接受触摸事件,则返回true;否则返回false。默认情况下,项目不接受触摸事件。
Qt4.6中引入了该功能。
Qt::MouseButtons QGraphicsItem::acceptedMouseButtons() const
返回此项接受鼠标事件的鼠标按钮。默认情况下,接受所有鼠标按钮。
如果一个项目接受鼠标按钮,当鼠标按钮的鼠标按下事件被传递时,它将成为鼠标抓取项目。但是,如果项目不接受按钮,QGraphicsScene将把鼠标事件转发到它下面的第一个项目。
[pure virtual] QRectF QGraphicsItem::boundingRect() const
此纯虚拟函数将项的外部边界定义为一个矩形;所有绘制必须限制在项的边界矩形内。QGraphicsView使用它来确定项是否需要重新绘制。
虽然项目的形状可以是任意的,但是边界矩形始终是矩形的,并且不受项目转换的影响。
如果要更改项的边框,必须首先调用PrepareGeteryChange()。这将通知场景即将发生的更改,以便它可以更新其项几何索引;否则,场景将不知道项的新几何,并且结果未定义(通常,渲染工件留在视图中)。
重新实现这个函数,让QGraphicsView确定需要重新绘制小部件的哪些部分(如果有的话)。
注意:对于绘制轮廓/笔划的形状,在边界矩形中包含一半的笔宽是很重要的。但是,不需要补偿抗锯齿。
示例:
QRegion QGraphicsItem::boundingRegion(const QTransform &itemToDeviceTransform) const
返回此项的边界区域。返回区域的坐标空间取决于itemToDeviceTransform。如果将标识QTransform作为参数传递,则此函数将返回局部坐标区域。
边界区域描述了项目视觉内容的粗略轮廓。虽然计算起来很昂贵,但它也比boundingrect()更精确,并且有助于避免在项目更新时不必要的重新绘制。这对于薄项目(如直线或简单多边形)尤其有效。可以通过调用 setBoundingRegionGranularity()来调整边界区域的粒度。默认粒度为0;其中项的边界区域与其边界矩形相同。
itemTodeviceTransform是从项坐标到设备坐标的转换。如果希望此函数返回场景坐标中的QRegion,可以将sceneTransform()作为参数传递。
这个功能是在QT 4.4引入的。
[virtual protected] void QGraphicsItem::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
对于事件事件,可以重新实现此事件处理程序,以接收此项目的拖曳输入事件。当光标进入项目区域时,将生成“拖动输入”事件。
通过接受事件(即,通过调用qEvent:::accept()),项目将接受放置事件,除了接收拖动移动和拖动离开。否则,事件将被忽略并传播到下面的项。如果该事件被接受,则在控件返回事件循环之前,该项将收到一个拖动移动事件。
DragEnterEvent的常见实现根据事件中关联的MIME数据接受或忽略事件。例子:
[virtual protected] void QGraphicsItem::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
对于事件事件,此事件处理程序可以重新实现以接收此项目的拖动移动事件。当光标在项目区域内移动时,会生成拖动移动事件。大多数情况下,不需要重新实现此函数;它用于指示只有项目的部分可以接受放置。
在事件上调用QEvent::Ignore()或QEvent::Accept()将切换该项是否接受从事件中删除的位置。默认情况下,接受事件,表示项目允许在指定位置放置。
默认情况下,项目不接收拖放事件;若要启用此功能,请调用setAcceptDrops(true)。
默认实现什么也不做。
[virtual protected] void QGraphicsItem::dropEvent(QGraphicsSceneDragDropEvent *event)
对于事件事件,可以重新实现此事件处理程序以接收此项的放置事件。只有在最后一个拖动移动事件被接受时,项才能接收放置事件。
对事件调用QEvent::Ignore()或QEvent::Accept()无效。
默认情况下,项不接收拖放事件;若要启用此功能,请调用setAcceptDrops(true)。
默认实现什么也不做。
[virtual protected] void QGraphicsItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
对于事件事件,可以重新实现此事件处理程序以接收此项目的鼠标双击事件。
当双击某个项目时,该项目将首先收到一个鼠标按下事件,然后是一个释放事件(即单击),然后是一个双击事件,最后是一个释放事件。
对事件调用QEvent::Ignore()或QEvent::Accept()无效。
默认实现调用mousePressEvent()。如果要在重新实现此函数时保留基本实现,请在重新实现中调用QGraphicsItem:: MouseDoubleClickevent()。
请注意,如果一个项目既不可选择也不可移动,它将不会接收双击事件(在这种情况下,鼠标单击将被忽略,并停止生成双击)。
[virtual] QPainterPath QGraphicsItem::shape() const
返回此项的形状作为本地坐标中的QPainterPath。该形状用于许多事情,包括碰撞检测、命中测试和QGraphicsScene::items()函数。
默认的实现调用boundingRect()返回一个简单的矩形,但是子类可以重新实现这个函数来为非矩形项返回一个更精确的形状。例如,圆形项目可以选择返回椭圆形状以更好地检测碰撞。例如:
形状的轮廓可以根据绘图时使用的笔的宽度和样式而变化。如果要将此轮廓包含在项的形状中,可以使用QPainterPathStroker从笔划创建形状。
此函数由contains()和collipswithpath()的默认实现调用。