Qt Graphics View实现场景自由拖拽(不修改sceneRect)
需求与问题
需求:基于C++ Qt的Graphics View实现场景自由拖拽功能,不能修改sceneRect,也不能使用view->setDragMode(QGraphicsView::ScrollHandDrag),因为业务逻辑依赖sceneRect进行计算。
可复现代码
view.h
#include <QGraphicsView> #include <QMouseEvent> #include <QWheelEvent> #include <qscrollbar.h> class CView : public QGraphicsView { Q_OBJECT public: CView(QWidget* parent = nullptr) : QGraphicsView(parent) { setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setResizeAnchor(QGraphicsView::AnchorUnderMouse); } protected: void mousePressEvent(QMouseEvent* event) override { if (event->button() == Qt::LeftButton) { m_lastPos = event->pos(); } QGraphicsView::mousePressEvent(event); } void mouseMoveEvent(QMouseEvent* event) override { if (event->buttons() & Qt::LeftButton) { QPoint delta = event->pos() - m_lastPos; QRectF sceneRect = this->sceneRect(); sceneRect.translate(-delta); this->setSceneRect(sceneRect); ensureVisible(sceneRect); m_lastPos = event->pos(); } QGraphicsView::mouseMoveEvent(event); } void wheelEvent(QWheelEvent* event) override { QPoint scrollAmount = event->angleDelta(); double scaleFactor = 1.15; if (scrollAmount.y() > 0) { scale(scaleFactor, scaleFactor); } else if (scrollAmount.y() < 0) { scale(1.0 / scaleFactor, 1.0 / scaleFactor); } // QRectF sceneRect = this->sceneRect(); // QPointF viewCenter = mapToScene(viewport()->rect().center()); // sceneRect.moveCenter(viewCenter); // this->setSceneRect(sceneRect); event->accept(); } private: QPoint m_lastPos; };
scene.h
#include <QObject> #include <QGraphicsScene> class CScene : public QGraphicsScene { Q_OBJECT public: CScene() {} protected: void drawBackground(QPainter *painter, const QRectF &rect) override { QGraphicsScene::drawBackground(painter, rect); painter->fillRect(sceneRect(), Qt::black); } };
main.cpp
#include "View.h" #include "Scene.h" #include <QApplication> int main(int argc, char* argv[]) { QApplication a(argc, argv); CView* view = new CView; view->resize(600, 500); CScene* scene = new CScene; scene->setSceneRect(0, 0, 300, 200); view->setScene(scene); view->show(); return a.exec(); }
当前遇到的问题
- 拖拽鼠标时视图会跳变(显示滚动条的话,滚动条值会直接跳到最大值,不确定是坐标转换问题还是
ensureVisible(sceneRect)导致); - 将视图嵌入Widget并添加QPushButton后,点击按钮调用
view->fitInView(sceneRect, Qt::KeepAspectRatio),场景无法在视图中居中。
解决方案
问题1:拖拽跳变修复
原代码错误地修改了sceneRect,既违背需求又导致视图定位混乱。正确逻辑是通过调整滚动条位置实现拖拽,完全不修改sceneRect:
修改mouseMoveEvent,并补充鼠标释放时的光标恢复:
void mouseMoveEvent(QMouseEvent* event) override { if (event->buttons() & Qt::LeftButton) { QPoint delta = event->pos() - m_lastPos; // 反向调整滚动条,实现视图拖拽效果 horizontalScrollBar()->setValue(horizontalScrollBar()->value() - delta.x()); verticalScrollBar()->setValue(verticalScrollBar()->value() - delta.y()); m_lastPos = event->pos(); } QGraphicsView::mouseMoveEvent(event); } void mouseReleaseEvent(QMouseEvent* event) override { if (event->button() == Qt::LeftButton) { setCursor(Qt::ArrowCursor); } QGraphicsView::mouseReleaseEvent(event); }
同时在mousePressEvent中添加光标切换,优化拖拽手感:
void mousePressEvent(QMouseEvent* event) override { if (event->button() == Qt::LeftButton) { m_lastPos = event->pos(); setCursor(Qt::ClosedHandCursor); } QGraphicsView::mousePressEvent(event); }
问题2:fitInView无法居中修复
- 在CView构造函数中添加对齐设置,确保视图默认居中:
setAlignment(Qt::AlignCenter);
- 按钮点击槽函数中,使用场景原始sceneRect调用
fitInView,必要时强制居中:
void on_pushButton_clicked() { view->resetTransform(); // 重置缩放变换(如果有) // 使用场景的原始sceneRect,而非view的sceneRect view->fitInView(view->scene()->sceneRect(), Qt::KeepAspectRatio); view->centerOn(view->scene()->sceneRect().center()); // 强制居中 }
完整修正后的view.h
#include <QGraphicsView> #include <QMouseEvent> #include <QWheelEvent> #include <qscrollbar.h> class CView : public QGraphicsView { Q_OBJECT public: CView(QWidget* parent = nullptr) : QGraphicsView(parent) { setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setResizeAnchor(QGraphicsView::AnchorUnderMouse); setAlignment(Qt::AlignCenter); // 设置居中对齐 } protected: void mousePressEvent(QMouseEvent* event) override { if (event->button() == Qt::LeftButton) { m_lastPos = event->pos(); setCursor(Qt::ClosedHandCursor); } QGraphicsView::mousePressEvent(event); } void mouseMoveEvent(QMouseEvent* event) override { if (event->buttons() & Qt::LeftButton) { QPoint delta = event->pos() - m_lastPos; // 调整滚动条实现拖拽,不修改sceneRect horizontalScrollBar()->setValue(horizontalScrollBar()->value() - delta.x()); verticalScrollBar()->setValue(verticalScrollBar()->value() - delta.y()); m_lastPos = event->pos(); } QGraphicsView::mouseMoveEvent(event); } void mouseReleaseEvent(QMouseEvent* event) override { if (event->button() == Qt::LeftButton) { setCursor(Qt::ArrowCursor); } QGraphicsView::mouseReleaseEvent(event); } void wheelEvent(QWheelEvent* event) override { QPoint scrollAmount = event->angleDelta(); double scaleFactor = 1.15; if (scrollAmount.y() > 0) { scale(scaleFactor, scaleFactor); } else if (scrollAmount.y() < 0) { scale(1.0 / scaleFactor, 1.0 / scaleFactor); } event->accept(); } private: QPoint m_lastPos; };
关键说明
- 拖拽核心:通过滚动条调整实现视图移动,完全不触碰
sceneRect,满足业务依赖需求; - 滚动条逻辑:鼠标移动的delta反向作用于滚动条值,保证拖拽方向与视图移动方向一致;
- 居中逻辑:设置视图对齐方式为居中,调用
fitInView时使用场景原始sceneRect,避免视图变换干扰居中效果。
内容的提问来源于stack exchange,提问作者super chen
相关产品推荐
相关产品推荐

