来自 C++ QAbstractListModel 的 Qml ListView 与 QThread 处理

问题描述

我使用 C++ QAbstractListModel 找到了一个 QML ListView sample。然而,获取列表模型的数据需要一段时间,waiting popup 被冻结。因此,我尝试在 a 中使用 QThread 示例(bccpu 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 方法以及与该方法相关的所有内容。