handle
handle

Reputation: 6299

Why could a custom QGraphicsItem cause the scene or view to move unless boundingRect is empty?

My Qt 4.8.1 C++ class to draw a crosshair subclasses QGraphicsItem and implements its paint() and boundingRect() methods. The paint() method consists of two drawLine() and one drawText() calls. The origin is centered. boundingRect() also respects these coordinates (-,-,+,+) and also the half-pen-width in each direction.

When creating, moving (or positioning) and then adding the object to the (visible) scene, the view shifts a few pixels so that the scene contents move slightly to the bottom right. If the boundingRect() returns an empty QRectF(), this shift does not happen.

itemChange() is called numerous times but does not seem to provide a clue (in my eyes):

Crosshair::Crosshair being created, id= "1"  at  QPointF(63.6, 88.487) scenePos= QPointF(33.6, 58.487) 
Crosshair::Crosshair position QPointF(63.6, 88.487) 
Crosshair::itemChange  ItemFlagsChange QVariant(uint, 1) 
Crosshair::itemChange  ItemFlagsHaveChanged QVariant(uint, 1) 
Crosshair::itemChange  ItemFlagsChange QVariant(uint, 33) 
Crosshair::itemChange  ItemFlagsHaveChanged QVariant(uint, 33) 
Crosshair::itemChange  ItemFlagsChange QVariant(uint, 2081) 
Crosshair::itemChange  ItemFlagsHaveChanged QVariant(uint, 2081) 
[...]
Crosshair::itemChange  ItemPositionChange QVariant(QPointF, QPointF(63.6, 88.487) ) 
Crosshair::itemChange  ItemPositionHasChanged QVariant(QPointF, QPointF(63.6, 88.487) ) 
Crosshair::itemChange  ItemSceneChange QVariant(QGraphicsScene*, ) 
Crosshair::itemChange  ItemSceneHasChanged QVariant(QGraphicsScene*, ) 
[...]
Crosshair::boundingRect  20 3 QRectF(-11.5,-11.5 21.5x21.5) 
Crosshair::boundingRect  20 3 QRectF(-11.5,-11.5 21.5x21.5) 
Crosshair::itemChange  ItemVisibleChange QVariant(bool, true) 
Crosshair::itemChange  ItemVisibleHasChanged QVariant(bool, true) 
Crosshair::boundingRect  20 3 QRectF(-11.5,-11.5 21.5x21.5) 
Crosshair::boundingRect  20 3 QRectF(-11.5,-11.5 21.5x21.5) 
...

I might be able to create a minimal example tomorrow, but maybe someone is able to guess my mistake even from this general description of the problem. Alternatively, hints towards further debugging are of course very welcome!


Edit Thanks Riateche for the advice. Some further debug outputs reveal that the sceneRect() indeed is changed, it expands exactly the size of the Crosshair's bounding rect, e.g. from QRectF(0,0 1680x636) to QRectF(-11.5,-11.5 1691.5x647.5). The scene's itemsBoundingRect stays the same. Now I failed to mention previously that the view has been scaled/transformed with fitInView(scene()->itemsBoundingRect(), Qt::KeepAspectRatio); and noticed that the shift indeed does not occur when the scene is viewed at 100% (e.g. resetTransform()). But I still do not understand how/why the sceneRect() changes when I am adding an element within the boundingRect(). It must have to do with my custom implementation, because adding an ellipse does not cause a shift of the scene/view.

Ok, here we go: I also failed to mention that I use setFlag(QGraphicsItem::ItemIgnoresTransformations); so that the crosshair stays the same size regardless of scale. This apparently causes the boundingrect to "stay" at the scene's origin when added, and ignores the setPos().

To be continued ...

Upvotes: 1

Views: 2245

Answers (2)

handle
handle

Reputation: 6299

I was trying whether the problem could be worked around with

QRectF save = scene->sceneRect();
scene->addItem( myGraphicsItem );
scene->setSceneRect( save );

as Riateche suggests on a different note, and it works.

However, I unfortunately still don't fully understand the cause of this.

Here's what the documentation has to say about this flag:

QGraphicsItem::ItemIgnoresTransformations

0x20

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). This flag is useful for keeping text label items horizontal and unscaled, so they will still be readable if the view is transformed. When set, the item's view geometry and scene geometry will be maintained separately. You must call deviceTransform() to map coordinates and detect collisions in the view. By default, this flag is disabled. This flag was introduced in Qt 4.3. Note: With this flag set you can still scale the item itself, and that scale transformation will influence the item's children.

Here is some possibly related information:

Upvotes: 0

Pavel Strakhov
Pavel Strakhov

Reputation: 40492

A scene has a bounding rectangle which is by default the smallest rect containing all items. If you add something to a scene outside of this rectangle, the bounding rect will be expanded.

A view respects the scene's rect. If the scene's rect is smaller than the view's viewport size, the view's scrollbars get hidden and the scene contents appear in the center of the viewport. So, if you add an item, scene's bounding rect changes and all contents get shifted. If the viewport is smaller than bounding rect then scrollbars appear.

If you want to avoid this behavior, you can use QGraphicsView::setSceneRect or QGraphicsScene::setSceneRect to fix the rect position. Note that everything outside of the set rectangle will be unavailable and invisible in the view.

Also consider switching to QGraphicsPathItem (and QGraphicsScene::addPath). It can be used to draw crosshairs.

Upvotes: 4

Related Questions