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++;
}
}
为了通过继承 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++;
}
}