Qt Graphics View框架概述
Qt 的 Graphics View 框架由三类核心对象构成:场景(QGraphicsScene
)、视图(QGraphicsView
)和图形项(QGraphicsItem)
。场景是一个不可见的二维绘图平面,用于管理大量图形项并负责事件分发和状态维护;视图是一个滚动窗口,用于可视化场景内容并处理输入事件;图形项是可绘制的几何元素,可响应鼠标/键盘等交互。在图形视图框架中,场景使用 BSP(二叉空间分区)树等索引算法来快速查找项,支持数百万级别的项查询;视图可关联一个或多个视口来观察同一场景(一个场景可设置多个视图实例)。
1. 基础入门与使用示例
1.1 设置 QGraphicsScene 和 QGraphicsView
使用 Graphics View 框架时,首先创建一个场景并向其中添加图形项,然后创建一个视图来显示该场景。例如:
QGraphicsScene scene;
scene.addText("Hello, world!");
QGraphicsView view(&scene);
view.show();
上述代码通过 scene.addText()
向场景中添加了一个文本项,随后构造了一个与场景关联的 QGraphicsView
并显示视图,注意 QGraphicsScene
本身不可见,它只管理项;要可视化场景,需要创建 QGraphicsView
小部件来承载场景。也可以调用诸如 scene.addRect()
, addEllipse()
, addPixmap()
等便利函数添加项,或者先 new
一个 QGraphicsItem
子类对象再调用 scene.addItem()
。默认情况下,新添加项的局部坐标中心为 (0,0)
,且其初始场景位置为 (0,0)。
1.2 添加 QGraphicsItem 并响应交互事件
除了使用内置项类(如 QGraphicsRectItem
、QGraphicsEllipseItem
、QGraphicsTextItem
等)外,还可以自定义 QGraphicsItem
子类,并在其中重写 boundingRect()
和 paint()
方法实现项的几何和绘制。在子类构造函数中,可以通过 setFlag()
启用交互特性,例如设置 ItemIsMovable
允许鼠标拖动移动项。示例:
class Node : public QGraphicsItem {
public:Node() {setFlag(ItemIsMovable); // 允许拖动setFlag(ItemIsSelectable); // 允许选中setCacheMode(DeviceCoordinateCache); // 启用缓存加速}QRectF boundingRect() const override { /* 返回项包围矩形 */ }void paint(QPainter *p, const QStyleOptionGraphicsItem*, QWidget*) override {/* 绘制项内容 */}
};
上例中,ItemIsMovable
标志会使项支持被单击并拖动,Qt 的内部机制会在鼠标按下、移动事件中自动更新项的位置。其他常用标志如 ItemIsSelectable
(支持框选或点击选择项)、ItemIsFocusable
(允许获取键盘焦点)等。还可以通过重写鼠标事件(mousePressEvent()
, mouseMoveEvent()
等)来定制交互逻辑。需要注意的是,视图会将鼠标和键盘事件转换为场景事件并传递给场景,再由场景分派给相应的项。例如,如果视图检测到用户点击了某项,框架会调用该项的 mousePressEvent()
,由项自行处理移动、选中、更新自身绘制等操作。
1.3 坐标系统和视图控制(缩放、滚动)
Graphics View 使用三种坐标系:项坐标、场景坐标和视图坐标。项的所有几何(比如 boundingRect()
、碰撞检测)均在项的局部坐标系中定义;项的位置 pos()
定义在其父坐标系(父项或场景坐标)中。视图坐标系以视图窗口左上角为原点,所有鼠标事件最初以视图坐标传入;可以使用 QGraphicsView::mapToScene()
和 mapFromScene()
在视图坐标和场景坐标之间映射点或矩形。视图本身提供仿射变换支持,通过 scale()
、rotate()
等函数可以对场景内容进行缩放、旋转。例如,调用 view.scale(2,2)
可将视图放大 2 倍,从而实现缩放效果。在视图中也可调用 centerOn()
或 ensureVisible()
快速滚动到场景中的指定点或区域。默认情况下,QGraphicsView
是一个带滚动条的窗口,当场景尺寸大于视图时会自动显示滚动条。如果要控制可视化区域,可通过 view.setSceneRect()
明确设置场景矩形,QGraphicsView
会相应调整滚动条范围。
2. 框架架构原理
2.1 协作关系(Scene-View-Item)
QGraphicsScene
充当图形项的容器,负责管理所有项的位置、层次和状态,并在需要时向视图报告需要重绘的区域。场景维护项的选择与焦点状态(通过 scene->selectedItems()
、scene->setFocusItem()
等接口),并在场景内容发生变化时发出 changed()
信号通知视图。QGraphicsView
将场景内容渲染到屏幕上,它可以与同一场景关联多个视口,用于从不同角度或大小观察相同数据。视图接收键鼠事件后会转换坐标并将事件转发给场景。QGraphicsItem
是场景中可见元素的基类,所有图形项都继承自它。框架提供了标准项(矩形、椭圆、文本、图像等),也可自定义项来实现特定形状。每个项都运行在自己的本地坐标系中,通过层叠的父子关系可以实现复杂组合结构。
2.2 事件传递机制与渲染流程
当视图捕获到鼠标或键盘事件时,它首先将事件坐标映射到场景坐标,并将事件发送给场景。QGraphicsScene
的 event()
函数再根据事件位置确定应该接收事件的顶层项,并调用该项的相应事件处理函数。例如,鼠标点击会最终调用项的 mousePressEvent()
,选中可交互项后场景会更新其选择状态。键盘事件默认送至当前具有焦点的项。场景还支持鼠标悬停、拖放、触摸等事件的传播(需调用 setAcceptHoverEvents(true)
、setAcceptTouchEvents(true)
才能接收)。
渲染方面,QGraphicsView
在绘制时调用每个可见项的 paint()
方法。绘制顺序先绘制父项再绘制子项,且可以通过 setZValue()
调整同级项的叠放顺序。若项未启用缓存(NoCache
),每次重绘都会调用 paint()
;启用 ItemCoordinateCache
或 DeviceCoordinateCache
后,项将先渲染到离屏缓存,再重复使用缓存。框架内部使用每个项的边界矩形(boundingRect()
)进行剔除和重绘区域计算:场景构建索引时以此为依据,视图重绘时剔除不在视区的项,并决定需要重新组合的重绘区域。
3. 性能优化技巧
在大规模场景下,绘制性能可能成为瓶颈。典型策略包括:索引调整、缓存优化和渲染流程优化。
索引策略:默认场景使用 BSP 树来加速项查找,但在项数极多且位置信息不经常查询时,更新 BSP 树的开销很大。可调用
scene->setItemIndexMethod(QGraphicsScene::NoIndex)
禁用索引,以减少更新成本(此时场景会逐项遍历处理)。同时,务必使用scene->setSceneRect()
设定场景边界,避免框架每次自动计算所有项的总体边界。缓存模式:对静态或仅平移不变形的项,应启用缓存模式以加快重绘。指出,
ItemCoordinateCache
在项的本地坐标系中缓存像素,而DeviceCoordinateCache
则在设备坐标系缓存。通常,对于只会平移但不旋转/缩放的项,DeviceCoordinateCache
性能最好,且保证最高质量。启用缓存后,当项不发生变换时会直接复用缓存,从而大幅降低paint()
调用次数。视图优化:通过合理设置视图的更新模式(
setViewportUpdateMode
)、渲染提示(setRenderHints
),可以减少不必要的重绘。例如,可禁用反锯齿(QPainter::Antialiasing
)和其它昂贵的状态来提高速度。若需要极致性能,可考虑使用硬件加速:QGraphicsView
允许设置 OpenGL 视口,调用view->setViewport(new QGLWidget)
即可让视图在 OpenGL 上下文中绘制。
4. 高级用法
自定义 QGraphicsItem 子类:编写自定义项时,应继承
QGraphicsItem
并实现其纯虚函数boundingRect()
(返回包围矩形)和paint()
(实际绘制)。例如,上述Node
类通过重写这两个函数定义了项形状和外观。通过在构造函数中调用setFlag()
设置ItemIsMovable
、ItemIsSelectable
等标志,可轻松启用拖拽和选择功能。如果需要项自身旋转或缩放,可在代码中调用item->setRotation(angle)
或setTransform()
(QGraphicsItem
自带变换功能)。嵌套视图与坐标变换:可以为同一场景创建多个视图实例,分别显示场景的不同部分或以不同变换观察场景。坐标变换方面,通过
QGraphicsView::mapToScene()
/mapFromScene()
可以在视图坐标与场景坐标间互相转换;通过QGraphicsItem::mapToScene()
,mapToItem()
等函数可以在项与项或项与场景之间映射坐标。这些映射函数便于实现复杂交互:例如,将鼠标事件在视图中定位到场景中对应项,或确定某项在另一个视图中的位置。动画与状态切换:Qt 提供了动画框架来驱动图形视图动画。在 Qt4 时可使用
QGraphicsItemAnimation
结合QTimeLine
为项设置路径动画;在 Qt5/6 中,更推荐使用QPropertyAnimation
(或QVariantAnimation
等)来为项的属性(如pos
、rotation
、opacity
等)创建动画。例如,可对一个QGraphicsObject
子类项的pos
属性应用QPropertyAnimation
,在一定时间内平滑地改变其位置,或者在点击时触发状态切换动画。这样结合状态标志或属性改变,能够实现丰富的可视效果。