C++/QML:ListView未根据来自QAbstractListModel的dataChanged信号进行更新

C++/QML: ListView is not updated on dataChanged signal from QAbstractListModel

本文关键字:dataChanged QAbstractListModel 信号 更新 QML ListView C++      更新时间:2023-10-16

我正试图为大型动态C/Fortran模拟编写QML Gui。我要显示的数据存储在Fortran Common块中,并按固定的时间步长进行更新。我的问题是,当dataChanged信号在每个时间步长后发出时,QML ListView不会刷新,尽管信号是由Gui接收的(测试在下面的代码中)。

我可能错过了一些非常明显的东西,因为当我再次向下和向上滑动ListView时,显示的数据是更新和正确的(我想是因为QML引擎在元素"看不见"并再次出现时会重新渲染它们)。因此,唯一不起作用的是,每次接收到dataChanged信号时,ListView都会更新,而不仅仅是在重新渲染时。下面是对我的方法和相关代码部分的更详细描述。

每个模拟实体都有几个属性(活动、位置…),所以我决定创建一个ListModel,其中包含每个实体的DataObject。这是相应的头文件(实际的模拟数据在"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);
};

与标题一样,.cpp文件也改编自您在Qt5 Cadaques Book中可以找到的内容,只是我的构造函数在所有模拟实体上迭代以设置指针。此外,还有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中发现了一个类似的问题,实际上它似乎是Qt 5.6中修复的一个错误。然而,使用Qt 5.7运行qmake并没有解决我的问题。

您不能在类中声明dataChanged()信号,因为您想要发出信号AbstractItemModel::dataChanged()。如果你重新声明它,你会添加一个全新的、不同的信号,它在任何地方都没有连接。如果删除acdata.h中的声明,一切都会正常工作。