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"); }
-
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"); }
@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
-
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
@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 modelmyModel
and when the signaltypeChanged()
it can be caught in its corresponding handleronTypeChanged
. 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 astypeChanged(QString type)
.Or if you don't then you already have received signal using
Connections
where you can update theText
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" -
@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?
-
@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?
@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.