You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
}

当前遇到的问题

  1. 拖拽鼠标时视图会跳变(显示滚动条的话,滚动条值会直接跳到最大值,不确定是坐标转换问题还是ensureVisible(sceneRect)导致);
  2. 将视图嵌入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无法居中修复

  1. 在CView构造函数中添加对齐设置,确保视图默认居中:
setAlignment(Qt::AlignCenter);
  1. 按钮点击槽函数中,使用场景原始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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 19:39:56