C++/QML:ListView 未在来自 QAbstractListModel 的 dataChanged 信号上更新
C++/QML: ListView is not updated on dataChanged signal from QAbstractListModel
我正在尝试为大型动态 C/Fortran 模拟编写 QML Gui。我要显示的数据存储在 Fortran Common 块中,并按固定时间步长更新。我的问题是 QML ListView 在每个时间步后发出 dataChanged 信号时不会刷新,尽管 Gui 接收到该信号(测试在下面的代码中)。
我可能错过了一些非常明显的东西,因为当我再次向下和向上轻弹我的 ListView 时,显示的数据已更新且正确(我猜是因为 QML 引擎 re-renders 元素在获得 "out of sight" 并再次返回)。所以唯一不起作用的是每次收到 dataChanged 信号时 ListView 都会更新,而不仅仅是当它是 re-rendered 时。下面是对我的方法和相关代码部分的更详细描述。
每个模拟实体都有几个属性(活动、位置...),因此我决定为每个实体创建一个包含 DataObject 的 ListModel。这是对应的header文件(实际模拟数据在"interface.h"中声明为extern structs,所以我可以通过指针访问它):
"acdata.h"
#include <QtCore>
#include <QObject>
#include <QtGui>
extern "C" {
#include "interface.h"
}
class AcDataObject : public QObject
{
Q_OBJECT
public:
explicit AcDataObject(int id_, int *pac_live, double *pac_pos_x, QObject *parent = 0) :
QObject(parent)
{
entity_id = id_;
ac_live = pac_live;
ac_pos_x = pac_pos_x;
}
int entity_id;
int *ac_live;
double *ac_pos_x;
};
class AcDataModel : public QAbstractListModel
{
Q_OBJECT
public:
enum RoleNames {
IdRole = Qt::UserRole,
LiveRole = Qt::UserRole + 1,
PosXRole = Qt::UserRole + 2
};
explicit AcDataModel(QObject *parent = 0);
virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
Q_INVOKABLE Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
void do_update();
protected:
virtual QHash<int, QByteArray> roleNames() const;
private:
QList<AcDataObject*> data_list;
QHash<int, QByteArray> m_roleNames;
QModelIndex start_index;
QModelIndex end_index;
signals:
void dataChanged(const QModelIndex &start_index, const QModelIndex &end_index);
};
与 header 一样,.cpp 文件也改编自 Qt5 Cadaques Book here 中的内容,除了我的构造函数遍历所有模拟实体以设置指针。此外,还有 do_update 函数为整个列表发出 dataChanged 信号。
"acdata.cpp"
#include "acdata.h"
AcDataModel::AcDataModel(QObject *parent) :
QAbstractListModel(parent)
{
m_roleNames[IdRole] = "entity_id";
m_roleNames[LiveRole] = "ac_live";
m_roleNames[PosXRole] = "ac_pos_x";
for (int i = 0; i < MAX_ENTITIES; i++) // MAX_ENTITIES is defined in interface.h
{
AcDataObject *data_object = new AcDataObject( i,
&fdata_ac_.ac_live[i], // fdata_ac_ is the C struct/Fortran common block defined in interface.h
&fdata_ac_.ac_pos_x[i] );
data_list.append(data_object);
}
}
int AcDataModel::rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
return data_list.count();
}
QVariant AcDataModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
if(row < 0 || row >= data_list.count()) {
return QVariant();
}
const AcDataObject *data_object = data_list.at(row);
switch(role) {
case IdRole: return data_object->entity_id;
case LiveRole: return *(data_object->ac_live);
case PosXRole: return *(data_object->ac_pos_x);
}
return QVariant();
}
QHash<int, QByteArray> AcDataModel::roleNames() const
{
return m_roleNames;
}
void AcDataModel::do_update() {
start_index = createIndex(0, 0);
end_index = createIndex((data_list.count() - 1), 0);
dataChanged(start_index, end_index);
}
Qt::ItemFlags AcDataModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) {return 0;}
return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
}
当模拟为运行时,每秒调用do_update()。我创建了一个带有 ListView 的测试 Gui,并将我的模型暴露给它:
摘自"threadcontrol.cpp"
acdata = new AcDataModel();
viewer = new QtQuick2ApplicationViewer();
viewer->rootContext()->setContextProperty("acdata", acdata);
viewer->setMainQmlFile(QStringLiteral("../lib/qml_gui/main.qml"));
viewer->showExpanded();
(此代码是控制不同线程的较大文件的一部分。我很确定其余部分与实际问题无关,而且这个问题真的很长...)
所以终于有了 main.qml。它包含一个包含 MAX_ENTITIES 元素的列表,每个元素都包含用于显示我的数据的文本字段。我还添加了一个 Connections 元素来检查 Gui 是否接收到 dataChanged 信号。
"main.qml"
ListView {
id: listviewer
model: acdata
delegate: Rectangle {
/* ... some formatting stuff like height etc ... */
Row {
anchors.fill: parent
Text {
/* ... formatting stuff ... */
text: model.entity_id
}
Text {
/* ... formatting stuff ... */
text: model.ac_live
}
Text {
/* ... formatting stuff ... */
text: model.ac_pos_x
}
}
}
Connections {
target: listviewer.model // EDIT: I drew the wrong conclusions here, see text below!
onDataChanged: {
console.log("DataChanged received")
}
}
}
当运行模拟时,每秒打印"DataChanged received"消息
编辑: 我在这里连接到 ListModel 而不是 ListView,尽管 ListView 必须接收 dataChanged 信号。由于控制台日志在连接到 listviewer 时不起作用,我可能错过了 listView 和 dataChanged 信号之间的连接。但是,我认为这应该在实现 dataChanged 信号时自动工作?
附加信息:我发现了一个带有 Qt Map 的 similar problem here,它实际上似乎是一个已在 Qt 5.6 中修复的错误。然而,运行 Qt 5.7 的 qmake 没有解决我的问题。
您不能在 class 中声明 dataChanged()
信号,因为您要发出信号 AbstractItemModel::dataChanged()
。如果您重新声明它,您会添加一个完全不同的新信号,该信号在任何地方都没有连接。如果您删除 acdata.h 中的声明,一切都会正常。
我正在尝试为大型动态 C/Fortran 模拟编写 QML Gui。我要显示的数据存储在 Fortran Common 块中,并按固定时间步长更新。我的问题是 QML ListView 在每个时间步后发出 dataChanged 信号时不会刷新,尽管 Gui 接收到该信号(测试在下面的代码中)。
我可能错过了一些非常明显的东西,因为当我再次向下和向上轻弹我的 ListView 时,显示的数据已更新且正确(我猜是因为 QML 引擎 re-renders 元素在获得 "out of sight" 并再次返回)。所以唯一不起作用的是每次收到 dataChanged 信号时 ListView 都会更新,而不仅仅是当它是 re-rendered 时。下面是对我的方法和相关代码部分的更详细描述。
每个模拟实体都有几个属性(活动、位置...),因此我决定为每个实体创建一个包含 DataObject 的 ListModel。这是对应的header文件(实际模拟数据在"interface.h"中声明为extern structs,所以我可以通过指针访问它):
"acdata.h"
#include <QtCore>
#include <QObject>
#include <QtGui>
extern "C" {
#include "interface.h"
}
class AcDataObject : public QObject
{
Q_OBJECT
public:
explicit AcDataObject(int id_, int *pac_live, double *pac_pos_x, QObject *parent = 0) :
QObject(parent)
{
entity_id = id_;
ac_live = pac_live;
ac_pos_x = pac_pos_x;
}
int entity_id;
int *ac_live;
double *ac_pos_x;
};
class AcDataModel : public QAbstractListModel
{
Q_OBJECT
public:
enum RoleNames {
IdRole = Qt::UserRole,
LiveRole = Qt::UserRole + 1,
PosXRole = Qt::UserRole + 2
};
explicit AcDataModel(QObject *parent = 0);
virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
Q_INVOKABLE Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
void do_update();
protected:
virtual QHash<int, QByteArray> roleNames() const;
private:
QList<AcDataObject*> data_list;
QHash<int, QByteArray> m_roleNames;
QModelIndex start_index;
QModelIndex end_index;
signals:
void dataChanged(const QModelIndex &start_index, const QModelIndex &end_index);
};
与 header 一样,.cpp 文件也改编自 Qt5 Cadaques Book here 中的内容,除了我的构造函数遍历所有模拟实体以设置指针。此外,还有 do_update 函数为整个列表发出 dataChanged 信号。
"acdata.cpp"
#include "acdata.h"
AcDataModel::AcDataModel(QObject *parent) :
QAbstractListModel(parent)
{
m_roleNames[IdRole] = "entity_id";
m_roleNames[LiveRole] = "ac_live";
m_roleNames[PosXRole] = "ac_pos_x";
for (int i = 0; i < MAX_ENTITIES; i++) // MAX_ENTITIES is defined in interface.h
{
AcDataObject *data_object = new AcDataObject( i,
&fdata_ac_.ac_live[i], // fdata_ac_ is the C struct/Fortran common block defined in interface.h
&fdata_ac_.ac_pos_x[i] );
data_list.append(data_object);
}
}
int AcDataModel::rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
return data_list.count();
}
QVariant AcDataModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
if(row < 0 || row >= data_list.count()) {
return QVariant();
}
const AcDataObject *data_object = data_list.at(row);
switch(role) {
case IdRole: return data_object->entity_id;
case LiveRole: return *(data_object->ac_live);
case PosXRole: return *(data_object->ac_pos_x);
}
return QVariant();
}
QHash<int, QByteArray> AcDataModel::roleNames() const
{
return m_roleNames;
}
void AcDataModel::do_update() {
start_index = createIndex(0, 0);
end_index = createIndex((data_list.count() - 1), 0);
dataChanged(start_index, end_index);
}
Qt::ItemFlags AcDataModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) {return 0;}
return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
}
当模拟为运行时,每秒调用do_update()。我创建了一个带有 ListView 的测试 Gui,并将我的模型暴露给它:
摘自"threadcontrol.cpp"
acdata = new AcDataModel();
viewer = new QtQuick2ApplicationViewer();
viewer->rootContext()->setContextProperty("acdata", acdata);
viewer->setMainQmlFile(QStringLiteral("../lib/qml_gui/main.qml"));
viewer->showExpanded();
(此代码是控制不同线程的较大文件的一部分。我很确定其余部分与实际问题无关,而且这个问题真的很长...)
所以终于有了 main.qml。它包含一个包含 MAX_ENTITIES 元素的列表,每个元素都包含用于显示我的数据的文本字段。我还添加了一个 Connections 元素来检查 Gui 是否接收到 dataChanged 信号。
"main.qml"
ListView {
id: listviewer
model: acdata
delegate: Rectangle {
/* ... some formatting stuff like height etc ... */
Row {
anchors.fill: parent
Text {
/* ... formatting stuff ... */
text: model.entity_id
}
Text {
/* ... formatting stuff ... */
text: model.ac_live
}
Text {
/* ... formatting stuff ... */
text: model.ac_pos_x
}
}
}
Connections {
target: listviewer.model // EDIT: I drew the wrong conclusions here, see text below!
onDataChanged: {
console.log("DataChanged received")
}
}
}
当运行模拟时,每秒打印"DataChanged received"消息
编辑: 我在这里连接到 ListModel 而不是 ListView,尽管 ListView 必须接收 dataChanged 信号。由于控制台日志在连接到 listviewer 时不起作用,我可能错过了 listView 和 dataChanged 信号之间的连接。但是,我认为这应该在实现 dataChanged 信号时自动工作?
附加信息:我发现了一个带有 Qt Map 的 similar problem here,它实际上似乎是一个已在 Qt 5.6 中修复的错误。然而,运行 Qt 5.7 的 qmake 没有解决我的问题。
您不能在 class 中声明 dataChanged()
信号,因为您要发出信号 AbstractItemModel::dataChanged()
。如果您重新声明它,您会添加一个完全不同的新信号,该信号在任何地方都没有连接。如果您删除 acdata.h 中的声明,一切都会正常。