Search code examples
c++qtqgraphicswidget

How do I scale/resize a QGraphicsWidget based on the QGraphicScene?


I would like my QGraphicsWidget to scale its size based on the size of the scene. The QGraphicsWidget I have currently is a fixed size depending on the return value of sizeHint (QGraphicsWidget is always 200 x 200). Attached below is minimal example:

MainWindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include <QGraphicsScene>
#include <QGraphicsView>

#include "RectangleWidget.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private:
    Ui::MainWindow *ui;
    QGraphicsScene * m_scene;
    QGraphicsView * m_view;
    RectangleWidget * m_rectangleWidget;
};

#endif // MAINWINDOW_H

MainWindow.cpp:

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

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

    m_scene = new QGraphicsScene(this);

    m_view = new QGraphicsView(m_scene, this);
    m_view->setAlignment(Qt::AlignLeft | Qt::AlignTop);

    m_rectangleWidget = new RectangleWidget();

    m_scene->addItem(m_rectangleWidget);

    setCentralWidget(m_view);
}

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

RectangleWidget.h:

#ifndef RECTANGLEWIDGET_H
#define RECTANGLEWIDGET_H

#include <QGraphicsLinearLayout>
#include <QGraphicsWidget>


class RectangleWidget: public QGraphicsWidget
{
public:
    RectangleWidget(QGraphicsWidget* parent = nullptr);

    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;

    void setGeometry(const QRectF &geom) override;
    QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const override;
};

#endif // RECTANGLEWIDGET_H

RectangleWidget.cpp:

#include "rectanglewidget.h"
#include <QPainter>

RectangleWidget::RectangleWidget(QGraphicsWidget* parent)
{

}

void RectangleWidget::paint(QPainter *painter,
    const QStyleOptionGraphicsItem *option, QWidget *widget /*= 0*/)
{
    Q_UNUSED(widget);
    Q_UNUSED(option);

    //Draw border
    painter->drawRoundedRect(boundingRect(), 0.0, 0.0);

}

QRectF RectangleWidget::boundingRect() const
{

    return QRectF(QPointF(0,0), geometry().size());
}

void RectangleWidget::setGeometry(const QRectF &geom)
{
    prepareGeometryChange();
    QGraphicsLayoutItem::setGeometry(geom);
    setPos(geom.topLeft());
}

QSizeF RectangleWidget::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
{
    switch (which) {
    case Qt::MinimumSize:
        return QSizeF(200, 200);

    default:
        break;
    }
    return constraint;
}

Window small

enter image description here

Any help on this would be appreciated.


Solution

  • Background

    Your QGraphicsWidget have to be aware of two things:

    1. When it is added to a scene

    In order to do that you have to reimplement QGraphicsWidget::itemChange and look for a change of type QGraphicsItem::ItemSceneHasChanged.

    1. When the size of this scene changes

    This could be done by connecting a slot or a lambda function to the QGraphicsScene::sceneRectChanged signal.

    Solution

    Based on the given explanation, my solution would be the following:

    In RectangleWidget.h after QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const override; add:

    protected:
        QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
    
    private:
        QSize m_rectSize;
    

    In RectangleWidget.cpp change return QSizeF(200, 200); to return m_rectSize; and add at the end:

    QVariant RectangleWidget::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
    {
        if (change == ItemSceneHasChanged) {
            connect(value.value<QGraphicsScene *>(), &QGraphicsScene::sceneRectChanged, [this](const QRectF &rect){
                m_rectSize.setWidth(rect.size().width());
                m_rectSize.setHeight(rect.size().height());
            });
        }
    
        return QGraphicsWidget::itemChange(change, value);
    }
    

    Finally, in MainWindow.cpp after m_scene->addItem(m_rectangleWidget); set the sceneRect as follows:

    m_scene->setSceneRect(0, 0, 100, 400);
    

    Note: The rectangle will respond to the changes of the scene, not the view. So if you resize the window, the rectangle will not be resized.

    Adjustment

    This will make the rectangle exactly the same size as the scene. If you want a different ratio, say 0.5, instead of m_rectSize.setWidth(rect.size().width()); write m_rectSize.setWidth(rect.size().width() / 2);, respectively m_rectSize.setHeight(rect.size().height() / 2);.