Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QGraphicsView::itemAt() - How to correctly pick QGraphicsItem?
Forum Updated to NodeBB v4.3 + New Features

QGraphicsView::itemAt() - How to correctly pick QGraphicsItem?

Scheduled Pinned Locked Moved Solved General and Desktop
12 Posts 2 Posters 1.6k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • T Offline
    T Offline
    tomas.soltys
    wrote on last edited by
    #1

    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?

    1 Reply Last reply
    0
    • A Offline
      A Offline
      Asperamanca
      wrote on last edited by
      #2

      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

      T 1 Reply Last reply
      2
      • A Asperamanca

        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

        T Offline
        T Offline
        tomas.soltys
        wrote on last edited by
        #3

        @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);
        
        1 Reply Last reply
        0
        • A Offline
          A Offline
          Asperamanca
          wrote on last edited by
          #4

          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.

          T 1 Reply Last reply
          0
          • A Asperamanca

            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.

            T Offline
            T Offline
            tomas.soltys
            wrote on last edited by
            #5

            @Asperamanca

            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.

            A 1 Reply Last reply
            0
            • T tomas.soltys

              @Asperamanca

              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.

              A Offline
              A Offline
              Asperamanca
              wrote on last edited by
              #6

              @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"?

              T 1 Reply Last reply
              0
              • A Asperamanca

                @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"?

                T Offline
                T Offline
                tomas.soltys
                wrote on last edited by tomas.soltys
                #7

                @Asperamanca

                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.

                1 Reply Last reply
                0
                • A Offline
                  A Offline
                  Asperamanca
                  wrote on last edited by
                  #8

                  @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?

                  T 1 Reply Last reply
                  0
                  • A Asperamanca

                    @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?

                    T Offline
                    T Offline
                    tomas.soltys
                    wrote on last edited by
                    #9

                    @Asperamanca

                    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?

                    A 1 Reply Last reply
                    0
                    • T tomas.soltys

                      @Asperamanca

                      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?

                      A Offline
                      A Offline
                      Asperamanca
                      wrote on last edited by
                      #10

                      @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.

                      T 1 Reply Last reply
                      0
                      • A Asperamanca

                        @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.

                        T Offline
                        T Offline
                        tomas.soltys
                        wrote on last edited by tomas.soltys
                        #11

                        @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.

                        1 Reply Last reply
                        0
                        • A Offline
                          A Offline
                          Asperamanca
                          wrote on last edited by
                          #12

                          If possible, calculate the geometry before paint, e,g. when setting text and font. This will save you an extra paint cycle.

                          1 Reply Last reply
                          0

                          • Login

                          • Login or register to search.
                          • First post
                            Last post
                          0
                          • Categories
                          • Recent
                          • Tags
                          • Popular
                          • Users
                          • Groups
                          • Search
                          • Get Qt Extensions
                          • Unsolved