Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am trying to write a QML Gui for a large dynamic C/Fortran simulation. The data I want to display is stored in Fortran Common blocks and updated on fixed time steps. My problem is that QML ListView does not refresh when the dataChanged signal is emitted after each time step, although the signal is received by the Gui (test is in the code below).

I am probably missing out something really obvious because when I flick my ListView down and up again, the displayed data is updated and correct (I guess because the QML engine re-renders elements when they get "out of sight" and back in again). So the only thing that does not work is that the ListView gets updated every time the dataChanged signal is received and not only when it is re-rendered. Below is a more detailed description of my approach and the relevant code parts.

Each simulation entity has several attributes (alive, position...), so I decided to create a ListModel containing a DataObject for each entity. This is the corresponding header file (the actual simulation data is declared as extern structs in "interface.h", so I can access it via pointer):

"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);
};

Like the header, the .cpp file is also adapted from what you can find in the Qt5 Cadaques Book here, except that my constructor iterates over all simulation entities to set the pointers. Additionally, there is the do_update function that emits the dataChanged signal for the whole list.

"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);
}

When the simulation is running, do_update() is called every second. I have created a test Gui with a ListView and exposed my model to it with:

Excerpt from "threadcontrol.cpp"

acdata = new AcDataModel();
viewer = new QtQuick2ApplicationViewer();

viewer->rootContext()->setContextProperty("acdata", acdata);
viewer->setMainQmlFile(QStringLiteral("../lib/qml_gui/main.qml"));
viewer->showExpanded();

(This code is part of a larger file that controls the different threads. I am quite sure the rest is not relevant to the actual problem and this question is getting really long...)

So finally there is main.qml. It contains a list with MAX_ENTITIES elements and each elements holds text fields to display my data. I have also added a Connections element to check if the dataChanged signal is received by the Gui.

"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")
        }
    }
}

When running the simulation, the "DataChanged received" message is printed every second.

Edit: I was connecting to the ListModel and not to the ListView here, although the ListView has to receive the dataChanged signal. As the console log does not work when connecting to listviewer, I am probably missing the connection between listView and dataChanged signal. However, I think this should work automatically when implementing the dataChanged signal?

Additional information: I have found a similar problem here with Qt Map and it actually seemed to be a bug that was fixed in Qt 5.6. However, running qmake with Qt 5.7 did not fix my problem.

like image 883
whittey Avatar asked Jul 28 '16 08:07

whittey


1 Answers

You mustn't declare the dataChanged() signal in your class, because you want to emit the signal AbstractItemModel::dataChanged(). If you re-declare it you add a comleptely new and different Signal that is not connected anywhere. If you remove the declaration in acdata.h everything should work fine.

like image 113
Florian Schmidt Avatar answered Sep 29 '22 09:09

Florian Schmidt