Search code examples
qtcrashqgraphicsitem

How to access the contents of the data property of my custom QGraphicsItem after a cast


I have a custom class which inherits from QGraphicsRectItem (basically QGraphicsItem). At the beginning I had attributes as well as the corresponding getters and setters. Everything is fine until I get the items from the scene and try to call the setters (or getters): The properties no longer exist. So I made a cast on the elements retrieved from the scene and as expected .. the program crashes (because the elements no longer existed ... well I think). To solve the problem I used the data property offered by the base class (QGraphicsItem) I thought that since it is an already implemented property I should not have any problems even after a cast. Yet the problem persists. Why and how to fix it? There are others solutions in my mind but i want to understand why Here is a simplified code that represent my problem

MyItem.h

#ifndef MYITEM_H
#define MYITEM_H

#include <QGraphicsRectItem>
#include <QGraphicsTextItem>
#include <QObject>
#include <QDebug>

class MyItem :public QObject, public QGraphicsRectItem
{
    Q_OBJECT
public:
    enum {Type = UserType + 200};
    MyItem();
    QString getTestProperty() const;
    void setTestProperty(QString p_newValue);
private:
    QGraphicsTextItem* test_txt;
};

#endif // MYITEM_H

MyItem.cpp

#include "myitem.h"

MyItem::MyItem(): QGraphicsRectItem(0,0,100,100)
{
    setFlag(QGraphicsItem::ItemIsMovable);
    test_txt = new QGraphicsTextItem(this);
    setData(0,QVariant("hi there"));
}

void MyItem::setTestProperty(QString p_newValue) {
    setData(0,p_newValue);
    qDebug()<<data(0).toString()<<endl;
    test_txt->setPlainText(data(0).toString());
}
QString MyItem::getTestProperty() const {
    return  data(0).toString();
}

MainWindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QtDebug>

#include <myitem.h>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    QGraphicsScene* m_scene;
    QGraphicsView* m_view;
};
#endif // MAINWINDOW_H

MainWindow.cpp

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    setMinimumSize(800,600);
    m_scene = new QGraphicsScene;
    m_view = new QGraphicsView;
    m_view->setScene(m_scene);

    MyItem* item1 = new MyItem();
    m_scene->addItem(item1);
    setCentralWidget(m_view);
    item1->setTestProperty("hello there :)");

    MyItem* supposedPointerToItem1 = new MyItem;
    supposedPointerToItem1 = qgraphicsitem_cast<MyItem*> (m_scene->items().first());
    supposedPointerToItem1->setTestProperty("Another test that may fail");//Failed

}

MainWindow::~MainWindow()
{
}

And finally here is the screenshot of the results i get

The program ended suddenly

enter image description here


Solution

  • What can fail will fail.

    So it is better to check. QGraphicsScene::items() returns all the items so it will also return the QGraphicsTextItem and that is what happens in your case: the first item is not MyItem but the QGraphicsTextItem.

    On the other hand you are creating a second MyItem unnecessarily, also I do not see the need to use Q_OBJECT or inherit from QObject.

    #ifndef MYITEM_H
    #define MYITEM_H
    
    #include <QGraphicsRectItem>
    
    class QGraphicsTextItem;
    
    class MyItem :public QGraphicsRectItem
    {
    public:
        enum {Type = QGraphicsItem::UserType + 200};
        MyItem(QGraphicsItem *parent = nullptr);
        QString getTestProperty() const;
        void setTestProperty(const QString &p_newValue);
        int type() const override;
    private:
        QGraphicsTextItem* test_txt;
    };
    
    #endif // MYITEM_H
    
    #include "myitem.h"
    
    #include <QDebug>
    
    MyItem::MyItem(QGraphicsItem *parent): QGraphicsRectItem(0,0,100,100, parent)
    {
        setFlag(QGraphicsTextItem::ItemIsMovable);
        test_txt = new QGraphicsTextItem(this);
        setData(0,QVariant("hi there"));
    }
    
    void MyItem::setTestProperty(const QString & p_newValue) {
        setData(0,p_newValue);
        qDebug()<<data(0).toString();;
        test_txt->setPlainText(data(0).toString());
    }
    
    QString MyItem::getTestProperty() const {
        return  data(0).toString();
    }
    
    int MyItem::type() const
    {
        return Type;
    }
    
    #include "mainwindow.h"
    #include "myitem.h"
    
    #include <QGraphicsScene>
    #include <QGraphicsView>
    
    MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
    {
        setMinimumSize(800,600);
        m_scene = new QGraphicsScene;
        m_view = new QGraphicsView;
        m_view->setScene(m_scene);
    
        MyItem* item1 = new MyItem();
        m_scene->addItem(item1);
        setCentralWidget(m_view);
        item1->setTestProperty("hello there :)");
    
        QList<QGraphicsItem*> items = m_scene->items();
        for(QGraphicsItem *item : qAsConst(items)){
            if(MyItem* myitem = qgraphicsitem_cast<MyItem *>(item)){
                myitem->setTestProperty("Another test that may fail");
            }
        }
    }