QAbstractItemModel property buinding



  • I have sub-classed a QAbstractTableModel which contains a database of live value changing continuously. Included in this post is a modification of abstractitemmodel example to illustrate my problem and what I want to achieve.

    When using the ListView in QML, the values updates according to changes in the model. How can I receive these updates when using a "Text QML Type" (or any other Types)?

    view.qml

    import QtQuick 2.4
    
    Rectangle {
        Text {
            text: myModel.get(0,0)
        }
    
        ListView {
            width: 200; height: 250
            y: 20
    
            model: myModel
            delegate: Text { text: "Animal: " + type }
        }
    
    }
    

    model.h:

    #include <QAbstractListModel>
    #include <QStringList>
    #include <QObject>
    
    class Animal : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(QString type READ type WRITE setType NOTIFY typeChanged)
    public:
        Animal(const QString type, QObject *parent = 0);
        QString type() const;
        void setType(QString s);
    
    signals:
        void typeChanged(QString);
    
    private:
        QString m_type;
    };
    
    class AnimalModel : public QAbstractListModel
    {
        Q_OBJECT
    public:
        enum AnimalRoles {
            TypeRole = Qt::UserRole + 1,
        };
    
        AnimalModel(QObject *parent = 0);
    
        void addAnimal(QString type);
        int rowCount(const QModelIndex & parent = QModelIndex()) const;
        QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
        // Qt 5.5 helper functions
        Q_INVOKABLE QVariant get(const QByteArray& roleName, int row, int column = 0, const QModelIndex& parent = QModelIndex()) const;
        Q_INVOKABLE QVariant get(int role, int row, int column = 0, const QModelIndex& parent = QModelIndex()) const;
    
    public slots:
        void onUpdate(QString v);
    
    protected:
        QHash<int, QByteArray> roleNames() const;
    private:
        QList<Animal *> m_animals;
    };
    

    model.cpp

    #include "model.h"
    
    Animal::Animal(const QString type, QObject *parent): QObject(parent)
    {
        setType(type);
    }
    
    QString Animal::type() const
    {
        return m_type;
    }
    
    void Animal::setType(QString s)
    {
        m_type = s;
        emit typeChanged(s);
    }
    
    AnimalModel::AnimalModel(QObject *parent)
        : QAbstractListModel(parent)
    {
    }
    
    void AnimalModel::onUpdate(QString newtype)
    {
        Animal *a = m_animals[0];
        a->setType(newtype);
        QModelIndex top = createIndex(0, 0);
        QModelIndex bottom = createIndex(m_animals.count(), 1);
        emit dataChanged(top, bottom);
    }
    
    void AnimalModel::addAnimal(QString type)
    {
        Animal *a = new Animal(type);
        beginInsertRows(QModelIndex(), rowCount(), rowCount());
        m_animals.append(a);
        endInsertRows();
    }
    
    int AnimalModel::rowCount(const QModelIndex & parent) const {
        Q_UNUSED(parent);
        return m_animals.count();
    }
    
    QVariant AnimalModel::data(const QModelIndex & index, int role) const {
        if (index.row() < 0 || index.row() >= m_animals.count())
            return QVariant();
    
        Animal *a = m_animals[index.row()];
        if (role == TypeRole)
            return a->type();
        if (role == 0)
            return a->type();
        return QVariant();
    }
    
    //![0]
    QHash<int, QByteArray> AnimalModel::roleNames() const {
        QHash<int, QByteArray> roles;
        roles[TypeRole] = "type";
        return roles;
    }
    //![0]
    
    /*!
    \since 5.5
    Helper function to access elements in the model for a given \a roleName,
    and an index defined by its \a row, \a column and \a parent.
    \sa data()
    */
    QVariant AnimalModel::get(const QByteArray& roleName, int row, int column, const QModelIndex& parent) const
    {
        int role = roleNames().key(roleName, -1);
        if (role < 0) {
            return QVariant();
        }
        return data(index(row, column, parent), role);
    }
    
    /*!
    \since 5.5
    Helper function to access elements in the model for a given \a role,
    and an index defined by its \a row, \a column and \a parent.
    \sa data()
    */
    QVariant AnimalModel::get(int role, int row, int column, const QModelIndex& parent) const
    {
        return data(index(row, column, parent), role);
    }
    

    main.cpp

    #include "model.h"
    
    #include <QGuiApplication>
    #include <qqmlengine.h>
    #include <qqmlcontext.h>
    #include <qqml.h>
    #include <QtQuick/qquickitem.h>
    #include <QtQuick/qquickview.h>
    #include <QThread>
    #include "worker.h"
    
    int main(int argc, char ** argv)
    {
        QGuiApplication app(argc, argv);
    
        AnimalModel model;
        model.addAnimal("Wolf");
        model.addAnimal("Polar bear");
        model.addAnimal("Quoll");
    
        QThread *thread = new QThread();
        Worker *worker = new Worker();
        worker->moveToThread(thread);
        QObject::connect(thread, SIGNAL(started()), worker, SLOT(process()));
        QObject::connect(worker, SIGNAL(updatevar(QString)), &model, SLOT(onUpdate(QString)));
        thread->start();
    
        QQuickView view;
        view.setResizeMode(QQuickView::SizeRootObjectToView);
        QQmlContext *ctxt = view.rootContext();
        ctxt->setContextProperty("myModel", &model);
    
        view.setSource(QUrl("qrc:view.qml"));
        view.show();
    
        return app.exec();
    }
    

    worker.h

    #ifndef WORKER_H
    #define WORKER_H
    
    #include <QObject>
    
    class Worker : public QObject
    {
        Q_OBJECT
    public:
        explicit Worker(QObject *parent = 0);
    
    signals:
        void completed(const char *v);
        void updatevar(QString v);
    
    public slots:
        void process();
    
    private:
        QList<QString> m_animalTypes;
    
    };
    
    #endif // WORKER_H
    

    worker.cpp

    #include "worker.h"
    #include "QThread"
    
    Worker::Worker(QObject *parent) :
        QObject(parent)
    {
        m_animalTypes.append("Polar bear");
        m_animalTypes.append("Quoll");
        m_animalTypes.append("Mouse");
        m_animalTypes.append("Squirrel");
        m_animalTypes.append("Fox");
        m_animalTypes.append("Wolf");
    }
    
    void Worker::process()
    {
        for (int i = 0; i < m_animalTypes.count(); i++)
        {
            QThread::sleep(2);
            emit updatevar(m_animalTypes[i]);
        }
        emit completed("Thread ended");
    }
    

  • Moderators

    @olejl77 You can use onAdd signal handler in your delegate where you can update the Text item.

    import QtQuick 2.4
    
    Rectangle {
        Text {
            id: myText
            //text: myModel.get(0,0)
        }
        ListView {
            width: 200; height: 250
            y: 20
            model: myModel
            delegate: Text { 
                text: "Animal: " + type 
                ListView.onAdd: myText.text = type
            }
        }
    }
    

    Is this what you wanted ?



  • Your code doesn't seem to work. When I run it the Text is empty.

    Also the ListView was only added to show that it is working in the list view. I don't want to use a ListView. I only want to show a specific index in the model in the Text property.

    Showing the type was working in my original code, but when the model emit typeChanged(), the Text is not updated.

    I want only the Text


  • Moderators

    @olejl77 Ok. Well then you just have to notify the updates in QML. One of the way is to use
    Connections. target it to your model myModel and when the signal typeChanged() it can be caught in its corresponding handler onTypeChanged. You can modify signal to allow it to pass an argument so that it can be directly received in the handler. So for eg:

    Text {
        id: myText
    }
    
    Connections {
        target: myModel
        onTypeChanged: myText.text = type
    }
    

    This will work if you pass for eg. type as an argument in signal as typeChanged(QString type).

    Or if you don't then you already have received signal using Connections where you can update the Text

    Connections {
        target: myModel
        onTypeChanged: myText.text = myModel.get(0,0)
    }
    

    Hope this is helps...



  • @p3c0 Thanks for trying to assist, but I can get it to work. I get the following error message in both your cases:
    QML Connections: Cannot assign to non-existent property "onTypeChanged"


  • Moderators

    @olejl77 That means either there is no signal named typeChanged in your model or there's case problem. Meaning to have onTypeChanged the signal name should be typeChanged. More info here in first paragraph. Make sure they are accordingly changed.



  • @p3c0 It finally dawned for me. My problem here is that "myModel" doesn't have a typeChanged. typeChanged is originating from the Animal object inside "myModel". What I want is a way to bind to a a specific object inside the model, and not to the model itself.

    Is there a "proper" way to do that? I could use the dataChanged signal and figure out from there if the change was from the object I want, but it sound unessessary complicated. How can I find out how the ListView component handles this?


  • Moderators

    @olejl77 You can add typeChanged signal and emit it from the model itself when new item is added to model or as per your requirement (i.e when specific item is added).

    What I want is a way to bind to a a specific object inside the model, and not to the model itself.

    AFAIK the only way to access the items of model is through model itself. The example I posted earlier will always get the Item at 0 index and not any other when.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.