Saya menemukan contoh QML ListViewmenggunakan C++ QAbstractListModel. Namun, butuh beberapa saat untuk mengambil data model daftar dan sembulan menunggu membeku. Jadi, saya mencoba menggunakan sampel QThread (a, b, c) di contoh tugas intensif cpu.

Kemudian, saya mendapatkan kesalahan di bawah ini, ketika utas yang berbeda (ThreadHandler::process()) mencoba mengambil data model di utas utama (PersonModel::requestPersonData()).

QObject::connect: Tidak dapat mengantre argumen tipe 'QQmlChangeSet' (Pastikan 'QQmlChangeSet' terdaftar menggunakan qRegisterMetaType().)

Pertanyaan saya adalah bagaimana cara menambahkan data ke QAbstractListModel dari fungsi QThread yang berbeda. Atau adakah cara untuk menangani model daftar yang memakan waktu karena data yang besar?

Berikut adalah kode yang dapat direproduksi. (Qt 5.12.10 MSVC2015 64bit, Qt Creator 4.14.2. windows 10)

Terima kasih sebelumnya.

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();
}
0
qmllearner 12 Mei 2021, 20:48

1 menjawab

Jawaban Terbaik

Karena model terkait dengan tampilan, maka Anda tidak dapat memodifikasinya langsung dari utas lain. Dalam hal ini lebih baik untuk membuat sinyal yang mengirimkan informasi ke 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();
}

Hapus metode setListModel dan semua yang terkait dengan metode itu.

0
eyllanesc 12 Mei 2021, 18:08