QML ListView не обновляется при сбросе модели

QT 5.8, Windows 10.

Приложение быстрого управления 2. В QML у меня есть ListView с моделью, полученной из QAbstractListModel.

В модели у меня есть следующий код:

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

После вызова этого метода в представлении QML ничего не происходит: список не обновляется. Если я вернусь назад, а затем вперед (на страницу с ListView) - он будет содержать обновленные данные. Экземпляр объекта модели всегда один и тот же.

Я делаю что-то неправильно?

Обновление №1:

Единственные методы, которые я переопределяю:

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

Обновление №2:

Код модели С++:

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-код:

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 {}
    }
}

Обновление №3:

Когда модель обновляется (добавляется один элемент), с QML ListView происходит что-то странное: помимо того, что он не обновляется (и не вызывает MediaPlaylistModel::data для получения новых элементов), существующие элементы были повреждены< /сильный>. Когда я нажимаю на существующий элемент, его свойство model.id всегда равно 0. Например. при запуске приложения его model.id был 24, после добавления одного элемента его model.id стал 0.

Обновление №4:

App.playlists.playlist(playlistId) возвращает указатель на этот экземпляр класса:

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

person Alexander Dyagilev    schedule 26.02.2017    source источник
comment
Не предоставляется код QML и код C++, который используется для обновления модели.   -  person Alexander V    schedule 27.02.2017
comment
Добавил. (Обновление №2).   -  person Alexander Dyagilev    schedule 27.02.2017
comment
Убедитесь, что auto row = static_cast<size_t>(index.row()); правильно. Что возвращает App.playlists.playlist(playlistId)?   -  person Jonas G. Drange    schedule 27.02.2017
comment
Это правильно. App.playlists.playlist возвращает указатель на QObject. Пожалуйста, посмотрите Обновление №4.   -  person Alexander Dyagilev    schedule 27.02.2017
comment
Я смотрю на № 4, и в классе С++ нет списка воспроизведения свойств. Кстати, есть отладочные сообщения?   -  person Alexander V    schedule 28.02.2017
comment
playlist не является свойством, это метод объекта плейлистов. Я получаю следующие сообщения: QObject::connect: Не удается поставить в очередь аргументы типа 'QQmlChangeSet' (убедитесь, что 'QQmlChangeSet' зарегистрирован с помощью qRegisterMetaType().).   -  person Alexander Dyagilev    schedule 28.02.2017
comment
Я нашел причину, благодаря вам. Смотрите мой ответ :)   -  person Alexander Dyagilev    schedule 28.02.2017


Ответы (2)


Ваш код, как указано, хорош. Со стороны QML, пока ваша модель привязана, а не динамически воссоздана в JS, у вас тоже все должно быть хорошо.

ListView {
    model: mediaPlaylistModel
}

Проблемы могут возникнуть, если вы случайно перегрузили beginResetModel или endResetModel. В целях тестирования вы можете попробовать подать сигнал QAbstractItemModel::modelReset() и посмотреть, изменится ли что-нибудь.

С QAbstractItemModel довольно легко что-то упустить, в результате чего ничего больше не работает!

person Adrien Leravat    schedule 26.02.2017
comment
Нет, я не отменяю эти методы. - person Alexander Dyagilev; 27.02.2017

Модель находилась в потоке без графического интерфейса. Я получал эти отладочные сообщения (спасибо AlexanderVX за указание на меня):

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

Перемещение объекта модели в поток GUI устранило проблему.

person Alexander Dyagilev    schedule 27.02.2017