QT 5.11.3 - C++ - QAbstractListModel - TableView 不会自动刷新

QT 5.11.3 - C++ - QAbstractListModel - TableView doesn't refresh itself automatically

为了通过继承 QAbstractListModel 的模型实时更新 TableView,我已经苦苦挣扎了五天,但没有成功。

我创建了一个 QAbstractListModel QDocumentList,它应该是一个 QDocument 列表(QDocument 只有一个名称和一个 ID)。然后我创建了一个 class DocManager,其中包含我的 QDocumentList 和一个每秒生成一个新文档的线程。

在 QML 方面,我刚刚使用我的 QDocumentList 作为模型制作了一个 TableView,并添加了一个小功能,可以在使用鼠标区域单击时“手动”将新文档添加到列表中。

这是我的问题:当从 C++ 后端(线程)创建新的 QDocument 时,我的 TableView 没有更新。它们只在我单击 TableView 时出现,谢谢到鼠标区域。

我做错了什么?

我的代码如下所示:

Main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "DocManager.h"

int main(int argc, char *argv[])
{
    DocManager dm;
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    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.rootContext()->setContextProperty("docManager", &dm);
    engine.load(url);


    return app.exec();
}

QDocument.h

#pragma once
#ifndef QDOCUMENT_H
#define QDOCUMENT_H

#include <QObject>
#include <QLibrary>
#include <QString>
#include <qvariant.h>
#include <QVariantList>
#include <QAbstractListModel>
#include <QList>

class QDocument : public QObject
{
    Q_OBJECT
//  QML_ELEMENT

    private:

    public:
        QString m_docName;
        int m_docId;

        QDocument();
        QDocument(QString docName, int docId);
        QDocument(QDocument* doc);

        Q_PROPERTY(QString docName READ getDocName WRITE setDocName NOTIFY docNameChanged)
        QString getDocName();
        void setDocName(QString name);

        Q_PROPERTY(int docId READ getDocId WRITE setDocId NOTIFY docIdChanged)
        int getDocId();
        void setDocId(int id);

    signals:
        void docNameChanged();
        void docIdChanged();
};

Q_DECLARE_METATYPE(QDocument *)

class QDocumentList : public QAbstractListModel
{
    Q_OBJECT

    public:

        enum Roles
        {
            doc_name = Qt::UserRole + 1,
            doc_state
        };

        explicit QDocumentList(QDocument *parent = nullptr) : QAbstractListModel(parent)
        {

        }
        ~QDocumentList();

        //Q_INVOKABLE void addDocument(QDocument doc)
        Q_INVOKABLE int rowCount(const QModelIndex& parent = QModelIndex()) const;
        QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
        QHash<int, QByteArray> roleNames() const override;


    public slots:
        QDocument* getQDocument(int index);
        Q_INVOKABLE void addDocument(QString docName, int docId);

    private:
        QList<QDocument*> m_docs;
        int m_docCount;

};


#endif

QDocument.cpp

#include "QDocument.h"

QDocument::QDocument()
{

}

QDocument::QDocument( QString docName, int docId)
{
    m_docName = docName;
    m_docId = docId;
}

QDocument::QDocument(QDocument * doc)
{
    m_docId = doc->getDocId();
    m_docName = doc->getDocName();
    m_docId = doc->getDocId();
}

QString QDocument::getDocName()
{
    return m_docName;
}

void QDocument::setDocName(QString name)
{
    if (m_docName != name)
    {
        m_docName = name;
        emit docNameChanged();
    }
}

int QDocument::getDocId()
{
    return m_docId;
}

void QDocument::setDocId(int id)
{
    if (m_docId != id)
    {
        m_docId = id;
        emit docIdChanged();
    }
}

QDocumentList::~QDocumentList()
{
    qDeleteAll(m_docs);
}

int QDocumentList::rowCount(const QModelIndex & parent) const
{
    return m_docs.count();
}


QVariant QDocumentList::data(const QModelIndex & index, int role) const
{
    switch (role)
    {
        case doc_name:
            return QVariant::fromValue<QString>(m_docs.at(index.row())->getDocName());
            break;

        case doc_state:
            return QVariant::fromValue<int>(m_docs.at(index.row())->getDocId());
            break;

        default:
            return QVariant();
            break;
    }
}

QDocument* QDocumentList::getQDocument(int index)
{
    if (index < 0 || index >= m_docs.size())
        return nullptr;

    return m_docs[index];
}


QHash<int, QByteArray> QDocumentList::roleNames() const
{
    QHash<int, QByteArray> roleNames;
    roleNames[doc_name] = "doc_name";
    roleNames[doc_state] = "doc_state";
    return roleNames;
}

