Alexander Dyagilev
Alexander Dyagilev

Reputation: 1425

QML ListView is not updated on model reset

Qt 5.8, Windows 10.

Quick Controls 2 application. In QML I have a ListView with a model derived from QAbstractListModel.

In the model I have the following code:

void MediaPlaylistModel::update()
{
    beginResetModel();
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_ids = m_playlist->itemsIds();
    }
    endResetModel();
}

Nothing happens in QML view after calling this method: list is not updated. If I go back and then forward (to the page with the ListView) - it'll contain updated data. Model object instance is the same always.

Am I doing something wrong?

Update #1:

The only methods I override are:

QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;

Update #2:

C++ model code:

MediaPlaylistModel::MediaPlaylistModel(
        QSharedPointer<AbstractMediaPlaylist> playlist,
        QObject *parent) :
    base_t(parent),
    m_playlist(playlist)
{
    Q_ASSERT(m_playlist);

    connect(playlist.data(), &AbstractMediaPlaylist::changed,
            this, &MediaPlaylistModel::update);

    update();
}

QHash<int, QByteArray> MediaPlaylistModel::roleNames() const
{
    QHash<int, QByteArray> result;
    result[IdRole] = "id";
    result[TitleRole] = "title";
    result[DurationRole] = "duration";
    return result;
}

void MediaPlaylistModel::update()
{
    Q_ASSERT_SAME_THREAD;
    beginResetModel();
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_ids = m_playlist->itemsIds();
    }
    endResetModel();
}

int MediaPlaylistModel::rowCount(
        const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    std::unique_lock<std::mutex> lock(m_mutex);
    return static_cast<int>(m_ids.size());
}

QVariant MediaPlaylistModel::data(
        const QModelIndex &index,
        int role) const
{
    auto row = static_cast<size_t>(index.row());
    int id = 0;

    {
        std::unique_lock<std::mutex> lock(m_mutex);
        if (row >= m_ids.size())
            return QVariant();
        id = m_ids[row];
    }

    if (role == IdRole)
        return id;

    QVariant result;

    auto item = m_playlist->item(id);

    switch(role)
    {
    case Qt::DisplayRole:
    case TitleRole:
        result = item.title;
        break;
    case DurationRole:
        result = item.duration;
        break;
    }

    return result;
}

QML code:

import QtQuick 2.7
import QtQuick.Controls 2.0
import com.company.application 1.0

Page
{
    id : root

    property int playlistId
    property var playlistApi: App.playlists.playlist(playlistId)

    ListView
    {
        id : playlist
        anchors.fill: parent
        model: playlistApi.model
        delegate: ItemDelegate
        {
            text: model.title
            width: parent.width
            onClicked: App.player.play(playlistId, model.id)
        }
        ScrollIndicator.vertical: ScrollIndicator {}
    }
}

Update #3:

When the model is updated (one item added), something strange happens with QML ListView: in addition to fact that it's not updated (and it does not call MediaPlaylistModel::data to retrieve new items), existing items got damaged. When I click on existed item, it's model.id property is always 0. E.g. at app start its model.id was 24, after one item added its model.id became 0.

Update #4:

App.playlists.playlist(playlistId) returns pointer to this class instance:

class CppQmlPlaylistApi :
        public QObject
{
    Q_OBJECT
    Q_PROPERTY(QObject* model READ model NOTIFY modelChanged)
    Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)

public:
    explicit CppQmlPlaylistApi(
            int playlistId,
            QWeakPointer<CorePlaylistsManager> playlistsMgr,
            QObject *parent = 0);

    QObject* model() const;

    QString title() const;
    void setTitle(const QString &title);

signals:
    void modelChanged();
    void titleChanged();
    void loadPlaylistRequested(int id);

protected slots:
    void onPlaylistLoaded(int id);
    void onPlaylistRemoved(int id);

protected:
    int m_playlistId = 0;
    QWeakPointer<CorePlaylistsManager> m_playlistsMgr;
    QSharedPointer<QAbstractItemModel> m_model;
};

Upvotes: 2

Views: 6605

Answers (2)

Alexander Dyagilev
Alexander Dyagilev

Reputation: 1425

The model was in non-GUI thread. I was getting these debug messages (thanks to AlexanderVX for pointing me out):

QObject::connect: Cannot queue arguments of type 'QQmlChangeSet' 
(Make sure 'QQmlChangeSet' is registered using qRegisterMetaType().)

Moving the model object to GUI thread fixed the problem.

Upvotes: 1

Adrien Leravat
Adrien Leravat

Reputation: 2789

Your code, as provided, is good. On the QML side, as long as your model is bound, and not dynamically re-created in JS, you should be good too.

ListView {
    model: mediaPlaylistModel
}

Problems can arise if you overloaded beginResetModel or endResetModel by accident. For tests purposes, you can try to emit the QAbstractItemModel::modelReset() signal, and see if it changes anything.

It's quite easy to miss something with QAbstractItemModel, resulting in nothing working anymore !

Upvotes: 0

Related Questions