c++qtqgraphicsviewqgraphicsscene

QGraphicsView::setSceneRect() scrolls the viewport instead of panning


I'm attempting to create a pannable QGraphicsScene in a resizeable QGraphicsView. When resizing, I need to keep the scene size fixed and instead use a transform on the view to ensure the scene always "covers" the viewport. The result is a nicely scaling scene that stays centered.

The issue is that when the view is smaller than the scene rect, panning the scene causes it to scroll instead. Even though I've disabled scrolling in every way I can think of, the calls to setSceneRect() seem to prioritize scrolling. This is limiting the movement along that axis before properly panning the scene.

Here is a gif of the issue. The rectangle is an item I added for visibility, it is not interactable:

To reiterate, the goal is to allow the scene rect to extend beyond the bounds of the viewport but not have any scrolling behavior possible. An example use case would be rendering a map view where the rendered tiles may extend beyond the viewport bounds and all panning is done logically instead of using scrollbars. I hope that makes sense.

I've created a Minimally Reproducible example:

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    MyGraphicsView *graphicsView = new MyGraphicsView();

    //These have no effect------

    //graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    //graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    graphicsView->setDragMode(QGraphicsView::NoDrag);
    graphicsView->setResizeAnchor(QGraphicsView::NoAnchor);

    //-------

    setCentralWidget(graphicsView);

    const qreal sceneWidth = 1920;
    const qreal sceneHeight = 1080;
    const QRectF sceneRect = QRectF(0, 0, sceneWidth, sceneHeight);

    QGraphicsScene *scene = new QGraphicsScene();
    scene->setSceneRect(sceneRect.translated(-sceneWidth / 2, -sceneHeight / 2));

    //Rectangle for visibility (same size as scene, doesnt move)
    QGraphicsRectItem* rectItem = new QGraphicsRectItem(sceneRect);
    rectItem->setPos(-sceneWidth / 2, -sceneHeight / 2);
    rectItem->setPen(QPen(Qt::black, 8));
    QLinearGradient gradient(sceneRect.topLeft(), sceneRect.bottomRight());
    gradient.setColorAt(0, Qt::blue);
    gradient.setColorAt(0.5, Qt::green);
    gradient.setColorAt(1, Qt::yellow);
    QBrush brush(gradient);
    rectItem->setBrush(brush);
    scene->addItem(rectItem);

    graphicsView->setScene(scene);
    graphicsView->setRenderHint(QPainter::Antialiasing);
    graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);

    // Center the scene in the view
    graphicsView->centerOn(0, 0);

    connect(graphicsView, &MyGraphicsView::resized, this, &MainWindow::resizeView);
}

void MainWindow::resizeView()
{
    MyGraphicsView *view = qobject_cast<MyGraphicsView*>(sender());
    
    // Get the new viewport size
    QSizeF viewportSize = view->viewport()->size();

    QSizeF sceneSize = view->sceneRect().size();

    // Calculate the scaling factor to fill the viewport with the scene
    qreal scaleX = viewportSize.width() / sceneSize.width();
    qreal scaleY = viewportSize.height() / sceneSize.height();
    qreal scale = qMax(scaleX, scaleY);

    // Set the new transformation matrix
    QTransform transform;
    transform.scale(scale, scale);
    view->setTransform(transform);

    // Center the scene on the center point
    QPointF centerPoint = view->sceneRect().center();
    view->centerOn(centerPoint);
}


MainWindow::~MainWindow()
{
    delete ui;
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QGraphicsView>
#include <QMouseEvent>
#include <QDebug>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT
public:
    explicit MyGraphicsView(QWidget *parent = nullptr): m_isPanning(false)
    {
    }
signals:
    void resized();
protected:

protected:
    void mousePressEvent(QMouseEvent* event) override
    {
        if (event->button() == Qt::LeftButton)
        {
            m_isPanning = true;
            m_lastPanPos = mapToScene(event->pos());
            setCursor(Qt::ClosedHandCursor);
        }

        QGraphicsView::mousePressEvent(event);
    }

    void mouseMoveEvent(QMouseEvent* event) override
    {
        if (m_isPanning)
        {
            QPointF delta = m_lastPanPos.toPoint() - mapToScene(event->pos());

            //The culprit
            setSceneRect(sceneRect().translated(delta.x(), delta.y()));

            m_lastPanPos = mapToScene(event->pos());
        }

        QGraphicsView::mouseMoveEvent(event);
    }

    void mouseReleaseEvent(QMouseEvent* event) override
    {
        if (event->button() == Qt::LeftButton && m_isPanning)
        {
            m_isPanning = false;
            setCursor(Qt::ArrowCursor);
        }

        QGraphicsView::mouseReleaseEvent(event);
    }

    void scrollContentsBy(int, int) override
    {
            // Don't call the base implementation, effectively preventing scrolling
    }

    void resizeEvent(QResizeEvent *event) override
    {
        QGraphicsView::resizeEvent(event);
        emit resized();
    }

private:
    bool m_isPanning;
    QPointF m_lastPanPos;
};


class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

public slots:
    void resizeView();

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

I've tried disabling scrollContentsBy, turning off scrollbar visibility, setting the view to NoDrag, but the issue seems directly tied to the functionality of setSceneRect(). If I comment that line out in the mouseMoveEvent function, there is no panning or scrollability at all, which is correct. As soon as I uncomment it, it starts scrolling again.

I'm stuck with Qt 5.6.2


Solution

  • Changing the drag mode to ScrollHandDrag seems to give the required behaviour (in qt 5.6.1 on linux)