来自具有 QThread 处理的 C++ QAbstractListModel 的 Qml ListView
Qml ListView from C++ QAbstractListModel with QThread processing
我找到了 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.
然后,当另一个线程 (ThreadHandler::process()) 试图在主线程 (PersonModel::requestPersonData()) 中获取模型数据时,我得到了以下错误。
QObject::connect: Cannot queue arguments of type 'QQmlChangeSet'
(Make sure 'QQmlChangeSet' is registered using qRegisterMetaType().)
我的问题是如何从不同的 QThread 函数向 QAbstractListModel 添加数据。或者有什么方法可以处理由于数据量大而耗时的列表模型?
这是可重现的代码。 (Qt 5.12.10 MSVC2015 64bit, Qt Creator 4.14.2.windows 10)
提前致谢。
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();
}
由于模型与视图相关,因此您不能直接从另一个线程修改它。在这种情况下,最好创建一个将信息发送到模型的信号:
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();
}
删除 setListModel 方法以及与该方法相关的所有内容。
我找到了 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.
然后,当另一个线程 (ThreadHandler::process()) 试图在主线程 (PersonModel::requestPersonData()) 中获取模型数据时,我得到了以下错误。
QObject::connect: Cannot queue arguments of type 'QQmlChangeSet' (Make sure 'QQmlChangeSet' is registered using qRegisterMetaType().)
我的问题是如何从不同的 QThread 函数向 QAbstractListModel 添加数据。或者有什么方法可以处理由于数据量大而耗时的列表模型?
这是可重现的代码。 (Qt 5.12.10 MSVC2015 64bit, Qt Creator 4.14.2.windows 10)
提前致谢。
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();
}
由于模型与视图相关,因此您不能直接从另一个线程修改它。在这种情况下,最好创建一个将信息发送到模型的信号:
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();
}
删除 setListModel 方法以及与该方法相关的所有内容。