Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Q_INVOKABLE from object used by QAbstractListModel



  • Hello!

    I want to display a single item and it's roles in the GUI from a QAbstractItemModel. In the manual I can only find how to display the whole model.

    The model emits dataChanged() in the setData() function. But the model has hundrets of items that are changed very often and very fast.(hundrets per second)

    I need a signal per item, but I can't get the item in the model to work when it's derived from Qobject

    Object in Model:

    #ifndef DATASOURCEOBJECT_H
    #define DATASOURCEOBJECT_H
    
    #include <QString>
    #include <QVariantMap>
    
    class DataSourceObject
    {
    public:
        DataSourceObject(const int &id=0, const QString &name="", const QString &displayname="", const double &value=0.0);
        DataSourceObject(const QJsonObject &obj);
        int id() const;
        void setId(int id);
    
        QString name() const;
        void setName(const QString &name);
    
        QString unit() const;
        void setUnit(const QString &unit);
    
        double value() const;
        void setValue(double value);
        QVariantMap toMap() const;
    
    private:
        int m_id;
        QString m_name;
        QString m_unit;
        double m_value;
    };
    
    #endif // DATASOURCEOBJECT_H
    Model Header:
    
    #ifndef DATASOURCEMODEL_H
    #define DATASOURCEMODEL_H
    
    #include "datasourceobject.h"
    #include <QAbstractListModel>
    
    class DataSourceModel : public QAbstractListModel
    {
        Q_OBJECT
    
    public:
        enum datasourceRoles {
            idRole = Qt::UserRole ,
            nameRole,
            unitRole,
            valueRole
        };
    
        explicit DataSourceModel(QObject *parent = nullptr);
        void addDataSourceObject(const DataSourceObject &dataSourceObject);
        Q_INVOKABLE QVariantMap get(int row) const;
    
        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
        Q_INVOKABLE QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
        bool setData(const QModelIndex &index, const QVariant &value,
                     int role = Qt::EditRole) override;
        Qt::ItemFlags flags(const QModelIndex& index) const override;
        QHash<int, QByteArray> roleNames() const override;
        //bool checkIndex(const QModelIndex &index) const;
    
    private:
        QList<DataSourceObject> m_DataSourceObjects;
    };
    
    
    
    #endif // DATASOURCEMODEL_H
    

    Model cpp:

    #include "datasourcemodel.h"
    
    DataSourceModel::DataSourceModel(QObject *parent)
        : QAbstractListModel(parent)
    {
    }
    
    QVariantMap DataSourceModel::get(int row) const
    {
        return m_DataSourceObjects[row].toMap();
    }
    
    void DataSourceModel::addDataSourceObject(const DataSourceObject &dataSourceObject)
    {
        beginInsertRows(QModelIndex(), rowCount(), rowCount());
        m_DataSourceObjects << dataSourceObject;
        endInsertRows();
    }
    int DataSourceModel::rowCount(const QModelIndex &parent) const
    {
        if (parent.isValid())
            return 0;
        return m_DataSourceObjects.count();
    }
    
    QVariant DataSourceModel::data(const QModelIndex &index, int role) const
    {
        if(index.row() < 0 || index.row() >= m_DataSourceObjects.count() || !index.isValid())
            return  QVariant();
    
        const DataSourceObject &dataSourceObject = m_DataSourceObjects[index.row()];
        if (role == idRole)
            return dataSourceObject.id();
        else if (role == nameRole)
            return dataSourceObject.name();
        else if (role == unitRole) {
            return dataSourceObject.unit();
        }
        else if (role == valueRole)
            return dataSourceObject.value();
        return QVariant();
    }
    
    bool DataSourceModel::setData(const QModelIndex &index, const QVariant &value, int role)
    {
        DataSourceObject &dataSourceObject = m_DataSourceObjects[index.row()];
        if (data(index, role) != value) {
            if(role == idRole)
                dataSourceObject.setId(value.toInt());
            else if(role == nameRole)
                dataSourceObject.setName(value.toString());
            else if(role == unitRole)
                dataSourceObject.setUnit(value.toString());
            else if(role == valueRole)
                dataSourceObject.setValue(value.toDouble());
            emit dataChanged(index, index, QVector<int>() << role);
            return true;
        }
        return false;
    }
    
    Qt::ItemFlags DataSourceModel::flags(const QModelIndex &index) const
    {
        if (!index.isValid())
            return Qt::NoItemFlags;
    
        return Qt::ItemIsEditable; // FIXME: Implement me!
    }
    
    QHash<int, QByteArray> DataSourceModel::roleNames() const
    {
        QHash<int, QByteArray> roles;
        roles[idRole] = "id";
        roles[nameRole] = "name";
        roles[unitRole] = "unit";
        roles[valueRole] = "value";
        return  roles;
    }
    

    I tried to derive the object inside the model from QObject, to use Q_INVOKABLE, but when derived from QOBject, I can't compile it.

    C:\Qt\5.10.0\mingw53_32\include\QtCore\qlist.h:435: Fehler: use of deleted function 'DataSourceObject::DataSourceObject(const DataSourceObject&)' if (QTypeInfo::isLarge || QTypeInfo::isStatic) n->v = new T(t);
    

  • Lifetime Qt Champion

    You can't copy QObject classes. So either don't derive from QObject or use pointers



  • Hello Christian,

    thank you, I changed it to pointers and now it's derived from QObject.

    But now I have the problem that I don't know how to access items from the model directly to use their Q_PROPERTYs
    Any ideas?


  • Lifetime Qt Champion

    Hi,

    Do you mean replace:

    dataSourceObject.setId(value.toInt());

    by

    dataSourceObject->setId(value.toInt());

    ?



  • I changed my model to this:
    Header:

    #ifndef DATASOURCEMODEL_H
    #define DATASOURCEMODEL_H
    
    #include "datasourceobject.h"
    #include <QAbstractListModel>
    
    class DataSourceModel : public QAbstractListModel
    {
        Q_OBJECT
    
    public:
        enum datasourceRoles {
            idRole = Qt::UserRole ,
            nameRole,
            unitRole,
            valueRole
        };
    
        explicit DataSourceModel(QObject *parent = nullptr);
        void addDataSourceObject(DataSourceObject *dataSourceObject);
        Q_INVOKABLE QVariantMap get(int row) const;
    
        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
        Q_INVOKABLE QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
        bool setData(const QModelIndex &index, const QVariant &value,
                     int role = Qt::EditRole) override;
        Q_INVOKABLE DataSourceObject *dataPointer(const QModelIndex &index);
        Qt::ItemFlags flags(const QModelIndex& index) const override;
        QHash<int, QByteArray> roleNames() const override;
        //bool checkIndex(const QModelIndex &index) const;
    
    private:
        QList<DataSourceObject*> m_DataSourceObjects;
    };
    
    #endif // DATASOURCEMODEL_H
    

    Cpp:

    #include "datasourcemodel.h"
    
    DataSourceModel::DataSourceModel(QObject *parent)
        : QAbstractListModel(parent)
    {
    }
    
    QVariantMap DataSourceModel::get(int row) const
    {
        return m_DataSourceObjects[row]->toMap();
    }
    
    void DataSourceModel::addDataSourceObject(DataSourceObject *dataSourceObject)
    {
        beginInsertRows(QModelIndex(), rowCount(), rowCount());
        m_DataSourceObjects << dataSourceObject;
        endInsertRows();
    }
    int DataSourceModel::rowCount(const QModelIndex &parent) const
    {
        if (parent.isValid())
            return 0;
        return m_DataSourceObjects.count();
    }
    
    QVariant DataSourceModel::data(const QModelIndex &index, int role) const
    {
        if(index.row() < 0 || index.row() >= m_DataSourceObjects.count() || !index.isValid())
            return  QVariant();
    
        DataSourceObject *dataSourceObject = m_DataSourceObjects[index.row()];
        if (role == idRole)
            return dataSourceObject->id();
        else if (role == nameRole) {
            return dataSourceObject->name();
        }
        else if (role == unitRole) {
            return dataSourceObject->unit();
        }
        else if (role == valueRole)
            return dataSourceObject->value();
        return QVariant();
    }
    
    bool DataSourceModel::setData(const QModelIndex &index, const QVariant &value, int role)
    {
        DataSourceObject *dataSourceObject = m_DataSourceObjects[index.row()];
        if (data(index, role) != value) {
            if(role == idRole)
                dataSourceObject->setId(value.toInt());
            else if(role == nameRole)
                dataSourceObject->setName(value.toString());
            else if(role == unitRole)
                dataSourceObject->setUnit(value.toString());
            else if(role == valueRole)
                dataSourceObject->setValue(value.toDouble());
            emit dataChanged(index, index, QVector<int>() << role);
            return true;
        }
        return false;
    }
    
    DataSourceObject *DataSourceModel::dataPointer(const QModelIndex &index)
    {
        return m_DataSourceObjects[index.row()];
    }
    
    Qt::ItemFlags DataSourceModel::flags(const QModelIndex &index) const
    {
        if (!index.isValid())
            return Qt::NoItemFlags;
    
        return Qt::ItemIsEditable; // FIXME: Implement me!
    }
    
    QHash<int, QByteArray> DataSourceModel::roleNames() const
    {
        QHash<int, QByteArray> roles;
        roles[idRole] = "id";
        roles[nameRole] = "name";
        roles[unitRole] = "unit";
        roles[valueRole] = "value";
        return  roles;
    }
    

    I can access now the objects from QML, but it stops working after about 40 changes and the used memory raises:

    dataSourceModel.dataPointer(dataSourceModel.index(row,0)).value
    

    Why does ist stop working after some changes of the "value" of the object

    When I debug the access in QML I see the address of the object in the console, but after some changes to it's value the function dataPointer returns NULL

    Console.log

    DataSourceObject(0x1fcc44a8)
    DataSourceObject(0x1fcc44a8)
    DataSourceObject(0x1fcc44a8)
    DataSourceObject(0x1fcc44a8)
    DataSourceObject(0x1fcc44a8)
    DataSourceObject(0x1fcc44a8)
    DataSourceObject(0x1fcc44a8)
    DataSourceObject(0x1fcc44a8)
    DataSourceObject(0x1fcc44a8)
    DataSourceObject(0x1fcc44a8)
    DataSourceObject(0x1fcc44a8)
    qrc:/qml/gauges/RPMBar1.qml:13: TypeError: Cannot read property 'value' of null
    null
    null
    null
    null
    null
    

    If I understand it right, the Models are changing the addresses of it's items during runtime. I need a workaround for that


  • Lifetime Qt Champion

    Can you provide a minimal compilable example that shows that behaviour ?



  • @SGaist
    you can download a running example here:
    https://www.dropbox.com/s/nbhsx8nkvc73jqz/powertune-2.zip?dl=0
    Please copy the datasources.json file into the build folder before starting

    Normally the data is supplied over network but I changed it to make a running example


  • Lifetime Qt Champion

    property int rpm : dataSourceModel.dataPointer(dataSourceModel.index(88,0)).value

    What should this do? Do you want to print the pointer to the object? Since it's a QObject it gets deleted after it gets out of scope I would guess - at least this it what it looks like when running it with valgrind



  • @Christian-Ehrlicher
    It returns the property "value" of an item of the model that I want to display in QML using Q_PROPERTY. I'm open for better ways to do that. In the Qt manuals is no way described to show single items of a model and get the GUI updatet automatically.
    If you run my program you can see that the GUI prints the property "value" of the item with ID "88" from the model.
    What do you mean with it gets out of scope? It is still part of the model and still visible when I display the whole model in a ListView in QML.


  • Lifetime Qt Champion

    This is what I mean and why your object gets destroyed: https://doc.qt.io/qt-5/qtqml-cppintegration-data.html#data-ownership
    I don't see a reason why this function should be Q_INVOKEABLE and even no need to access the plain data instead going through the model.



  • @Christian-Ehrlicher
    Then can you give me a way how to do it with the model? How can I display a single item in the QML? The data() function is of no use to me. I need "realtime" updated values in the QML.


  • Lifetime Qt Champion

    What should be the difference to call data() or the property value which also only calls the function value() ?



  • @Christian-Ehrlicher
    When I call in QML e.g. text: mymodel.data(index,role); this only shows the momentary value of this item, but it's not updating the GUI automatically when value changes.
    This is my problem. I need the GUI updated when the item changes.



  • @Slash200
    I only know about Qt widgets, not QML. In Qt widgets when model gets updated a signal like dataChanged (and others) is emitted, and a linked view will receive that and update itself as necessary. Are you supposed to do something like this from QML? Or is it just supposed to work automatically? Examples tend to use, say, ListView; I don't know whether you using it for a text means it needs to be told to update on data change.


  • Lifetime Qt Champion

    @Slash200 said in Q_INVOKABLE from object used by QAbstractListModel:

    but it's not updating the GUI automatically when value changes.

    so why not connect to the dataChanged signal then?
    e.g. here: https://forum.qt.io/topic/71392/qml-receives-undefined-for-roles-from-datachanged-signal



  • The dataChanged Signal is fired for every item and I've got about 200 items which are updated hundrets times per seconds, which are minimum 20.000 signals per second. (I'm receiving realtime data from a motorsports Engine control unit via CANBUS)
    This is a dashboarding software which runs on a embedded device with not very much CPU power. I want only to update the QML items when the specific item is changed. otherwise the CPU is exhausted, because every gauge will update, even if a different item is changed. I hope I've explained my problem in a way that's reasonably understandable.


  • Lifetime Qt Champion

    So define a custom signal in your model and connect to this.



  • @Christian-Ehrlicher
    yes this works. I've got a signal: void itemValueChanged(double value, int id);
    The qml item can now see by the ID if it's it's value that has to be updatet,
    But this means that all items need a if to check wether if it's it's id or not and will cause also a lot of load.
    Like that:

    Connections {
        target: dataSourceModel
        onItemValueChanged: {
            if(myId === id){
                text = value;
                }
    
        }
    }
    

    Is this the way you meant to do it?


  • Lifetime Qt Champion

    @Slash200 said in Q_INVOKABLE from object used by QAbstractListModel:

    Is this the way you meant to do it?

    More or less, but you can also emit a signal for the specific entry as you seem to know the row number (88?)


Log in to reply