codeup
codeup

Reputation: 83

How to keep the size and position of QGraphicsItem when scaling the view?

I have some QGraphicsItems in the QGraphicsScene which should keep the same size and position when scaling. I've tried QGraphicsItem::ItemIgnoresTransformations but it turns out that the items get wrong positions. Below is a sample code:

I have subclassed QGraphicsView like this:

class Graphics : public QGraphicsView
{
public:
    Graphics();
    QGraphicsScene *scene;
    QGraphicsRectItem *rect;
    QGraphicsRectItem *rect2;

protected:
    void wheelEvent(QWheelEvent *event);
};

And in its constructor:

Graphics::Graphics()
{
    scene = new QGraphicsScene;
    rect = new QGraphicsRectItem(100,100,50,50);
    rect2 = new QGraphicsRectItem(-100,-100,50,50);
    scene->addLine(0,200,200,0);

    rect->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
    scene->addItem(rect);
    scene->addItem(rect2);

    setScene(scene);
    scene->addRect(scene->itemsBoundingRect());
}

The wheelEvent virtual function:

void Graphics::wheelEvent(QWheelEvent *event)
{
    if(event->delta() < 0)
        scale(1.0/2.0, 1.0/2.0);
    else
        scale(2, 2);
    scene->addRect(scene->itemsBoundingRect());

    qDebug() << rect->transform();
    qDebug() << rect->boundingRect();
    qDebug() << rect2->transform();
    qDebug() << rect2->boundingRect();
}

orginal view looks like this: 1

take the line as road and rect aside as a symbol. When zoomed out, the rect maintain its size but jumps out of the scene: 2

which should be that topleft of rect to middle of line. I'm also confused with debug info showing that the boundingRect and transform stays the same, which seems that nothing has changed! What causes the problem and is there any way to solve it? Could someone help? Thank you!

Upvotes: 5

Views: 5787

Answers (2)

TPhaster
TPhaster

Reputation: 63

The solution to this is even simpler.

QGraphicsItem::ItemIgnoresTransformations

The item ignores inherited transformations (i.e., its position is still anchored to its parent, but the parent or view rotation, zoom or shear transformations are ignored). [...]

And that's the key! Item ignores all transformations, but is still bound to its parent. So you need two items: a parent item that will keep the relative position (without any flags set) and a child item that will do the drawing (with QGraphicsItem::ItemIgnoresTransformations flag set) at parent's (0,0) point.

Here is some working code of a crosshair that have constant size and rotation, while keeping the relative position to its parent:

#include <QGraphicsItem>
#include <QPainter>

class CrossHair : public QGraphicsItem
{
private:
    class CrossHairImpl : public QGraphicsItem
    {
    public:
        CrossHairImpl (qreal len, QGraphicsItem *parent = nullptr)
            : QGraphicsItem(parent), m_len(len)
        {
            setFlag(QGraphicsItem::ItemIgnoresTransformations);
        }

        QRectF boundingRect (void) const override
        {
            return QRectF(-m_len, -m_len, m_len*2, m_len*2);
        }

        void paint (QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
        {
            painter->setPen(QPen(Qt::red, 1));
            painter->drawLine(0, -m_len, 0, m_len);
            painter->drawLine(-m_len, 0, m_len, 0);
        }

    private:
        qreal m_len;
    };

public:
    CrossHair (qreal x, qreal y, qreal len, QGraphicsItem *parent = nullptr)
        : QGraphicsItem(parent), m_impl(len, this)  // <-- IMPORTANT!!!
    {
        setPos(x, y);
    }

    QRectF boundingRect (void) const override
    {
        return QRectF();
    }

    void paint (QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override
    {
        // empty
    }

private:
    CrossHairImpl m_impl;
};

Upvotes: 2

codeup
codeup

Reputation: 83

Sorry for delay, now I've solved the problem myself.

I found QGraphicsItem::ItemIgnoresTransformations only works when the point you want stick to is at (0,0) in item's coordinate. You need also update boundingRect manually in this way. Nevertheless, the best solution I've found is subclass QGraphicsItem and set matrix in paint() according to world matrix. Below is my code .

QMatrix stableMatrix(const QMatrix &matrix, const QPointF &p)
{
    QMatrix newMatrix = matrix;

    qreal scaleX, scaleY;
    scaleX = newMatrix.m11();
    scaleY = newMatrix.m22();
    newMatrix.scale(1.0/scaleX, 1.0/scaleY);

    qreal offsetX, offsetY;
    offsetX = p.x()*(scaleX-1.0);
    offsetY = p.y()*(scaleY-1.0);
    newMatrix.translate(offsetX, offsetY);

    return newMatrix;
}

And the paint function:

void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                   QWidget *widget)
{
     QPointF p(left, top);
     painter->setMatrix(stableMatrix(painter->worldMatrix(), p));
     painter->drawRect(left, top, width, height);
}

The second argument of stableMatrix is sticked point, in my sample code it's top-left of the item. You can change it to your preference. It works really fine! Hope this post help :)

Upvotes: 2

Related Questions