Qt QGraphicsView 深度解析:从架构设计到源码内幕
一、QGraphicsView 框架Qt 最强大的 2D 图形引擎QGraphicsView 不是普通的控件它是 Qt 官方定义的Graphics View Framework一套完整的三层架构┌─────────────────────────────────────────────────────────────────────┐ │ QGraphicsView (视图层) │ │ 继承自 QAbstractScrollArea负责渲染、事件分发、视口变换 │ │ 可以有多个 View 指向同一个 Scene │ │ 提供 scale() / rotate() / translate() / setTransform() │ └────────────────────────────┬────────────────────────────────────────┘ │ paint() / event() │ ┌────────────────────────────▼────────────────────────────────────────┐ │ QGraphicsScene (场景层) │ │ 继承自 QObject负责管理所有 Item、事件转发、选中/焦点管理 │ │ 提供 BSP 树加速空间索引O(log n) 查找 │ │ 支持多层渲染Background / Item / Foreground │ └────────────────────────────┬────────────────────────────────────────┘ │ itemAt() / items() │ ┌────────────────────────────▼────────────────────────────────────────┐ │ QGraphicsItem (图元层抽象基类) │ │ 所有可见元素的基类支持嵌套父子关系 │ │ 内置子类QGraphicsRectItem / QGraphicsEllipseItem / │ │ QGraphicsPixmapItem / QGraphicsPathItem / │ │ QGraphicsTextItem / QGraphicsLineItem │ │ 自定义继承 QGraphicsItem重写 paint() boundingRect() │ └─────────────────────────────────────────────────────────────────────┘核心设计原则数据与视图分离一个 Scene 可以绑定多个 View同步显示不同视角事件链转发View → Scene → Item逐层分发可拦截空间索引加速Scene 内部用 BSP 树大规模 Item 下保持流畅坐标系统独立场景坐标逻辑、视图坐标屏幕、项坐标局部二、坐标系统三套坐标三种用途2.1 三种坐标系// 场景坐标Scene Coordinates // - Scene 的逻辑坐标系所有 Item 的统一参考系 // - Item::scenePos() / Item::sceneBoundingRect() 返回此坐标 // - 由 Scene 管理与 View 无关 // 视图坐标View Coordinates // - View 视口的物理像素坐标 // - 鼠标事件 QMouseEvent::pos() 是视图坐标 // - 滚动条偏移、缩放变换都会影响此坐标 // 项坐标Item Coordinates // - Item 的局部坐标系原点在 Item 的 boundingRect 左上角 // - Item::pos() 相对于父 Item 的坐标 // - Item::mapFromScene() / mapToScene() 进行转换2.2 坐标转换方法class QGraphicsView : public QAbstractScrollArea { public: // 视图坐标 → 场景坐标 QPointF mapToScene(const QPoint point) const; QRectF mapToScene(const QRect rect) const; // 场景坐标 → 视图坐标 QPoint mapFromScene(const QPointF point) const; QRect mapFromScene(const QRectF rect) const; // 获取当前变换矩阵 QTransform transform() const; void setTransform(const QTransform matrix, bool combine false); }; class QGraphicsItem { public: // 项坐标 → 场景坐标 QPointF mapToScene(const QPointF point) const; QRectF mapToScene(const QRectF rect) const; // 场景坐标 → 项坐标 QPointF mapFromScene(const QPointF point) const; QRectF mapFromScene(const QRectF rect) const; // 项坐标 → 父项坐标 QPointF mapToParent(const QPointF point) const; // 父项坐标 → 项坐标 QPointF mapFromParent(const QPointF point) const; // 设置项在场景中的位置 void setPos(const QPointF pos); void setPos(qreal x, qreal y); // 获取项在场景中的位置 QPointF pos() const; // 相对于父项 QPointF scenePos() const; // 相对于场景 };2.3 坐标转换源码// qtbase/src/widgets/graphicsview/qgraphicsview.cpp QPointF QGraphicsView::mapToScene(const QPoint point) const { // 获取视口变换矩阵的逆矩阵 QTransform viewTransform viewportTransform(); if (viewTransform.isIdentity()) return QPointF(point); // 应用逆变换视图坐标 → 场景坐标 return viewTransform.inverted().map(QPointF(point)); } QTransform QGraphicsView::viewportTransform() const { Q_D(const QGraphicsView); // 1. 基础变换平移滚动条偏移 QTransform transform; transform.translate(-d-horizontalScrollValue(), -d-horizontalScrollValue()); // 2. 应用缩放和旋转 transform * d-matrix; // 用户设置的 transform // 3. 视口中心偏移alignment if (d-alignment Qt::AlignLeft) ; // 无偏移 else if (d-alignment Qt::AlignRight) transform.translate(viewport()-width(), 0); else // AlignHCenter transform.translate(viewport()-width() / 2, 0); return transform; }三、QGraphicsView 渲染源码解析3.1 paintEvent 入口// qtbase/src/widgets/graphicsview/qgraphicsview.cpp void QGraphicsView::paintEvent(QPaintEvent *event) { Q_D(QGraphicsView); // 创建画家 QPainter painter(viewport()); painter.setRenderHints(d-renderHints); // 保存原始状态 painter.save(); // 1. 应用视口变换滚动、缩放、旋转 painter.setTransform(viewportTransform()); // 2. 设置裁剪区域只渲染视口可见部分 if (!d-clipPath.isEmpty()) painter.setClipPath(d-clipPath); // 3. 渲染场景 // 这会调用 QGraphicsScene::render() d-scene-render(painter, event-rect(), viewport()-rect()); // 4. 恢复状态 painter.restore(); // 5. 渲染前景层可选 if (d-foregroundBrush.style() ! Qt::NoBrush) { painter.setBrushOrigin(0, 0); painter.fillRect(event-rect(), d-foregroundBrush); } }3.2 QGraphicsScene::render 源码// qtbase/src/widgets/graphicsview/qgraphicsscene.cpp void QGraphicsScene::render(QPainter *painter, const QRectF target, const QRectF source, Qt::AspectRatioMode aspectRatioMode) { // 1. 计算源区域默认整个场景 QRectF sourceRect source.isNull() ? sceneRect() : source; if (!sourceRect.isValid()) return; // 2. 计算目标区域默认整个视口 QRectF targetRect target.isNull() ? QRectF(painter-transform().mapRect(QRectF(painter-viewport()))) : target; // 3. 获取可见项核心BSP 树索引 QListQGraphicsItem * items items(sourceRect, Qt::IntersectsItemBoundingRect, Qt::AscendingOrder); // 4. 按渲染层级分组 struct Layer { int level; QListQGraphicsItem * items; }; QListLayer layers; for (QGraphicsItem *item : items) { int level item-d_ptr-layer; // 渲染层级 auto it std::find_if(layers.begin(), layers.end(), [level](const Layer l) { return l.level level; }); if (it layers.end()) { layers.append({level, {item}}); } else { it-items.append(item); } } // 5. 按层级顺序渲染 std::sort(layers.begin(), layers.end(), [](const Layer a, const Layer b) { return a.level b.level; }); for (const Layer layer : layers) { for (QGraphicsItem *item : layer.items) { // 跳过不可见项 if (!item-isVisible()) continue; // 保存画家的状态 painter-save(); // 应用项的变换位置、旋转、缩放 item-d_ptr-paint(painter, option, nullptr); // 恢复状态 painter-restore(); } } }3.3 QGraphicsItem::paint 虚函数// qtbase/src/widgets/graphicsview/qgraphicsitem.cpp void QGraphicsItemPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_Q(QGraphicsItem); // 1. 应用项的变换 painter-translate(q-x(), q-y()); if (!transform.isIdentity()) painter-concatTransform(transform); // 2. 应用不透明度 if (opacity 1.0) { painter-setOpacity(painter-opacity() * opacity); } // 3. 可选应用缓动效果如阴影、模糊 if (effect) { effect-draw(painter); return; } // 4. 调用用户实现的 paint() q-paint(painter, option, widget); } // 用户必须重写的虚函数 virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget nullptr) 0;四、BSP 树空间索引加速的核心4.1 为什么需要空间索引当场景中有 10000 个项时QGraphicsScene::items(point)如果线性遍历复杂度是 O(n)每次鼠标移动都要调用会卡死。Qt 的解决方案BSP 树Binary Space Partitioning将场景递归分割查询复杂度降到 O(log n)。4.2 BSP 树结构// qtbase/src/widgets/graphicsview/qgraphicsscene_bsp.cpp class QGraphicsSceneBspTree { public: struct Node { enum Type { Leaf, Horizontal, Vertical }; Type type; int firstChild; int secondChild; QRectF rect; QListQGraphicsItem * items; // 仅叶子节点存储 }; void initialize(const QRectF rect, int depth); void insertItem(QGraphicsItem *item); void removeItem(QGraphicsItem *item); QListQGraphicsItem * items(const QRectF rect) const; QListQGraphicsItem * items(const QPointF point) const; private: QVectorNode m_nodes; int m_depth; // 默认 10 层 QRectF m_rect; };4.3 BSP 树插入与查询源码// qtbase/src/widgets/graphicsview/qgraphicsscene_bsp.cpp void QGraphicsSceneBspTree::insertItem(QGraphicsItem *item) { insertItem(item, 0); } void QGraphicsSceneBspTree::insertItem(QGraphicsItem *item, int index) { Node node m_nodes[index]; if (node.type Node::Leaf) { // 叶子节点直接插入 node.items.append(item); return; } // 分支节点根据项的位置决定进入哪个子树 QRectF itemRect item-sceneBoundingRect(); QRectF firstRect m_nodes[node.firstChild].rect; QRectF secondRect m_nodes[node.secondChild].rect; if (itemRect.left() firstRect.left() itemRect.right() firstRect.right()) { // 完全在第一子树 insertItem(item, node.firstChild); } else if (itemRect.left() secondRect.left() itemRect.right() secondRect.right()) { // 完全在第二子树 insertItem(item, node.secondChild); } else { // 跨越分割线存储在当前节点叶子节点不会走到这里 node.items.append(item); } } QListQGraphicsItem * QGraphicsSceneBspTree::items(const QPointF point) const { QListQGraphicsItem * result; climbTree(result, point, 0); return result; } void QGraphicsSceneBspTree::climbTree(QListQGraphicsItem * result, const QPointF point, int index) const { const Node node m_nodes[index]; // 检查当前节点的项跨越分割线的项 for (QGraphicsItem *item : node.items) { if (item-sceneBoundingRect().contains(point)) result.append(item); } if (node.type Node::Leaf) return; // 递归查询包含该点的子树 if (m_nodes[node.firstChild].rect.contains(point)) climbTree(result, point, node.firstChild); else if (m_nodes[node.secondChild].rect.contains(point)) climbTree(result, point, node.secondChild); }4.4 BSP 树深度配置// 场景初始化时设置 BSP 深度 scene new QGraphicsScene(this); scene-setSceneRect(-5000, -5000, 10000, 10000); scene-setItemIndexMethod(QGraphicsScene::BspTreeIndex); // 默认 scene-setBspTreeDepth(12); // 默认 10可根据项数量调整 // 估算最优深度 int optimalDepth static_castint(std::log2(itemCount) 1); scene-setBspTreeDepth(qBound(1, optimalDepth, 20)); // 如果项都在移动BSP 树频繁重建会有开销 // 可以切换为线性索引适合动态场景 scene-setItemIndexMethod(QGraphicsScene::NoIndex);五、事件处理链View → Scene → Item5.1 鼠标事件分发流程// 1. View 接收原始鼠标事件 void QGraphicsView::mousePressEvent(QMouseEvent *event) { Q_D(QGraphicsView); // 转换为场景坐标 QPointF scenePoint mapToScene(event-pos()); // 调用 Scene 的事件处理器 QGraphicsSceneMouseEvent sceneEvent(QEvent::GraphicsSceneMousePress); sceneEvent.setPos(scenePoint); sceneEvent.setButtonDownScenePos(event-button(), scenePoint); sceneEvent.setButton(event-button()); sceneEvent.setModifiers(event-modifiers()); d-scene-mousePressEvent(sceneEvent); // 如果事件被接受记录鼠标捕获项 if (sceneEvent.isAccepted()) d-mouseGrabberItem sceneEvent.widget(); } // 2. Scene 分发到具体 Item void QGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { Q_D(QGraphicsScene); // 使用 BSP 树快速查找鼠标下的项 QGraphicsItem *item itemAt(event-scenePos()); if (item item-isEnabled()) { // 转换为项坐标 QPointF itemPos item-mapFromScene(event-scenePos()); event-setPos(itemPos); // 分发到项 item-mousePressEvent(event); // 如果项接受了事件设置鼠标捕获 if (event-isAccepted()) { d-mouseGrabberItem item; return; } } // 没有项接受事件Scene 自己处理如框选 d-mouseGrabberItem nullptr; event-accept(); } // 3. Item 处理事件 void QGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (!isSelectable()) { event-ignore(); return; } // 默认行为选中 setSelected(true); event-accept(); }5.2 事件传播链中断// 自定义 Item拖拽移动 class DraggableItem : public QGraphicsRectItem { public: DraggableItem(const QRectF rect) : QGraphicsRectItem(rect) { setFlag(QGraphicsItem::ItemIsMovable, true); setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); setFlag(QGraphicsItem::ItemIsSelectable, true); } protected: // 不需要重写 mousePressEvent基类已经处理了选中 // 重写鼠标释放可添加吸附逻辑 void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override { // 吸附到网格 QPointF pos scenePos(); qreal gridSize 20.0; qreal x qRound(pos.x() / gridSize) * gridSize; qreal y qRound(pos.y() / gridSize) * gridSize; setPos(x, y); // 继续默认行为 QGraphicsRectItem::mouseReleaseEvent(event); } // 拦截变化通知限制移动范围 QVariant itemChange(GraphicsItemChange change, const QVariant value) override { if (change ItemPositionChange scene()) { QPointF newPos value.toPointF(); QRectF rect scene()-sceneRect(); if (!rect.contains(newPos)) { // 限制在场景范围内 newPos.setX(qBound(rect.left(), newPos.x(), rect.right())); newPos.setY(qBound(rect.top(), newPos.y(), rect.bottom())); return newPos; } } return QGraphicsRectItem::itemChange(change, value); } };六、自定义 QGraphicsItem从入门到精通6.1 必须实现的两个虚函数class MyItem : public QGraphicsItem { public: explicit MyItem(QGraphicsItem *parent nullptr) : QGraphicsItem(parent), m_size(100, 100) { // 启用悬停事件 setAcceptHoverEvents(true); // 启用缓存模式性能优化 setCacheMode(DeviceCoordinateCache); } // 1. 返回项的边界矩形必须实现 QRectF boundingRect() const override { // 边界矩形要稍大一点包含绘制笔刷的宽度 qreal penWidth 2.0; return QRectF(0, 0, m_size.width() penWidth, m_size.height() penWidth); } // 2. 绘制项必须实现 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget nullptr) override { Q_UNUSED(widget); // 提取悬停状态 bool hovered option-state QStyle::State_MouseOver; // 绘制背景 painter-setPen(QPen(Qt::black, 2)); painter-setBrush(hovered ? QColor(100, 150, 255) : QColor(70, 130, 230)); painter-drawRoundedRect(boundingRect().adjusted(1, 1, -1, -1), 5, 5); // 绘制文字 painter-setFont(QFont(Arial, 10)); painter-drawText(boundingRect(), Qt::AlignCenter, m_text); } // 3. 可选精确的形状用于碰撞检测 QPainterPath shape() const override { QPainterPath path; path.addRoundedRect(boundingRect().adjusted(1, 1, -1, -1), 5, 5); return path; } void setText(const QString text) { if (m_text ! text) { prepareGeometryChange(); // 重要通知 Scene 边界可能变化 m_text text; update(); // 触发重绘 } } private: QSizeF m_size; QString m_text; };6.2 带连接线的图元节点// 节点项 class NodeItem : public QGraphicsItem { public: explicit NodeItem(const QString name, QGraphicsItem *parent nullptr) : QGraphicsItem(parent), m_name(name) { setFlag(ItemIsMovable, true); setFlag(ItemSendsGeometryChanges, true); setFlag(ItemIsSelectable, true); setCacheMode(DeviceCoordinateCache); } QRectF boundingRect() const override { return QRectF(-50, -25, 100, 50); } void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override { Q_UNUSED(widget); // 选中高亮 if (option-state QStyle::State_Selected) { painter-setPen(QPen(Qt::blue, 2)); painter-setBrush(QColor(200, 220, 255)); } else { painter-setPen(QPen(Qt::black, 1)); painter-setBrush(Qt::white); } painter-drawRoundedRect(boundingRect(), 8, 8); painter-drawText(boundingRect(), Qt::AlignCenter, m_name); } QVariant itemChange(GraphicsItemChange change, const QVariant value) override { // 移动时通知连接线更新 if (change ItemPositionHasChanged) { for (EdgeItem *edge : m_edges) edge-adjust(); } return QGraphicsItem::itemChange(change, value); } void addEdge(EdgeItem *edge) { m_edges.append(edge); } QPointF connectionPoint() const { return pos(); } private: QString m_name; QListEdgeItem * m_edges; }; // 连接线项 class EdgeItem : public QGraphicsItem { public: EdgeItem(NodeItem *source, NodeItem *dest, QGraphicsItem *parent nullptr) : QGraphicsItem(parent), m_source(source), m_dest(dest) { setFlag(ItemIsSelectable, true); setZValue(-1); // 连接线在节点下方 m_source-addEdge(this); m_dest-addEdge(this); adjust(); } void adjust() { prepareGeometryChange(); m_sourcePoint m_source-connectionPoint(); m_destPoint m_dest-connectionPoint(); // 计算控制点贝塞尔曲线 m_path QPainterPath(); m_path.moveTo(m_sourcePoint); QPointF ctrl1, ctrl2; qreal dx m_destPoint.x() - m_sourcePoint.x(); if (qAbs(dx) 100) { ctrl1 m_sourcePoint QPointF(dx * 0.3, 0); ctrl2 m_destPoint - QPointF(dx * 0.3, 0); m_path.cubicTo(ctrl1, ctrl2, m_destPoint); } else { m_path.lineTo(m_destPoint); } update(); } QRectF boundingRect() const override { qreal penWidth 2; return m_path.boundingRect().adjusted(-penWidth, -penWidth, penWidth, penWidth); } void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override { Q_UNUSED(option); Q_UNUSED(widget); QPen pen(Qt::black, 2); if (isSelected()) pen.setColor(Qt::blue); painter-setPen(pen); painter-setBrush(Qt::NoBrush); painter-drawPath(m_path); // 绘制箭头 qreal arrowSize 10; QPointF end m_destPoint; QLineF line(m_sourcePoint, m_destPoint); qreal angle std::atan2(line.dy(), line.dx()); QPointF arrowP1 end - QPointF(std::cos(angle - M_PI / 3) * arrowSize, std::sin(angle - M_PI / 3) * arrowSize); QPointF arrowP2 end - QPointF(std::cos(angle M_PI / 3) * arrowSize, std::sin(angle M_PI / 3) * arrowSize); painter-setBrush(Qt::black); painter-drawPolygon(QPolygonF() end arrowP1 arrowP2); } private: NodeItem *m_source; NodeItem *m_dest; QPointF m_sourcePoint; QPointF m_destPoint; QPainterPath m_path; };七、缓存模式性能优化的关键7.1 三种缓存模式enum CacheMode { NoCache, // 无缓存每次 paint() 都重绘 ItemCoordinateCache, // 在项坐标系下缓存适合缩放 DeviceCoordinateCache // 在设备坐标系下缓存适合静态项 }; // 使用 item-setCacheMode(QGraphicsItem::DeviceCoordinateCache);7.2 缓存模式源码// qtbase/src/widgets/graphicsview/qgraphicsitem.cpp void QGraphicsItemPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_Q(QGraphicsItem); // 检查缓存模式 if (cacheMode ! NoCache) { // 尝试从缓存获取 QPixmap cachedPixmap; QPoint cacheKey calculateCacheKey(); if (QPixmapCache::find(cacheKey, cachedPixmap)) { // 缓存命中直接绘制 painter-drawPixmap(0, 0, cachedPixmap); return; } // 缓存未命中绘制到缓存 cachedPixmap QPixmap(boundingRect.size().toSize()); cachedPixmap.fill(Qt::transparent); QPainter cachePainter(cachedPixmap); q-paint(cachePainter, option, widget); QPixmapCache::insert(cacheKey, cachedPixmap); painter-drawPixmap(0, 0, cachedPixmap); return; } // 无缓存直接绘制 q-paint(painter, option, widget); }缓存选择原则模式适用场景缓存失效条件NoCache频繁变化、动态绘制无ItemCoordinateCache会旋转/缩放但形状不变update()调用、几何变化DeviceCoordinateCache完全静态如背景图视口缩放、update()八、性能优化清单8.1 场景优化// 1. 限制场景大小避免 BSP 树过大 scene-setSceneRect(-5000, -5000, 10000, 10000); // 2. 动态调整 BSP 深度 int itemCount scene-items().size(); int depth static_castint(std::log2(itemCount) 1); scene-setBspTreeDepth(qBound(1, depth, 20)); // 3. 大量移动项时禁用索引 scene-setItemIndexMethod(QGraphicsScene::NoIndex); // 动画结束后恢复 scene-setItemIndexMethod(QGraphicsScene::BspTreeIndex); // 4. 批量添加项减少 BSP 重建次数 scene-blockSignals(true); for (int i 0; i 10000; i) { scene-addItem(new MyItem()); } scene-blockSignals(false);8.2 视图优化// 1. 启用视口更新模式 view-setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); // 2. 使用 OpenGL 渲染大幅提升性能 QOpenGLWidget *glWidget new QOpenGLWidget; view-setViewport(glWidget); // 3. 关闭抗锯齿如果不需要 view-setRenderHints(QPainter::Antialiasing, false); // 4. 限制渲染区域 view-setOptimizationFlag(QGraphicsView::DontAdjustForAntialiasing, true); view-setOptimizationFlag(QGraphicsView::DontSavePainterState, true);8.3 项优化// 1. 设置合理的缓存模式 item-setCacheMode(QGraphicsItem::DeviceCoordinateCache); // 2. 精确的边界矩形避免不必要的重绘 QRectF boundingRect() const override { return QRectF(0, 0, 100, 100); // 精确值 } // 3. 使用 QPainterPath::setFillRule(Qt::WindingFill) // 减少自相交判断开销 // 4. 分层渲染静态项与动态项分开 staticLayer-setZValue(0); dynamicLayer-setZValue(10); // 5. 减少信号连接特别是 itemChange // 在批量操作时临时禁用信号 item-blockSignals(true); // ... 批量操作 item-blockSignals(false);九、常见陷阱与解决方案陷阱 1boundingRect 太小导致裁剪// 错误边界不包含笔刷宽度 QRectF boundingRect() const override { return QRectF(0, 0, 100, 100); } void paint(...) { painter-setPen(QPen(Qt::black, 5)); // 笔宽 5 painter-drawRect(0, 0, 100, 100); // 右下角被裁剪 } // 正确扩展边界 QRectF boundingRect() const override { qreal penWidth 5; return QRectF(-penWidth/2, -penWidth/2, 100 penWidth, 100 penWidth); }陷阱 2忘记 prepareGeometryChange// 错误直接改变几何属性 void setSize(const QSizeF size) { m_size size; // 没有 prepareGeometryChange update(); // 只触发重绘不更新 BSP 树 } // 正确先通知再更新 void setSize(const QSizeF size) { prepareGeometryChange(); // 必须通知 Scene 边界变化 m_size size; update(); }陷阱 3在 paint 中修改项的状态// 错误paint 中修改状态 void paint(...) { if (m_needsUpdate) { m_data recalculate(); // ❌ paint 中不应有副作用 m_needsUpdate false; } // ... 绘制 } // 正确状态更新在 paint 外部 void updateData() { m_data recalculate(); m_needsUpdate false; update(); // 触发重绘 }陷阱 4内存泄漏忘记设置父项或手动删除// 错误创建项但未设置父项或添加到场景 QGraphicsRectItem *item new QGraphicsRectItem(rect); // 无父项无人管理 // 正确设置父项或添加到场景 QGraphicsRectItem *item new QGraphicsRectItem(rect, parentItem); // 或 scene-addItem(item); // 场景会管理项的生命周期 // 正确手动管理 QGraphicsRectItem *item new QGraphicsRectItem(rect); item-setParentItem(parentItem); // 父项销毁时自动删除子项十、实战案例简易流程图编辑器// flowchart.h #include QGraphicsView #include QGraphicsScene #include QGraphicsItem #include QMenu class FlowChartScene : public QGraphicsScene { Q_OBJECT public: explicit FlowChartScene(QObject *parent nullptr); protected: void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; private slots: void addProcessNode(); void addDecisionNode(); void addTerminalNode(); private: QPointF m_contextMenuPos; }; class FlowChartView : public QGraphicsView { Q_OBJECT public: explicit FlowChartView(QWidget *parent nullptr); protected: void wheelEvent(QWheelEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; private: bool m_panning false; QPoint m_lastPos; qreal m_zoomFactor 1.0; }; // flowchart.cpp FlowChartScene::FlowChartScene(QObject *parent) : QGraphicsScene(parent) { setSceneRect(-2000, -2000, 4000, 4000); setItemIndexMethod(QGraphicsScene::BspTreeIndex); } void FlowChartScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { m_contextMenuPos event-scenePos(); QMenu menu; menu.addAction(添加处理节点, this, FlowChartScene::addProcessNode); menu.addAction(添加判断节点, this, FlowChartScene::addDecisionNode); menu.addAction(添加终止节点, this, FlowChartScene::addTerminalNode); menu.exec(event-screenPos()); } void FlowChartScene::addProcessNode() { ProcessNodeItem *node new ProcessNodeItem(); node-setPos(m_contextMenuPos); addItem(node); } FlowChartView::FlowChartView(QWidget *parent) : QGraphicsView(parent) { setRenderHint(QPainter::Antialiasing); setDragMode(QGraphicsView::RubberBandDrag); setOptimizationFlag(DontSavePainterState); setViewportUpdateMode(MinimalViewportUpdate); setTransformationAnchor(AnchorUnderMouse); } void FlowChartView::wheelEvent(QWheelEvent *event) { qreal factor 1.15; if (event-angleDelta().y() 0) scale(factor, factor); else scale(1.0 / factor, 1.0 / factor); m_zoomFactor transform().m11(); } void FlowChartView::mousePressEvent(QMouseEvent *event) { if (event-button() Qt::MiddleButton) { m_panning true; m_lastPos event-pos(); setCursor(Qt::ClosedHandCursor); event-accept(); return; } QGraphicsView::mousePressEvent(event); } void FlowChartView::mouseMoveEvent(QMouseEvent *event) { if (m_panning) { QScrollBar *hBar horizontalScrollBar(); QScrollBar *vBar verticalScrollBar(); QPoint delta event-pos() - m_lastPos; hBar-setValue(hBar-value() - delta.x()); vBar-setValue(vBar-value() - delta.y()); m_lastPos event-pos(); event-accept(); return; } QGraphicsView::mouseMoveEvent(event); } void FlowChartView::mouseReleaseEvent(QMouseEvent *event) { if (event-button() Qt::MiddleButton) { m_panning false; setCursor(Qt::ArrowCursor); event-accept(); return; } QGraphicsView::mouseReleaseEvent(event); } // 使用 int main(int argc, char *argv[]) { QApplication app(argc, argv); FlowChartScene *scene new FlowChartScene(); FlowChartView *view new FlowChartView(); view-setScene(scene); view-resize(800, 600); view-show(); return app.exec(); }十一、源码阅读路线图qtbase/src/widgets/graphicsview/ ├── qgraphicsview.h / .cpp ← 视图层渲染、事件入口、变换 ├── qgraphicsscene.h / .cpp ← 场景层项管理、BSP 树、事件分发 ├── qgraphicsitem.h / .cpp ← 项基类几何、绘制、事件处理 ├── qgraphicsscene_bsp.cpp ← BSP 树实现关键 ├── qgraphicswidget.cpp ← 支持布局的项用于复杂 UI └── qgraphicsproxywidget.cpp ← 嵌入 QWidget 的代理 qtbase/src/gui/painting/ └── qtransform.h / .cpp ← 变换矩阵实现关键断点// 1. QGraphicsView::paintEvent ← 渲染入口 // 2. QGraphicsScene::render ← BSP 树查询可见项 // 3. QGraphicsSceneBspTree::items ← 空间索引核心 // 4. QGraphicsItemPrivate::paint ← 缓存模式分支 // 5. QGraphicsScene::mousePressEvent ← 事件分发链 // 6. QGraphicsItem::itemChange ← 几何变化通知结语QGraphicsView 框架的设计精髓是三层解耦 空间索引 事件转发。View负责怎么画视口变换、渲染设置、用户交互Scene负责画什么项管理、空间索引、事件分发Item负责画得怎样自定义绘制、几何计算、事件响应理解了 BSP 树、坐标转换、事件链、缓存模式QGraphicsView 就不再是黑盒——每一个items(point)调用、每一次setPos()更新、每一场paint()绘制都是有迹可循的设计决策。注若有发现问题欢迎大家提出来纠正
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2507514.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!