Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Allow QGraphicsView to move outside scene



  • I have a derived class of QGraphicsView where I set the drag mode to ScrollHandDrag and also implement the zoom functionality:

    Header

    #ifndef CUSTOMGRAPHICSVIEW_H
    #define CUSTOMGRAPHICSVIEW_H
    
    #include <QGraphicsView>
    
    class CustomGraphicsView : public QGraphicsView
    {
      Q_OBJECT
    public:
      CustomGraphicsView(QWidget* parent = nullptr);
    
    protected:
      virtual void wheelEvent(QWheelEvent* event) override;
    };
    
    #endif  // CUSTOMGRAPHICSVIEW_H
    

    Implementation

    #include "customview.h"
    
    #include <QWheelEvent>
    
    CustomGraphicsView::CustomGraphicsView(QWidget* parent) : QGraphicsView(parent)
    {
      setScene(new QGraphicsScene);
      setDragMode(ScrollHandDrag);
    }
    
    void CustomGraphicsView::wheelEvent(QWheelEvent* event)
    {
      // if ctrl pressed, use original functionality
      if (event->modifiers() & Qt::ControlModifier)
        QGraphicsView::wheelEvent(event);
      // otherwise, do yours
      else
      {
        setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
        if (event->delta() > 0)
        {
          scale(1.1, 1.1);
        }
        else
        {
          scale(0.9, 0.9);
        }
      }
    }
    

    When I use this class in a program (see below), I can move around the scene and zoom in and out. However, when the image is bigger in one of the dimension than the viewport, but not in the other one (see attached image) I can only drag along the axis that coincides with the image being bigger than the. This, in the attached image, is vertical as it can be seen by the presence of the right-hand side scroll bar.

    My question is: is there a way to not restrict the movement? Can I set the scroll mode that allows me to move freely regardless of the scene being contained in the view? Is my only option to reimplement mouseMoveEvent?

    enter image description here

    Application

    #include <QApplication>
    #include <QGraphicsPixmapItem>
    #include "customview.h"
    
    int main(int argc, char** argv)
    {
      QApplication app(argc, argv);
      CustomGraphicsView cgv;
      QGraphicsPixmapItem* item = new QGraphicsPixmapItem(QPixmap::fromImage(QImage("clouds-country-daylight-371633.jpg")));
      cgv.scene()->addItem(item);
      cgv.show();
      return app.exec();
    }
    

    The image I used is this one.



  • After a careful read of the documentation, my conclusion is that it is not possible to move outside the scene. However, one can manually set the limits of the scene to something bigger than the actual scene. The easiest solution is to set a big enough scene at the beginning as suggested here. However, this is not dynamic and has limitations. I solved this issue by auto-computing the scene limits whenever the scene is updated. For that, I connect the QGraphicsScene::changed to a slot where the auto size of the scene is computed and I manually force the scene to be updated with the mouse move. The final class with the desired behavior is:

    Header

    #ifndef CUSTOMGRAPHICSVIEW_H
    #define CUSTOMGRAPHICSVIEW_H
    
    #include <QGraphicsView>
    
    class CustomGraphicsView : public QGraphicsView
    {
      Q_OBJECT
    public:
      CustomGraphicsView(QWidget* parent = nullptr);
    
    protected:
      virtual void wheelEvent(QWheelEvent* event) override;
      virtual void mouseMoveEvent(QMouseEvent* event) override;
      virtual void mousePressEvent(QMouseEvent* event) override;
      virtual void mouseReleaseEvent(QMouseEvent* event) override;
    
      void autocomputeSceneSize(const QList<QRectF>& region);
    };
    
    #endif  // CUSTOMGRAPHICSVIEW_H
    

    CPP

    #include "customview.h"
    
    #include <QWheelEvent>
    
    CustomGraphicsView::CustomGraphicsView(QWidget* parent) : QGraphicsView(parent)
    {
      // Set up new scene
      setScene(new QGraphicsScene);
    
      // Do not show scroll bars
      setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
      setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    
      // Connect scene update to autoresize
      connect(scene(), &QGraphicsScene::changed, this, &CustomGraphicsView::autocomputeSceneSize);
    }
    
    void CustomGraphicsView::wheelEvent(QWheelEvent* event)
    {
      // if ctrl pressed, use original functionality
      if (event->modifiers() & Qt::ControlModifier)
        QGraphicsView::wheelEvent(event);
      // Rotate scene
      else if (event->modifiers() & Qt::ShiftModifier)
      {
        if (event->delta() > 0)
        {
          rotate(1);
        }
        else
        {
          rotate(-1);
        }
      }
      // Zoom
      else
      {
        ViewportAnchor previous_anchor = transformationAnchor();
        setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
        if (event->delta() > 0)
        {
          scale(1.1, 1.1);
        }
        else
        {
          scale(0.9, 0.9);
        }
        setTransformationAnchor(previous_anchor);
      }
    }
    
    void CustomGraphicsView::mouseMoveEvent(QMouseEvent* event)
    {
      QGraphicsView::mouseMoveEvent(event);
      if (event->buttons() & Qt::LeftButton)
        // If we are moveing with the left button down, update the scene to trigger autocompute
        scene()->update(mapToScene(rect()).boundingRect());
    }
    
    void CustomGraphicsView::mousePressEvent(QMouseEvent* event)
    {
      if (event->buttons() & Qt::LeftButton)
        // Set drag mode when left button is pressed
        setDragMode(QGraphicsView::ScrollHandDrag);
      QGraphicsView::mousePressEvent(event);
    }
    
    void CustomGraphicsView::mouseReleaseEvent(QMouseEvent* event)
    {
      if (dragMode() & QGraphicsView::ScrollHandDrag)
        // Unset drag mode when left button is released
        setDragMode(QGraphicsView::NoDrag);
      QGraphicsView::mouseReleaseEvent(event);
    }
    
    void CustomGraphicsView::autocomputeSceneSize(const QList<QRectF>& region)
    {
      Q_UNUSED(region);
    
      // Widget viewport recangle
      QRectF widget_rect_in_scene(mapToScene(-20, -20), mapToScene(rect().bottomRight() + QPoint(20, 20)));
    
      // Copy the new size from the old one
      QPointF new_top_left(sceneRect().topLeft());
      QPointF new_bottom_right(sceneRect().bottomRight());
    
      // Check that the scene has a bigger limit in the top side
      if (sceneRect().top() > widget_rect_in_scene.top())
        new_top_left.setY(widget_rect_in_scene.top());
    
      // Check that the scene has a bigger limit in the bottom side
      if (sceneRect().bottom() < widget_rect_in_scene.bottom())
        new_bottom_right.setY(widget_rect_in_scene.bottom());
    
      // Check that the scene has a bigger limit in the left side
      if (sceneRect().left() > widget_rect_in_scene.left())
        new_top_left.setX(widget_rect_in_scene.left());
    
      // Check that the scene has a bigger limit in the right side
      if (sceneRect().right() < widget_rect_in_scene.right())
        new_bottom_right.setX(widget_rect_in_scene.right());
    
      // Set new scene size
      setSceneRect(QRectF(new_top_left, new_bottom_right));
    }
    

Log in to reply