Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

ListView not Updated properly



  • Hello,

    this is my model.cpp

    /******************************************************************************
     *
     * Copyright (C) 2018-2019 Marton Borzak <hello@martonborzak.com>
     *
     * This file is part of the YIO-Remote software project.
     *
     * YIO-Remote software is free software: you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation, either version 3 of the License, or
     * (at your option) any later version.
     *
     * YIO-Remote software is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with YIO-Remote software. If not, see <https://www.gnu.org/licenses/>.
     *
     * SPDX-License-Identifier: GPL-3.0-or-later
     *****************************************************************************/
    
    #include "albummodel_mediaplayer.h"
    
    ListModel::ListModel(QObject *parent) :
        QAbstractListModel(parent)
    {
    }
    
    int ListModel::rowCount(const QModelIndex &parent) const
    {
        Q_UNUSED(parent)
        return m_data.size();
    }
    
    QVariant ListModel::data(const QModelIndex &index, int role) const
    {
        if (index.row() < 0 || index.row() >= m_data.count()) {
            return QVariant();
        }
        const ModelItem &item = m_data[index.row()];
        switch (role) {
            case KeyRole:
                return item.item_key();
            case TitleRole:
                return item.item_title();
            case SubTitleRole:
                return item.item_subtitle();
            case TypeRole:
                return item.item_type();
            case ImageUrlRole:
                return item.item_imageUrl();
            case CommandsRole:
                return item.item_commands();
        }
        return QVariant();
    }
    
    QHash<int, QByteArray> ListModel::roleNames() const
    {
        QHash<int, QByteArray> roles;
        roles[KeyRole] = "item_key";
        roles[TitleRole] = "item_title";
        roles[SubTitleRole] = "item_subtitle";
        roles[TypeRole] = "item_type";
        roles[ImageUrlRole] = "item_image";
        roles[CommandsRole] = "item_commands";
        return roles;
    }
    
    void ListModel::append(const ModelItem &modelItem)
    {
        const int i = m_data.size();
        beginInsertRows(QModelIndex(), i, i);
        m_data.append(modelItem);
        endInsertRows();
    }
    
    void BrowseModel::addItem(const QString &key, const QString &title, const QString &subtitle, const QString &type,
                              const QString &imageUrl, const QVariant &commands) {
        ListModel *model = static_cast<ListModel *>(m_model);
        const ModelItem item = ModelItem(key, title, subtitle, type, imageUrl, commands);
        model->append(item);
        emit modelChanged();
    }
    
    void BrowseModel::clearItems()
    {
        if(m_model) {
            delete m_model;
        }
    
        m_model = new ListModel();
    
        emit modelChanged();
    }
    
    void BrowseModel::clearProperties()
    {
        m_id.clear();
        m_title.clear();
        m_subtitle.clear();
        m_type.clear();
        m_imageUrl.clear();
        m_commands.clear();
    }
    
    

    this is my qml file

    /******************************************************************************
     *
     * Copyright (C) 2018-2019 Marton Borzak <hello@martonborzak.com>
     *
     * This file is part of the YIO-Remote software project.
     *
     * YIO-Remote software is free software: you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation, either version 3 of the License, or
     * (at your option) any later version.
     *
     * YIO-Remote software is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with YIO-Remote software. If not, see <https://www.gnu.org/licenses/>.
     *
     * SPDX-License-Identifier: GPL-3.0-or-later
     *****************************************************************************/
    import QtQuick 2.12
    import QtQuick.Controls 2.12
    import Style 1.0
    
    import Haptic 1.0
    
    import "qrc:/basic_ui" as BasicUI
    
    Rectangle {
        id: main
        width: parent.width; height: parent.height
        color: Style.color.dark
        radius: Style.cornerRadius
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // VARIABLES
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        property var playListBrowseModel
        property bool isCurrentItem: parent._currentItem
        property bool start: true
        property bool filtertagplaylist: false
        property string filterString: "All"
    
        onIsCurrentItemChanged: {
            if (isCurrentItem && start) {
                console.debug("LOAD USER PLAYLISTS");
                start = false;
                obj.browseModelChanged.connect(onFirstLoadComplete);
                obj.getPlaylist("user");
            }
            if (!isCurrentItem) {
                playListListView.contentY = 0-120;
            }
        }
    
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // FUNCTIONS
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        function onFirstLoadComplete(model) {
            obj.browseModelChanged.disconnect(onFirstLoadComplete);
            main.playListBrowseModel = model.model;
        }
    
        function load(album, type) {
            swipeView.currentIndex++;
            if (type === "playlist") {
                obj.browseModelChanged.connect(onBrowseModelChanged);
                obj.getPlaylist(album);
            }
        }
    
        function onBrowseModelChanged(model) {
            if (playlistLoader) {
                if (playlistLoader.source != "qrc:/components/media_player/ui/AlbumView.qml")
                    playlistLoader.setSource("qrc:/components/media_player/ui/AlbumView.qml", { "albumModel": model })
                else if (playlistLoader.item) {
                    playlistLoader.item.albumModel = model;
                    playlistLoader.item.itemFlickable.contentY = 0;
                }
            }
            obj.browseModelChanged.disconnect(onBrowseModelChanged);
    
        }
    
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // UI ELEMENTS
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        property alias swipeView: swipeView
    
        SwipeView {
            id: swipeView
            width: parent.width; height: parent.height
            currentIndex: 0
            interactive: false
            clip: true
    
            Item {
                property alias playListListView: playListListView
                Component {
                        id: componentTagFilterPlaylist
    
                        BasicUI.Tag {
                            id: tagFilterPlaylist
                            tag: "playlist"
                            selected: false
    
                            MouseArea {
                                anchors.fill: parent
                                onClicked: {
                                                if (selected) {
                                                    selected = false;
                                                    filterString = "All"
                                                    tagFilterAll.selected = true;
                                                    playListListView.update();
                                                } else {
                                                    filterString = "Playlist"
                                                    tagFilterAll.selected = false;
                                                    playListListView.update();
                                                    selected = true;
                                                }
                                            }
                            }
                        }
                }
                Row {
                    id: tagRepeaterRow
    
                    BasicUI.Tag {
                        id: tagFilterAll
                        tag: "All"
                        selected:true
                        MouseArea {
                            anchors.fill: parent
                            onClicked: {
                                if (!tagFilterAll.selected) {
                                    tagFilterAll.selected = true;
                                    filterString = "All"
                                }
                            }
                         }
                    }
    
                    Repeater {
                        id: tagRepeater
                        model: main.playListBrowseModel
    
    
                        Loader {
                            id: filterLoader
                            sourceComponent:
                                if (item_type === "playlist"){
                                    if (!filtertagplaylist) {
                                        filtertagplaylist = true;
                                        return componentTagFilterPlaylist
                                    }
                                }
                        }
                    }
                }
    
    
    
                ListView {
                    id: playListListView
                    width: parent.width; height: parent.height-100
                    spacing: 20
                    anchors { top: tagRepeaterRow.bottom; horizontalCenter: parent.horizontalCenter }
                    maximumFlickVelocity: 6000
                    flickDeceleration: 1000
                    boundsBehavior: Flickable.DragAndOvershootBounds
                    flickableDirection: Flickable.VerticalFlick
                    clip: true
                    cacheBuffer: 3000
    
                    delegate: playListThumbnail
                    model: main.playListBrowseModel
    
                    ScrollBar.vertical: ScrollBar {
                        opacity: 0.5
                    }
    
                    header: Component {
                        Item {
                            width: parent.width; height: 120
    
                            Text {
                                id: title
                                color: Style.color.text
                                text: qsTr("My playlists") + translateHandler.emptyString
                                font { family: "Open Sans Bold"; weight: Font.Bold; pixelSize: 40 }
                                lineHeight: 1
                                anchors { left: parent.left; leftMargin: 30; top: parent.top; topMargin: 30 }
                            }
                        }
                    }
    
                    populate: Transition {
                        id: popTransition
                        SequentialAnimation {
                            PropertyAction { property: "opacity"; value: 0 }
                            PauseAnimation { duration: popTransition.ViewTransition.index*100 }
                            NumberAnimation { properties: "opacity"; from: 0; to: 1; duration: 300; easing.type: Easing.InExpo }
                        }
                    }
                }
    
                Component {
                    id: playListThumbnail
    
                    Item {
                        id: trackThumbnailItem
                        width: parent.width-60; height: 80
                        anchors.horizontalCenter: parent.horizontalCenter
                        visible:
                            if (filterString === "Playlist") {
                                return true;
                            } else if (filterString === "All") {
                                return true;
                            } else {
                                return false;
                            }
    
                        Rectangle {
                            id: albumImage
                            width: 80; height: 80
    
                            Image {
                                source: item_image
                                anchors.fill: parent
                                fillMode: Image.PreserveAspectCrop
                                asynchronous: true
                            }
                        }
    
                        Text {
                            id: albumTitleText
                            text: item_title
                            elide: Text.ElideRight
                            width: itemFlickable.width-60-albumImage.width-20-80
                            wrapMode: Text.NoWrap
                            color: Style.color.text
                            anchors { left: albumImage.right; leftMargin: 20; top: albumImage.top; topMargin: item_subtitle == "" ? 26 : 12 }
                            font { family: "Open Sans Regular"; pixelSize: 25 }
                            lineHeight: 1
                        }
    
                        Text {
                            id: albumSubTitleText
                            text: item_subtitle
                            elide: Text.ElideRight
                            visible: item_subtitle == "" ? false : true
                            width: albumTitleText.width
                            wrapMode: Text.NoWrap
                            color: Style.color.text
                            opacity: 0.6
                            anchors { left: albumTitleText.left; top: albumTitleText.bottom; topMargin: 5 }
                            font { family: "Open Sans Regular"; pixelSize: 20 }
                            lineHeight: 1
                        }
    
                        MouseArea {
                            anchors.fill: parent
    
                            onClicked: {
                                Haptic.playEffect(Haptic.Click);
                                load(item_key, item_type);
                            }
                        }
    
                        BasicUI.ContextMenuIcon {
                            anchors { right: parent.right; verticalCenter: parent.verticalCenter }
    
                            mouseArea.onClicked: {
                                Haptic.playEffect(Haptic.Click);
                                contextMenuLoader.setSource("qrc:/basic_ui/ContextMenu.qml", { "width": itemFlickable.width, "id": item_key, "type": item_type, "list": item_commands })
                            }
                        }
                    }
                }
            }
    
            Item {
    
                Loader {
                    id: playlistLoader
                    asynchronous: true
                    anchors.fill: parent
                }
    
                Text {
                    id: backButton
                    color: Style.color.text
                    text: Style.icon.left_arrow
                    renderType: Text.NativeRendering
                    width: 70; height: 70
                    verticalAlignment: Text.AlignVCenter; horizontalAlignment: Text.AlignHCenter
                    font {family: "icons"; pixelSize: 80 }
                    anchors { left: parent.left; leftMargin: 10; top: parent.top; topMargin: 20 }
    
                    MouseArea {
                        id: backButtonMouseArea
                        width: parent.width + 20; height: parent.height + 20
                        anchors.centerIn: parent
    
                        onClicked: {
                            Haptic.playEffect(Haptic.Click);
                            swipeView.currentIndex = 0;
                        }
                    }
                }
            }
        }
    }
    
    

    and the album.qml file

    /******************************************************************************
     *
     * Copyright (C) 2018-2019 Marton Borzak <hello@martonborzak.com>
     *
     * This file is part of the YIO-Remote software project.
     *
     * YIO-Remote software is free software: you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation, either version 3 of the License, or
     * (at your option) any later version.
     *
     * YIO-Remote software is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with YIO-Remote software. If not, see <https://www.gnu.org/licenses/>.
     *
     * SPDX-License-Identifier: GPL-3.0-or-later
     *****************************************************************************/
    
    import QtQuick 2.12
    import QtQuick.Controls 2.12
    import Style 1.0
    
    import Haptic 1.0
    import MediaPlayerUtils 1.0
    
    import "qrc:/basic_ui" as BasicUI
    
    Rectangle {
        id: main
        width: parent.width; height: parent.height
        color: mediaplayerUtils.pixelColor
        radius: Style.cornerRadius
    
        Behavior on color {
            ColorAnimation { duration: 300 }
        }
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // VARIABLES
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        property var albumModel
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // UTILITIES
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        MediaPlayerUtils {
            id: mediaplayerUtils
            imageURL: albumModel.imageUrl
        }
    
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // UI ELEMENTS
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
        property alias itemFlickable: itemFlickable
    
        Flickable {
            id: itemFlickable
            width: parent.width; height: parent.height-100
            maximumFlickVelocity: 6000
            flickDeceleration: 1000
            contentHeight: 150 + image.height + title.height + artist.height + trackListView.height
            boundsBehavior: Flickable.DragAndOvershootBounds
            flickableDirection: Flickable.VerticalFlick
            clip: true
    
            Behavior on contentY {
                PropertyAnimation {
                    duration: 300
                    easing.type: Easing.OutExpo
                }
            }
    
            ScrollBar.vertical: ScrollBar {
                opacity: 0.5
            }
    
    
            BasicUI.CustomImageLoader {
                id: image
                width: 280; height: 280
                anchors { horizontalCenter: parent.horizontalCenter; top: parent.top; topMargin: 100 }
                url: albumModel.imageUrl === "" ? "qrc:/images/mini-music-player/no_image.png" : albumModel.imageUrl
            }
    
            Text {
                color: Style.color.text
                text: Style.icon.play
                renderType: Text.NativeRendering
                width: 70; height: 70
                verticalAlignment: Text.AlignVCenter; horizontalAlignment: Text.AlignHCenter
                font {family: "icons"; pixelSize: 80 }
                anchors.centerIn: image
    
                MouseArea {
                    width: parent.width + 20; height: parent.height + 20
                    anchors.centerIn: parent
                    onClicked: {
                        Haptic.playEffect(Haptic.Click);
                        obj.playMedia(albumModel.id, albumModel.type);
                    }
                }
            }
    
            Text {
                id: title
                color: Style.color.text
                text: albumModel.title
                horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter
                elide: Text.ElideRight
                wrapMode: Text.NoWrap
                width: parent.width-80
                font { family: "Open Sans Bold"; weight: Font.Bold; pixelSize: 30 }
                lineHeight: 1
                anchors { horizontalCenter: parent.horizontalCenter; top: image.bottom; topMargin: 20 }
            }
    
            Text {
                id: artist
                color: Style.color.text
                text: albumModel.subtitle
                horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter
                elide: Text.ElideRight
                wrapMode: Text.NoWrap
                width: parent.width-80
                font: Style.font.button
                anchors { top: title.bottom; horizontalCenter: parent.horizontalCenter }
            }
    
            ListView {
                id: trackListView
                width: parent.width-60; height: childrenRect.height
                spacing: 20
                interactive: false
                anchors { top: artist.bottom; topMargin: 40; horizontalCenter: parent.horizontalCenter }
    
                model: albumModel.model
                delegate: trackThumbnail
            }
    
            Component {
                id: trackThumbnail
    
                Item {
                    width: parent.width; height: 80
    
                    Text {
                        id: trackNumber
                        text: index+1
                        color: Style.color.text
                        anchors { left: parent.left; verticalCenter: parent.verticalCenter }
                        font { family: "Open Sans Regular"; pixelSize: 25 }
                        lineHeight: 1
                    }
    
                    Text {
                        id: albumTitleText
                        text: item_title
                        elide: Text.ElideRight
                        width: parent.width-100
                        wrapMode: Text.NoWrap
                        color: Style.color.text
                        anchors { left: parent.left; leftMargin: 45; top: parent.top }
                        font { family: "Open Sans Regular"; pixelSize: 25 }
                        lineHeight: 1
                    }
    
                    Text {
                        id: albumSubTitleText
                        text: item_subtitle
                        elide: Text.ElideRight
                        width: albumTitleText.width
                        wrapMode: Text.NoWrap
                        color: Style.color.text
                        opacity: 0.6
                        anchors { left: albumTitleText.left; top: albumTitleText.bottom; topMargin: 5 }
                        font { family: "Open Sans Regular"; pixelSize: 20 }
                        lineHeight: 1
                    }
    
                    MouseArea {
                        anchors.fill: parent
                        onClicked: {
                            Haptic.playEffect(Haptic.Click);
                            obj.playMedia(item_key, item_type);
                        }
                    }
    
                    BasicUI.ContextMenuIcon {
                        colorBg: mediaplayerUtils.pixelColor
                        anchors { right: parent.right; verticalCenter: parent.verticalCenter }
    
                        mouseArea.onClicked: {
                            Haptic.playEffect(Haptic.Click);
                            contextMenuLoader.setSource("qrc:/basic_ui/ContextMenu.qml", { "width": main.width, "id": item_key, "type": item_type, "list": item_commands })
                        }
                    }
                }
            }
        }
    }
    
    

    When i call

    m_userPlaylistBrowseModel->clearItems();
                m_userPlaylistBrowseModel->clearProperties();
    m_userPlaylistBrowseModel->addItem(playlists[i].toMap().value("id").toString(),
                                   playlists[i].toMap().value("name").toString(), "", type, image, commands);
    

    The model is not updated.

    Any help appriciated :)



  • You have to call the functions that trigger signals ListView will look for. For a clear of the data you have to do the beginresetmodel and endresetmodel. For the additem you have to use the begin/endinsertrows functions.

    https://doc.qt.io/qt-5/qabstractitemmodel.html#protected-functions

    Look for examples of how to build a model that shows how to do this. I think those are referenced in the model/view docs.



  • @fcarney

    I changed is

    /******************************************************************************
     *
     * Copyright (C) 2018-2019 Marton Borzak <hello@martonborzak.com>
     *
     * This file is part of the YIO-Remote software project.
     *
     * YIO-Remote software is free software: you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation, either version 3 of the License, or
     * (at your option) any later version.
     *
     * YIO-Remote software is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with YIO-Remote software. If not, see <https://www.gnu.org/licenses/>.
     *
     * SPDX-License-Identifier: GPL-3.0-or-later
     *****************************************************************************/
    
    #include "albummodel_mediaplayer.h"
    
    ListModel::ListModel(QObject *parent) :
        QAbstractListModel(parent)
    {
    }
    
    int ListModel::rowCount(const QModelIndex &parent) const
    {
        Q_UNUSED(parent)
        return m_data.size();
    }
    
    QVariant ListModel::data(const QModelIndex &index, int role) const
    {
        if (index.row() < 0 || index.row() >= m_data.count()) {
            return QVariant();
        }
        const ModelItem &item = m_data[index.row()];
        switch (role) {
            case KeyRole:
                return item.item_key();
            case TitleRole:
                return item.item_title();
            case SubTitleRole:
                return item.item_subtitle();
            case TypeRole:
                return item.item_type();
            case ImageUrlRole:
                return item.item_imageUrl();
            case CommandsRole:
                return item.item_commands();
        }
        return QVariant();
    }
    
    QHash<int, QByteArray> ListModel::roleNames() const
    {
        QHash<int, QByteArray> roles;
        roles[KeyRole] = "item_key";
        roles[TitleRole] = "item_title";
        roles[SubTitleRole] = "item_subtitle";
        roles[TypeRole] = "item_type";
        roles[ImageUrlRole] = "item_image";
        roles[CommandsRole] = "item_commands";
        return roles;
    }
    
    void ListModel::append(const ModelItem &modelItem)
    {
        const int i = m_data.size();
        beginInsertRows(QModelIndex(), i, i);
        m_data.append(modelItem);
        endInsertRows();
    }
    
    void ListModel::clear() {
        beginResetModel();
        m_data.clear();
        endResetModel();
    }
    
    
    void BrowseModel::addItem(const QString &key, const QString &title, const QString &subtitle, const QString &type,
                              const QString &imageUrl, const QVariant &commands) {
        ListModel *model = static_cast<ListModel *>(m_model);
        const ModelItem item = ModelItem(key, title, subtitle, type, imageUrl, commands);
        model->append(item);
        emit modelChanged();
    }
    
    void BrowseModel::clearItems()
    {
        if(m_model) {
            ListModel *model = static_cast<ListModel *>(m_model);
            model->clear();
            delete m_model;
        }
    
        m_model = new ListModel();
    
        emit modelChanged();
    }
    
    void BrowseModel::clearProperties()
    {
        m_id.clear();
        m_title.clear();
        m_subtitle.clear();
        m_type.clear();
        m_imageUrl.clear();
        m_commands.clear();
    }
    
    

    But still not working


  • Lifetime Qt Champion

    What is BrowseModel ? Why do you have two models at all?



  • @Christian-Ehrlicher how to change it to one model?


Log in to reply