Q_INVOKABLE void QDocumentList::addDocument(QString docName, int docId)
{
    this->beginInsertRows(this->index(m_docs.count()), m_docs.count(), m_docs.count());
    m_docs.append(new QDocument(docName, docId));
    this->endInsertRows();
}

DocManager.h

#ifndef DOCMANAGER_H
#define DOCMANAGER_H

#include <QObject>
#include <QLibrary>
#include <QString>
#include <QVariantList>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <iostream>
#include "QDocument.h"

class DocManager : public QObject
{
    Q_OBJECT
    private:
        QDocumentList *docs;

    public:
        Q_PROPERTY(QDocumentList* docList READ getDocList NOTIFY docListChanged)

        DocManager();

        //*** Afficheur thread ***
        std::thread thread_doc;

        //Message thread functions
        void func_doc(); //TODO : logs
        QDocumentList* getDocList();

    signals:
        void docListChanged();

};

#endif // DOCMANAGER_H

DocManager.cpp

#include "DocManager.h"

DocManager::DocManager()
{
    docs = new QDocumentList();
    thread_doc = std::thread([this] {func_doc();});
}

QDocumentList* DocManager::getDocList()
{
    return docs;
}

void DocManager::func_doc()
{
    int i=0;
    while (i<100)
    {
        std::cout << "Creating doc " << i << std::endl;
        docs->addDocument("dummy_", i);
        _sleep(1000);
        i++;
    }

}

main.qml

import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.2
import QtLocation 5.3
import QtQuick.Controls 2.1

import QtQuick.Controls 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQml.Models 2.1
import QtQml 2.0

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    Rectangle
    {
        anchors.fill: parent

        TableView
        {
            property int updateCounter: 0
            id: docGrid
            model: docManager.docList
            anchors.fill: parent
            anchors.centerIn: parent
            focus: true

            TableViewColumn
            {
                role: "doc_name"
                title: "Document name"
                width: 600
            }

            TableViewColumn
            {
                role: "doc_state"
                title: "Document status"
                width: 300
            }

            style: TableViewStyle {
                minimumHandleLength : 12
                handle: Rectangle {
                    implicitHeight: 13
                    implicitWidth: 13
                    color: "grey"
                }
            }

            rowDelegate: Rectangle
            {
                height: parent.height
                SystemPalette
                {
                    id: myPalette;
                    colorGroup: SystemPalette.Active
                }
                color:
                {
                    var baseColor = styleData.alternate ? myPalette.alternateBase:myPalette.base
                    return styleData.selected ? myPalette.highlight:baseColor
                }

            }

            MouseArea
            {
                anchors.fill: parent
                onClicked: docManager.docList.addDocument("click_dummy", docManager.docList.rowCount());
            }
        }
    }
}

问题是 QObjects 不是线程安全的,与 GUI 交互的 QObjects 更是如此,因此您不应该从另一个线程更新 GUI,而必须从辅助线程向主线程发送信息在主线程中,应该更新 GUI。 Qt 为此提供了几个选项,例如信号、QMetaObject::invokedMethod() 和 QEvents。在这种情况下,我将使用第二个选项在 DocManager 中创建 addDocument 方法:

#ifndef DOCMANAGER_H
#define DOCMANAGER_H

#include "QDocument.h"

#include <QObject>
#include <thread>

class DocManager : public QObject
{
    Q_OBJECT
private:
    QDocumentList *docs;

public:
    Q_PROPERTY(QDocumentList* docList READ getDocList NOTIFY docListChanged)

    DocManager();

    //*** Afficheur thread ***
    std::thread thread_doc;

    //Message thread functions
    void func_doc(); //TODO : logs
    QDocumentList* getDocList();
private slots:
    void addDocument(const QString & docName, int docId);
signals:
    void docListChanged();

};

#endif // DOCMANAGER_H
#include "DocManager.h"

#include <iostream>

DocManager::DocManager()
{
    docs = new QDocumentList();
    thread_doc = std::thread([this] {func_doc();});
}

QDocumentList* DocManager::getDocList()
{
    return docs;
}

void DocManager::addDocument(const QString &docName, int docId)
{
    docs->addDocument(docName, docId);
}

void DocManager::func_doc()
{
    int i=0;
    while (i<100)
    {
        std::cout << "Creating doc " << i << std::endl;
        QMetaObject::invokeMethod(this, "addDocument",
                                  Qt::QueuedConnection,
                                  Q_ARG(QString, "dummy_"),
                                  Q_ARG(int, i));
        std::this_thread::sleep_for(std::chrono::seconds(1));
        i++;
    }
}