Joe
Joe

Reputation: 6826

QGraphicsItem::itemChange notified for position change but not for size change

I've got a class derived from QGraphicsEllipseItem in which I need to know when its position or size changes in any way. I handle resizing with a mouse and calls to QGraphicsEllipse::setRect.

OK, so I dutifully overrode the itemChange() method in the class and then was careful to set the ItemSendsGeometryChanges flag after creating it

// Returns a human readable string for any GraphicsItemChange enum value

inline std::string EnumName(QGraphicsItem::GraphicsItemChange e);

// Simple test ellipse class

class MyEllipse : public QGraphicsEllipseItem
{
public:
    MyEllipse(int x, int y, int w, int h) : QGraphicsEllipseItem(x, y, w, h)
    {
        setFlags(
            QGraphicsItem::ItemIsSelectable 
            | QGraphicsItem::ItemIsMovable 
            | QGraphicsItem::ItemSendsGeometryChanges);
    }


    // QGraphicItem overrides
    virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override
    {
        std::stringstream oss;
        oss << "ItemChange " << EnumName(change) << std::endl;
        OutputDebugString(oss.str().c_str());
        return __super::itemChange(change, value);
    }
};

My main code creates one of these, adds it to the scene and then tries moving/resizing it.

And while I do always receive notifications after calling setPos() on the ellipse, I get NO notification after calling setRect(). I can use setRect to completely change the ellipse's geometry but my itemChange override is never called. Not with any flags.

Now obviously changing the item's rect is changing its geometry, so what am I missing?

Is there some other flag I should set? Some other way to change the size of the ellipse I should use? Some other notification virtual I can override?

Upvotes: 5

Views: 3496

Answers (1)

ilotXXI
ilotXXI

Reputation: 1085

The problem is that QGraphicsItem's position is not related with QGraphicsEllipseItem's rectangle. The first one is a position of the item relative to it's parent item or, if it is NULL, to it's scene. The last one is a rectangle relative to the item position where an ellipse should be drawn. The scene and QGraphicsItem's core don't know about any changes of it.

Let's take a look at this test:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QGraphicsScene scene;
    QGraphicsView view(&scene);
    QGraphicsEllipseItem item(10, 20, 30, 40);
    scene.addItem(&item);

    qDebug() << item.pos() << item.scenePos() << item.boundingRect();
    item.setRect(110, 120, 30, 40);
    qDebug() << item.pos() << item.scenePos() << item.boundingRect();

    view.resize(500, 500);
    view.show();

    return app.exec();
}

Output is:

QPointF(0,0) QPointF(0,0) QRectF(9.5,19.5 31x41)
QPointF(0,0) QPointF(0,0) QRectF(109.5,119.5 31x41)

Possible ways out:

  1. use setTransform. Transform matrix changes are tracked by standard QGraphicsitems, and itemChange will receive corresponding change. But I guess that non-ident matrices can decrease performance (didn't check).
  2. implement your own function like setRect in which you will track geometry changes manually.
  3. subclass QGraphicsItem, not QGraphicsEllipseItem. In this case you can prevent untrackable geometry changes as they are performed through your rules. It looks like this:

    class EllipseItem: public QGraphicsItem
    {
    public:
        // Here are constructors and a lot of standard things for
        // QGraphicsItem subclassing, see Qt Assistant.
        ...
    
        // This method is not related with QGraphicsEllipseItem at all.
        void setRect(const QRectF &newRect)
        {
            setPos(newRect.topLeft());
            _width = newRect.width();
            _height = newRect.height();
            update();
        }
    
        QRectF boundingRect() const override
        {
            return bRect();
        }
    
        void paint(QPainter * painter, const QStyleOptionGraphicsItem * option,
                   QWidget * widget = nullptr) override
        {
            painter->drawRect(bRect());
        }
    
    private:
        qreal   _width;
        qreal   _height;
    
        QRectF bRect() const
        {
            return QRectF(0, 0, _width, _height);
        }
    };
    

    You also should track item transformations and moves through QGraphicsItem::itemChange.

Upvotes: 3

Related Questions