QGraphicsView::itemAt() - How to correctly pick QGraphicsItem?
-
How does QGraphicsView::itemAt() works?
I have a class DiagramView derived from QGraphicsView and following method:
void DiagramView::mousePressEvent(QMouseEvent *mouseEvent) { if (QGraphicsItem *item = this->itemAt(mouseEvent->pos())) { qDebug() << "Hit " << mouseEvent->pos() << mapToScene(mouseEvent->pos()) << item->boundingRect() << item; } else { qDebug() << "Miss" << mouseEvent->pos() << mapToScene(mouseEvent->pos()); } }Here is the output from above when I performed 4 mouse clicks going from top to bottom.:
Miss QPoint(672,424) QPointF(259,-2) Hit QPoint(672,426) QPointF(259,0) QRectF(0,0 130x54) QGraphicsItem(0x560853ca75e0, pos=250,0) Hit QPoint(672,443) QPointF(259,17) QRectF(0,0 130x54) QGraphicsItem(0x560853ca75e0, pos=250,0) Miss QPoint(672,451) QPointF(259,25)Note that my item is a rectangle of size 130x54 and same is its bounding rectangle. Somehow I am not able to score hit in whole rectangle but only in small part of it. Last click QPointF(259,17) was definitely inside the bounding rectangle QRectF(0,0 130x54) with pos=250,0
Same happens in x direction.What am I missing?
-
Seems you are mixing coordinate systems.
boundingRect is in 'item' coordinates. That doesn't tell you the item's position within the scene.
Map the boundingRect to scene coordinates using QGraphicsItem::mapToScene, then you can compare it the the scene coordinates of your mouse position -
Seems you are mixing coordinate systems.
boundingRect is in 'item' coordinates. That doesn't tell you the item's position within the scene.
Map the boundingRect to scene coordinates using QGraphicsItem::mapToScene, then you can compare it the the scene coordinates of your mouse position@Asperamanca thanks for your reply. When I map to scene it gets only more obvious that something is oddly wrong.
void DiagramView::mousePressEvent(QMouseEvent *mouseEvent) { if (QGraphicsItem *item = this->itemAt(mouseEvent->pos())) { qDebug() << "Hit " << mouseEvent->pos() << mapToScene(mouseEvent->pos()) << item->mapToScene(item->boundingRect()); } else { qDebug() << "Miss" << mouseEvent->pos() << mapToScene(mouseEvent->pos()); } }Output:
Miss QPoint(680,423) QPointF(267,-3) Hit QPoint(679,426) QPointF(266,0) QPolygonF(QPointF(250,0)QPointF(380,0)QPointF(380,54)QPointF(250,54)QPointF(250,0)) Hit QPoint(679,430) QPointF(266,4) QPolygonF(QPointF(250,0)QPointF(380,0)QPointF(380,54)QPointF(250,54)QPointF(250,0)) Hit QPoint(675,450) QPointF(262,24) QPolygonF(QPointF(250,0)QPointF(380,0)QPointF(380,54)QPointF(250,54)QPointF(250,0)) Miss QPoint(675,451) QPointF(262,25)I actually happens for any item at any position exactly same. As if the view/scene was able to "see" only part of the item, as if it was down-scaled scaled and keeping its origin.
I paint the rectangle in following way:
painter->drawRoundedRect(QRectF(0,0,130,54),5,5); setPos(250,0); -
How do you set the boundingRect?
Does the item have a shape as well?itemAt primarily uses shape(), boundingRect() is only a fallback if shape is not set.
-
How do you set the boundingRect?
Does the item have a shape as well?itemAt primarily uses shape(), boundingRect() is only a fallback if shape is not set.
QRectF GraphicsNodeItem::boundingRect() const { return QRectF(0,0,130,54).normalized(); } QPainterPath GraphicsNodeItem::shape() const { QPainterPath path; path.addRect(this->boundingRect()); return path; }Even if I set bounding rectangle to some ridiculous size itemAt() will always return item only if clicked within small rectangle originating in the top left corner of the bounding rectangle.
-
QRectF GraphicsNodeItem::boundingRect() const { return QRectF(0,0,130,54).normalized(); } QPainterPath GraphicsNodeItem::shape() const { QPainterPath path; path.addRect(this->boundingRect()); return path; }Even if I set bounding rectangle to some ridiculous size itemAt() will always return item only if clicked within small rectangle originating in the top left corner of the bounding rectangle.
@tomas-soltys
That looks weird. What version of Qt are you using?
Can you try the behavior with a stock QGraphicsRectItem, using the setRect function to set your "boundingRect"? -
@tomas-soltys
That looks weird. What version of Qt are you using?
Can you try the behavior with a stock QGraphicsRectItem, using the setRect function to set your "boundingRect"?QGraphicsRectItem works nicely.
QGraphicsRectItem *rectItem = new QGraphicsRectItem; rectItem->setRect(0, 0, 130, 54); rectItem->setPos(250,0);Qt version is 6.3.1 and I am experiencing this behavior on Linux, MacOS and Windows.
-
@tomas-soltys said in QGraphicsView::itemAt() - How to correctly pick QGraphicsItem?:
GraphicsNodeItem
Your code for boundingRect and shape is pretty much identical to the code of QGraphicsRectItem (ignoring the extras that are needed when using a pen as border).
Is your GraphicsNodeItem a base class? Could boundingRect() be overloaded and thus behave differently? -
@tomas-soltys said in QGraphicsView::itemAt() - How to correctly pick QGraphicsItem?:
GraphicsNodeItem
Your code for boundingRect and shape is pretty much identical to the code of QGraphicsRectItem (ignoring the extras that are needed when using a pen as border).
Is your GraphicsNodeItem a base class? Could boundingRect() be overloaded and thus behave differently?Yes it is a base class.
class GraphicsNodeItem : public QGraphicsItem { public: GraphicsNodeItem(QGraphicsItem *parent = nullptr); QRectF boundingRect() const override; QPainterPath shape() const override; }; class GraphicsPersonItem : public GraphicsNodeItem { public: explicit GraphicsPersonItem(const FPerson &person, GraphicsNodeItem *parent = nullptr); void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; };I have noticed that GraphicsNodeItem::shape() is called only when successful hit, whereas GraphicsNodeItem::boundingRect() is being called constantly. Is this correct behavior?
-
Yes it is a base class.
class GraphicsNodeItem : public QGraphicsItem { public: GraphicsNodeItem(QGraphicsItem *parent = nullptr); QRectF boundingRect() const override; QPainterPath shape() const override; }; class GraphicsPersonItem : public GraphicsNodeItem { public: explicit GraphicsPersonItem(const FPerson &person, GraphicsNodeItem *parent = nullptr); void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; };I have noticed that GraphicsNodeItem::shape() is called only when successful hit, whereas GraphicsNodeItem::boundingRect() is being called constantly. Is this correct behavior?
@tomas-soltys
I think boundingRect is called first, if that indicates a hit, the shape is checked.So if you click far outside of a circle, the click is outside of it's boundingRect and shape() is not called.
If you click into the center of the circle, boundingRect indicates a hit, and shape confirms it.
If you click at the corner of the circle, the click may be inside the boundingRect, but outside of the shape. -
@tomas-soltys
I think boundingRect is called first, if that indicates a hit, the shape is checked.So if you click far outside of a circle, the click is outside of it's boundingRect and shape() is not called.
If you click into the center of the circle, boundingRect indicates a hit, and shape confirms it.
If you click at the corner of the circle, the click may be inside the boundingRect, but outside of the shape.@Asperamanca
I have found working solution. Since size of my item depends on QFontMetrics used by QPainter I am setting final dimensions inside GraphicsPersonItem::paint() method. Once I added this->prepareGeometryChange(); at the beginning my picking started to work correctly. -
If possible, calculate the geometry before paint, e,g. when setting text and font. This will save you an extra paint cycle.