C++/QML: ListView не обновляется по сигналу dataChanged от QAbstractListModel

Я пытаюсь написать графический интерфейс QML для большой динамической симуляции C/Fortran. Данные, которые я хочу отобразить, хранятся в блоках Fortran Common и обновляются через фиксированные временные интервалы. Моя проблема заключается в том, что QML ListView не обновляется, когда сигнал dataChanged испускается после каждого временного шага, хотя сигнал получен графическим интерфейсом (тест находится в коде ниже).

Я, вероятно, упускаю что-то действительно очевидное, потому что, когда я снова и снова пролистываю свой ListView, отображаемые данные обновляются и корректируются (я думаю, потому что механизм QML повторно отображает элементы, когда они «исчезают из поля зрения» и снова) . Таким образом, единственное, что не работает, это то, что ListView обновляется каждый раз, когда принимается сигнал dataChanged, а не только при его повторном отображении. Ниже приведено более подробное описание моего подхода и соответствующих частей кода.

У каждой моделируемой сущности есть несколько атрибутов (активность, позиция...), поэтому я решил создать ListModel, содержащую DataObject для каждой сущности. Это соответствующий заголовочный файл (фактические данные моделирования объявлены как внешние структуры в «interface.h», поэтому я могу получить к ним доступ через указатель):

"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 и представил ему свою модель с помощью:

Выдержка из "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, чтобы проверить, получен ли графический интерфейсом сигнал 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 получено" печатается каждую секунду.

Изменить: здесь я подключался к ListModel, а не к ListView, хотя ListView должен получать сигнал dataChanged. Поскольку журнал консоли не работает при подключении к listviewer, я, вероятно, упускаю связь между сигналом listView и dataChanged. Однако я думаю, что это должно работать автоматически при реализации сигнала dataChanged?

Дополнительная информация: здесь я обнаружил аналогичную проблему с Qt Map, и на самом деле эта ошибка была исправлена ​​в Qt 5.6. Однако запуск qmake с Qt 5.7 не решил мою проблему.


person whittey    schedule 28.07.2016    source источник
comment
dataChanged(start_index, end_index) это не функция, это сигнал. попробуй emit dataChanged(start_index, end_index);   -  person ramtheconqueror    schedule 28.07.2016
comment
Спасибо за ваш вклад! К сожалению, это ничего не изменило. Насколько я знаю, ключевое слово emit не является обязательным, потому что dataChanged уже определен как сигнал. Однако из-за вашего комментария я понял ошибку в своем блоке соединений QML и отредактирую свой вопрос.   -  person whittey    schedule 28.07.2016


Ответы (1)


Вы не должны объявлять сигнал dataChanged() в своем классе, потому что вы хотите испускать сигнал AbstractItemModel::dataChanged(). Если вы повторно объявите его, вы добавите совершенно новый и другой сигнал, который никуда не подключен. Если вы удалите объявление в acdata.h, все должно работать нормально.

person Florian Schmidt    schedule 28.07.2016