Search code examples
qtmouseeventqgraphicsitemqgraphicsscene

QGraphicsRectItem member of a custom scene class added on mouse action gives error


I want to somehow paint a selection rectangle on scene, to show selected items (not he item bounding rectangle, but the bounding rectangle mapped to scene - and if multiple selection, the selection bounding rectangle).

I would like to try something like, on mouse press, to show the rectangle (and update based on current selection), and on mouse release, to hide it.

I am having trouble keeping the rectangle on the scene, and on mouse release it may be removing it, or maybe it was never there - and I get an error:

QGraphicsScene::removeItem: item 0x37828's scene (0x0) is different from this scene (0x1f57b68)

(The above error, and the fact that the item doesn't stay after mouse press, makes me think that it is not added properly but I don't understand why).

Here is a little sample code:

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>

class MyScene : public QGraphicsScene
{
public:
    MyScene(qreal x, qreal y, qreal w, qreal h) {
        setSceneRect(x, y, w, h);
        m_selectionRectangle = new QGraphicsRectItem(0,0,1,1);
        m_selectionRectangle->setBrush(Qt::magenta);
        m_selectionRectangle->setOpacity(0.2);
    }
    ~MyScene() {
        if(m_selectionRectangle)
            delete m_selectionRectangle;
    }
protected:
    virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) {
        QGraphicsScene::mousePressEvent(event);
        if(!selectedItems().isEmpty()) {
            QRectF selectionRect = QRectF();
            foreach(QGraphicsItem* item, selectedItems()) 
                selectionRect |= item->mapToScene(item->boundingRect()).boundingRect();
            m_selectionRectangle->setRect(selectionRect);
            addItem(m_selectionRectangle);
        }
    }
    virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
        QGraphicsScene::mouseReleaseEvent(event);
        removeItem(m_selectionRectangle);
    }
private:
    QGraphicsRectItem* m_selectionRectangle;
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MyScene* s = new MyScene(0, 0, 800, 600);
    QGraphicsView view(s);
    view.setDragMode(QGraphicsView::RubberBandDrag);
    view.show();
    QGraphicsRectItem* xxx = new QGraphicsRectItem(200, 200, 100, 100);
    QGraphicsEllipseItem* yyy = new QGraphicsEllipseItem(300, 300, 200, 100);
    s->addItem(xxx);
    s->addItem(yyy);
    xxx->setFlags(QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemIsFocusable|QGraphicsItem::ItemIsSelectable);
    yyy->setFlags(QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemIsFocusable|QGraphicsItem::ItemIsSelectable);
    return app.exec();
}

What is the meaning of that error, what am I doing wrong in adding the selection rectangle, and why doesn't it stay there - and how can I fix it ?


Solution

  • The meaning of the error is literal: you're passing an item to removeItem that is not a child item of the scene you're trying to remove it from. It is nonsense to remove an item that is not in the scene to start with.

    There is nothing that guarantees that the selection rectangle is on the scene when the mouse button is released, since there are paths through mousePressEvent that don't add the rectangle to the scene. I'm not even sure if you are guaranteed to get a press event preceding each release event at all.

    You have to only remove the rectangle if it's on the scene (and virtual is not needed, but Q_DECL_OVERRIDE is!):

    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE {
      QGraphicsScene::mouseReleaseEvent(event);
      if (m_selectionRectangle.scene()) removeItem(m_selectionRectangle);
    }
    

    Also, the destructor of your custom scene is unnecessary. Simply add the item by value:

    class MyScene : public QGraphicsScene
    {
        QGraphicsRectItem m_selectionRectangle;
    public:
        MyScene(qreal x, qreal y, qreal w, qreal h) :
            m_selectionRectangle(0, 0, 1 1)
        {
            setSceneRect(x, y, w, h);
            m_selectionRectangle.setBrush(Qt::magenta);
            m_selectionRectangle.setOpacity(0.2);
        }
    protected:
        void mousePressEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE {
            ...
        }
        ...
    };