I found a QML ListView sample using a C++ QAbstractListModel. However, it took a while to fetch the list model's data and waiting popup was freezing. So, I tried to use QThread samples (a, b, c) in the cpu intensive task sample.
Then, I got the below errors, when a different thread (ThreadHandler::process()) tried to fetch the model data in the main thread (PersonModel::requestPersonData()).
QObject::connect: Cannot queue arguments of type 'QQmlChangeSet' (Make sure 'QQmlChangeSet' is registered using qRegisterMetaType().)
My question is how to add data into the QAbstractListModel from the different QThread's function. Or is there any way to handle the time consuming list model due to the large data?
Here is the reproducible code. (Qt 5.12.10 MSVC2015 64bit, Qt Creator 4.14.2. windows 10)
Thanks in advance.
personmodel.h
#ifndef PERSONMODEL_H
#define PERSONMODEL_H
#include <QAbstractListModel>
struct Person
{
QString First;
QString Last;
};
class PersonModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
explicit PersonModel(QObject *parent = nullptr);
virtual ~PersonModel();
enum PersonRoles {
FirstRole = Qt::UserRole + 1,
LastRole
};
Q_ENUM(PersonRoles)
void addPerson(Person *p);
Person* getPerson(int index);
void clear();
int count() const;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
virtual QHash<int, QByteArray> roleNames() const override;
public slots:
void requestPersonData(void);
void requestDeleteAll();
bool remove(const int inIndex);
signals:
void countChanged();
private:
QHash<int, QByteArray> _roles;
QList<Person *> _people;
};
#endif // PERSONMODEL_H
personmodel.cpp
#include "personmodel.h"
#include "threadhandler.h"
#include <QThread>
#include <QWaitCondition>
#include <QDebug>
#include <time.h>
PersonModel::PersonModel(QObject *parent)
: QAbstractListModel(parent)
{
_roles[FirstRole] = "first";
_roles[LastRole] = "last";
}
PersonModel::~PersonModel()
{
_people.clear();
}
int PersonModel::count() const
{
return rowCount();
}
int PersonModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return _people.count();
}
QVariant PersonModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= _people.count())
return QVariant();
Person *p = _people.at(index.row());
switch (role) {
case FirstRole:
return QVariant::fromValue(p->First);
case LastRole:
return QVariant::fromValue(p->Last);
default:
break;
}
return QVariant();
}
QHash<int, QByteArray> PersonModel::roleNames() const
{
return _roles;
}
void PersonModel::clear()
{
qDeleteAll(_people);
_people.clear();
}
Person *PersonModel::getPerson(int index)
{
return _people.at(index);
}
void PersonModel::addPerson(Person *p)
{
int row = _people.count();
beginInsertRows(QModelIndex(), row, row);
_people.append(p);
endInsertRows();
}
bool PersonModel::remove(const int inIndex)
{
if ((rowCount() <= 0) || (inIndex < 0) || (rowCount() <= inIndex))
return false;
beginRemoveRows(QModelIndex(), inIndex, inIndex);
_people.removeAt(inIndex);
endRemoveRows();
return true;
}
void PersonModel::requestPersonData()
{
QThread* pPThread = new QThread();
ThreadHandler* pHandler = new ThreadHandler();
pHandler->setListModel(this);
pHandler->moveToThread(pPThread);
connect(pPThread, SIGNAL(started()), pHandler, SLOT(process()));
connect(pHandler, SIGNAL(finished()), this, SIGNAL(countChanged()));
// Automatically delete pPThread and pHandler after the work is done.
connect(pHandler, SIGNAL(finished()), pHandler, SLOT(deleteLater()), Qt::QueuedConnection);
connect(pPThread, SIGNAL(finished()), pPThread, SLOT(deleteLater()), Qt::QueuedConnection);
pPThread->start();
}
void PersonModel::requestDeleteAll()
{
for (int i = rowCount() - 1; i >= 0; i--)
{
remove(i);
}
}
threadhandler.h
#ifndef THREADHANDLER_H
#define THREADHANDLER_H
#include <QObject>
class PersonModel;
class ThreadHandler : public QObject
{
Q_OBJECT
public:
explicit ThreadHandler(QObject *parent = nullptr);
void setListModel(PersonModel* personModel);
public slots:
void process();
signals:
void finished();
private:
void doPrimes();
int calculatePrimes(int inRepeat);
private:
PersonModel* m_personModel;
};
#endif // THREADHANDLER_H
threadhandler.cpp
#include "threadhandler.h"
#include "personmodel.h"
#include <QDebug>
#include "time.h"
ThreadHandler::ThreadHandler(QObject *parent) : QObject(parent)
{
}
void ThreadHandler::setListModel(PersonModel *personModel)
{
m_personModel = personModel;
}
void ThreadHandler::process()
{
qDebug() << Q_FUNC_INFO << "thread handler starts";
// simulate the cpu intensive procedure such as db query or 3rd party library call
calculatePrimes(3);
int row = 5;//rowCount();
QString strLastNameIndex;
for (int i = 0; i < row; i++)
{
strLastNameIndex = QString::number(i);
Person* person3 = new Person();
person3->First = "Bob" + strLastNameIndex;
person3->Last = "LastName" + strLastNameIndex;
m_personModel->addPerson(person3);
}
qDebug() << Q_FUNC_INFO << "row count: " << row;
emit finished();
qDebug() << Q_FUNC_INFO << "thread handler ends";
}
#define MAX_PRIME 100000
void ThreadHandler::doPrimes()
{
unsigned long i, num, primes = 0;
for (num = 1; num <= MAX_PRIME; ++num) {
for (i = 2; (i <= num) && (num % i != 0); ++i);
if (i == num)
++primes;
}
printf("Calculated %ld primes.\n", primes);
}
int ThreadHandler::calculatePrimes(int inRepeat)
{
time_t start, end;
time_t run_time;
start = time(NULL);
for (int i = 0; i < inRepeat; ++i) {
doPrimes();
}
end = time(NULL);
run_time = (end - start);
printf("This machine calculated all prime numbers under %d %d times "
"in %lld seconds\n", MAX_PRIME, inRepeat, run_time);
return run_time;
}
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQml 2.0
Window {
id: root
width: 640
height: 480
visible: true
title: qsTr("Hello World")
MouseArea {
anchors.fill: parent
onClicked: {
timer.start()
// loadingDialog.is_running = true
console.log("personListView click to request data from qml to C++")
PersonModel.requestDeleteAll();
PersonModel.requestPersonData();
}
}
ListView {
id: personListView
width: 150; height: 400
visible: loadingDialog.is_running === true ? false : true;
model: PersonModel
delegate: personDelegate
Component.onCompleted: {
console.log(PersonModel, model)
console.log(PersonModel.count, model.count)
}
onCountChanged: {
// console.log("personListView onCountChanged getting person data is finished.")
// console.log("personListView onCountChanged after search model count: " + PersonModel.count)
loadingDialog.is_running = false
}
}
Component {
id: personDelegate
Rectangle {
width: personListView.width
height: 30
color: "lightgreen"
Text {
text: model.first + " " + model.last
}
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
personListView.currentIndex = model.index
console.log("personListView ListView item" + model.index + " is clicked.")
PersonModel.remove(model.index);
}
}
}
}
Timer {
id: timer
interval: 100
repeat: false
running: false
triggeredOnStart: false
onTriggered: {
loadingDialog.is_running = true
timer.stop()
}
}
Rectangle {
id: loadingDialog
width: 50; height: 50
color: "red"
property bool is_running: false
visible: is_running;
NumberAnimation on x {
from: 0
to: 250;
duration: 3000
loops: Animation.Infinite
running: loadingDialog.is_running
}
}
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext> // setContextProperty()
#include "personmodel.h"
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
PersonModel mymodel;
// Add initial data for list model
Person person1;
person1.First = "Bob";
person1.Last = "One";
Person person2;
person2.First = "Bob2";
person2.Last = "Two";
mymodel.addPerson(&person1);
mymodel.addPerson(&person2);
engine.rootContext()->setContextProperty("PersonModel", &mymodel);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
Since the model is related to the view then you cannot modify it directly from another thread. In this case it is better to create a signal that sends the information to the model:
threadhandler.h
signals:
void finished();
void sendPerson(Person *person);
threadhandler.cpp
void ThreadHandler::process()
{
qDebug() << Q_FUNC_INFO << "thread handler starts";
// simulate the cpu intensive procedure such as db query or 3rd party library call
calculatePrimes(3);
int row = 5;//rowCount();
QString strLastNameIndex;
for (int i = 0; i < row; i++)
{
strLastNameIndex = QString::number(i);
Person* person3 = new Person;
person3->First = "Bob" + strLastNameIndex;
person3->Last = "LastName" + strLastNameIndex;
Q_EMIT sendPerson(person3);
}
qDebug() << Q_FUNC_INFO << "row count: " << row;
emit finished();
qDebug() << Q_FUNC_INFO << "thread handler ends";
}
personmodel.cpp
void PersonModel::requestPersonData()
{
QThread* pPThread = new QThread();
ThreadHandler* pHandler = new ThreadHandler();
// pHandler->setListModel(this);
pHandler->moveToThread(pPThread);
connect(pPThread, &QThread::started, pHandler, &ThreadHandler::process);
connect(pHandler, &ThreadHandler::finished, this, &PersonModel::countChanged);
connect(pHandler, &ThreadHandler::sendPerson, this, &PersonModel::addPerson);
// Automatically delete pPThread and pHandler after the work is done.
connect(pHandler, &ThreadHandler::finished, pHandler, &QObject::deleteLater, Qt::QueuedConnection);
connect(pPThread, &QThread::finished, pPThread, &QObject::deleteLater, Qt::QueuedConnection);
pPThread->start();
}
Remove the setListModel method and everything related to that method.