简体   繁体   中英

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!

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 :)

The solution to this is even simpler.

QGraphicsItem::ItemIgnoresTransformations

The item ignores inherited transformations (ie, 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;
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM