0

I'm making a TV-Guide on Sailfish OS, and have met an - for now - obstacle. As I want to have the possiblity to mark each entry for a customized list I need to modify the model behind. I have tried modifying the model directly: model.favorite = true but that doesn't work. I have tried to modify the underlying arrayOfObjects, but that isn't reflected in the UI, and I can't trigger an update because I can't access the ListView. I have tried to make a customized model, but since I can't reference it's instance, to no avail.

Below is a very simplified representation of the layout using mostly basic QML.

Page {
    Flickable {
        Column {
            SlideshowView { // PathView
                model: arrayOfObjects
                delegate: channelDelegate
            }
        }
    }

    Component {
        id: channelDelegate
        ListView {
            id: channelList
            // ProgramModel just iterates thru arrayOfObjects and appends them
            model: ProgramModel {
                programs: arrayOfObjects
            }
            delegate: programDelegate
            Component.onCompleted: {
                // I can't reference channelList from here.
            }
        }
    }

    Component {
        id: programDelegate
        ListItem {
            Button {
                onClicked: {
                    // How do I reference channelList?
                    // Doing it by name doesn't work.
                }
            }
        }
    }
}

I have tried calling ApplicationWindow (which works), to send a signal that I connect to in channelList.onCompleted (which also works), but since I can't reference the list from there it doesn't help.

I'm on QT 5.6 so some solutions may not work. And I would really prefer keeping it pure QML; no C++.

tanghus
  • 55
  • 3
  • 10
  • What does `arrayOfObjects` look like? – Amfasis Dec 17 '19 at 08:24
  • provide a [mre] – eyllanesc Dec 17 '19 at 12:02
  • arrayOfObjects could look like: `[{"stop":1576562400,"start":1576560600,"categories":["Kultur og Natur","Programmer"],"id":"16064655","title":"AnneMad får gæster","availableAsVod":false,"rerun":false,"premiere":false,"live":false},{"stop":1576564200,"start":1576562400,"categories":["Dokumentar","Programmer"],"id":"10137725","title":"Søren Ryge: Fuglekasser til Afrika","availableAsVod":false,"rerun":false,"premiere":false,"live":false}]` – tanghus Dec 17 '19 at 12:47
  • wrt minimal reproducible example I will have to setup a desktop environment. Currently I only have it set up for SFOS, but I'll try to have it done by the end of the day. Thanks. – tanghus Dec 17 '19 at 12:51
  • Perusing the docs I fell over [view](https://doc.qt.io/archives/qt-5.6/qml-qtquick-listview.html#view-attached-prop). I excuse my ignorance with my inexperience ;) This may solve my problem, but I will have to do some tests first. – tanghus Dec 17 '19 at 14:35
  • But it didn't. Continuing at @DaszuOne 's answer. – tanghus Dec 18 '19 at 00:06

2 Answers2

1

I can't reproduce your case exactly, but maybe this quick example help you somehow.

import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 2.2

Window {
    id: mainWrapper
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    signal updateModel(int id, string name)

    Component {
        id: listWrapper

        ListView {
            id: list

            model: [
               { name: "name0" },
               { name: "name1" },
               { name: "name2" }
           ]

            delegate: Loader {
                width: parent.width
                property variant model: modelData
                property int modelIndex: index
                sourceComponent: listDelegate
            }

            function updateModelName(id, name) {
                var listModel = list.model
                listModel[id].name = name
                list.model = listModel
            }
        }
    }

    Loader {
        id: loadedList
        x: 0
        anchors.fill: parent
        sourceComponent: listWrapper

        Component.onCompleted: {
            mainWrapper.updateModel.connect(loadedList.item.updateModelName)
        }
    }

    Component {
        id: listDelegate

        Rectangle {
            id: listRow
            width: 200
            height: 80
            Text {
                text: model.name
            }

            Button {
                x: 100
                text: "update"
                onClicked: {
                    mainWrapper.updateModel(modelIndex, "better_name"+modelIndex)
                }
            }
        }
    }
}

The clue of this example is to use signal and function to update model.

DaszuOne
  • 759
  • 1
  • 6
  • 18
  • I have tried using signals, but the problem is that all click event is inside a delegate, where I don't have access to the model. I will integrate your ideas in a reproducible example as mentioned in comments above so I can get a better overview. Thanks for now - I'll be back ;) – tanghus Dec 17 '19 at 13:02
  • 1
    Ok, we'll see when you provide more code. For now I have modified example, so buttons are delegates of a list-a little bit more close to your case :) – DaszuOne Dec 17 '19 at 13:22
  • Looking closer and testing your code, it should be doable your way, except for one thing: The `id` in the `arrayOfObjects` is hardcoded. I could replace the `id` in the model data with e.g. `channelId` and add a new `id`, I suppose. Or I could loop over the objects in the model in `updateModelName()` until I get to the correct id, as I also have that in the delegate? It would be minimal performance impact as long as only one delegate needs repainting. It seems preferable from here, rather than changing the entire model data? – tanghus Dec 18 '19 at 00:24
  • It most be too late, as I just contradicted myself in one sentence: I *do* have the id in the delegate, so there's no need to loop thru them :P That most be tested, but I guess I'd better wait till tomorrow ;) – tanghus Dec 18 '19 at 00:41
  • Consider modified example. Now list delegate is loaded component with properties "model" and "modelIndex". Of course there is more possible approaches (eg. as you mentioned iterate through model). Hope it helps you :) – DaszuOne Dec 18 '19 at 08:13
  • Very nifty also having the delegate in a Loader. Actually just putting `listWrapper` in a Loader sped up rendering time ten-fold :) – tanghus Dec 19 '19 at 09:40
0

Because the SlideshowView replaces the focus in DaszuOne's answer, i.e. switches slide, list.model = listModel causes the entire view to change. Changing a single entry in the model, doesn't trigger an update, so it would require even more code to make that happen. So after considering back and forth, I decided that doing it "the right way" would be more cumbersome and hard to read, than doing it the easy way.

I made a very simple wrapper, that can use what kind of persistent storage you use, having only two methods: setFavorite() and isFavorite.

In the delegate I update the property in onCompleted so it will be called every time the element needs to be rendered:

    Component.onCompleted: {
        favorite = favorites.isFavorite(model.channelId, model.id)
    }

My conclusion is, that DaszuOne's answer is the correct answer - but not in this case ;)

Thank you for all your help, it made me understand QML better.

tanghus
  • 55
  • 3
  • 10