问题描述
我使用 C++ QAbstractListModel 找到了一个 QML ListView sample。然而,获取列表模型的数据需要一段时间,waiting popup 被冻结。因此,我尝试在 a 中使用 QThread 示例(b、c、cpu intensive task sample)。
然后,当另一个线程 (ThreadHandler::process()) 尝试在主线程 (PersonModel::requestPersonData()) 中获取模型数据时,我收到了以下错误。
QObject::connect: 不能将 'QQmlChangeSet' 类型的参数加入队列 (确保使用 qRegisterMetaType() 注册了“QQmlChangeSet”。)
我的问题是如何从不同的 QThread 函数将数据添加到 QAbstractListModel 中。或者有什么办法可以处理大数据导致的耗时列表模型?
这是可重现的代码。 (Qt 5.12.10 MSVC2015 64 位,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,SLOT(deleteLater()),Qt::QueuedConnection);
connect(pPThread,pPThread,Qt::QueuedConnection);
pPThread->start();
}
void PersonModel::requestDeleteall()
{
for (int i = rowCount() - 1; i >= 0; i--)
{
remove(i);
}
}
线程处理程序.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)
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,&PersonModel::addPerson);
// Automatically delete pPThread and pHandler after the work is done.
connect(pHandler,&QObject::deleteLater,Qt::QueuedConnection);
connect(pPThread,&QThread::finished,pPThread,Qt::QueuedConnection);
pPThread->start();
}
删除 setListModel 方法以及与该方法相关的所有内容。