Please nominate your Qt Champions for 2021! https://forum.qt.io/topic/132134/looking-for-the-2021-qt-champions

QML ListView and ListElement - initialization that is actually of use



  • I have a ListView. It displays assorted information, including the current date and time. Or it would if QML would allow for a ListElement to be initialized with the values. And when I say, current date and time, I mean that it is intended to actively update. No, I do NOT want to have a timer update the value within the specific module because the value will be used other places and it would be inefficient to have multiple timers firing off for the same purpose.

    So, I have a data module, DataAccess. It has routines to calculate and format the current data and time and updates two strings to hold those values. I have hard coded these objects to the screen that also has the ListView as Text objects with the values bound to the text field. When I do this, they update as expected. But I need this to work IN the ListView. I need to bind this behavior to the specified ListElements. But binding() requires a function and the ListElement field is a string and an error is thrown that a function cannot be assigned to a string, even though all this function does is return the appropriate string.

    Is there a way to tell QML that the value returned is in fact a string? Is there a way to bind to that specific string? Because even DataAccess.currentDate, (a string) in the initialization of the ListElement throws an error about not being able to use a script to initialize the element.

    Yes, I'm stuck with 5.15. If 6 fixes a lot of this, that would be great, but of no use to me.

    A requisite code sample.

    From DataAccess.qml

    // The currentTIme is updated every second. The currentDate, if the date changes
    // The Timer is local to the DataAccess module.
    property string currentDate
    property string currentTime
    

    Tried this in the ListModel of where the dynamic values are needed.

            ListElement { theTitle: "UTC Date"; theRightString: DataAccess.currentDate }
            ListElement { theTitle: "UTC Time"; theRightString: DataAccess.currentTime }
    

    Tried this in the onVisibleChanged handler where it is needed.

            ciModel.setProperty(ConsoleInfo.CInfoEntries.CIUTCDate, "theRightString",
                                                    Qt.binding( function() { return DataAccess.currentDate }))
            ciModel.setProperty(ConsoleInfo.CInfoEntries.CIUTCTime, "theRightString",
                                                    Qt.binding( function() { return DataAccess.currentTime }))


  • Usually for this, you need to create c++ QAbstractListViewModel in which you can place pointers to QObject type, where you can declare Q_PROPERTY, and then qml ListView delegate will be refreshed automatically, when qproperty is changed. But if you want to do this in qml, with ListModel, there are few ways how to do this, here are some examples

    1. If you are sure that all elements have changed, or you do not care that all elements will be recreated, just call modelChanged, this will cause a complete recreating and redrawing of all content. Example:
        Timer {
            id: updateTimer
    
            interval: 1000
            repeat: true
            running: true
    
            onTriggered: {
                for (let i = 0; i < listView.model.count; i++) {
                    listView.model.setProperty(i, "theTitle", new Date().toUTCString())
                }
                listView.modelChanged() // or modelUpdated with changeSet
            }
        }
    
        ListView {
            id: listView
    
            anchors.centerIn: parent
    
            width: 500
            height: 300
    
            model: ListModel {
                ListElement {
                    theTitle: "UTC date"
                    theRightString: "string 1"
                }
    
                ListElement {
                    theTitle: "UTC date"
                    theRightString: "string 2"
                }
            }
    
            delegate: Item {
                width: parent.width
                height: 30
    
                Row {
                    spacing: 10
    
                    Text {
                        text: model.theTitle
                    }
                    Text {
                        text: model.theRightString
                    }
                }
            }
        }
    
    1. You can bind your timer, or some others signals directly inside delegate. This is more "smart" way, because delegate won't be recreated. Example:
    import QtQuick 2.15
    import QtQuick.Window 2.15
    
    Window {
        id: root
    
        signal someUpdateSignal()
    
        width: 640
        height: 480
        visible: true
    
        Timer {
            id: updateTimer
    
            interval: 1000
            repeat: true
            running: true
    
            onTriggered: {
                for (let i = 0; i < listView.model.count; i++) {
                    listView.model.setProperty(i, "theTitle", new Date().toUTCString())
                }
                root.someUpdateSignal()
            }
        }
    
        ListView {
            id: listView
    
            anchors.centerIn: parent
    
            width: 500
            height: 300
    
            model: ListModel {
                ListElement {
                    theTitle: "UTC date"
                    theRightString: "string 1"
                }
    
                ListElement {
                    theTitle: "UTC date"
                    theRightString: "string 2"
                }
            }
    
            delegate: Item {
                width: parent.width
                height: 30
    
                Row {
                    spacing: 10
    
                    Text {
                        id: theTitleLabel
    
                        text: model.theTitle
    
                        // Or you can connect this signal with Connections, or create property
                        Component.onCompleted: root.someUpdateSignal.connect(() => {
                                                                                theTitleLabel.text = model.theTitle
                                                                             })
                    }
                    Text {
                        id: theRightStringLabel
    
                        text: model.theRightString
    
                        Component.onCompleted: root.someUpdateSignal.connect(() => {
                                                                                theRightStringLabel.text = model.theRightString
                                                                             })
                    }
                }
            }
        }
    }
    
    1. Or you can change values directly in visible delegates. Example:
    import QtQuick 2.15
    import QtQuick.Window 2.15
    
    Window {   
        width: 640
        height: 480
        visible: true
    
        Timer {
            id: updateTimer
    
            interval: 1000
            repeat: true
            running: true
    
            onTriggered: {
                for (let i = 0; i < listView.model.count; i++) { // Refresh the model
                    listView.model.setProperty(i, "theTitle", new Date().toUTCString())
                }
    
                for (let i = 0; i < listView.children.length; i++) { // Refresh delegate properties
                    const delegateItem = listView.children[i];
                    if (delegateItem.hasOwnProperty("titleProperty")) {
                        delegateItem.titleProperty = listView.model.get(delegateItem.index).theTitle
                    }
                }
            }
        }
    
        ListView {
            id: listView
    
            anchors.centerIn: parent
    
            width: 500
            height: 300
    
            model: ListModel {
                ListElement {
                    theTitle: "UTC date"
                    theRightString: "string 1"
                }
    
                ListElement {
                    theTitle: "UTC date"
                    theRightString: "string 2"
                }
            }
    
            delegate: Item {
                property string titleProperty: model.theTitle
                property string rightProperty: model.theRightString
                readonly property int index: model.index
    
                width: parent.width
                height: 30
    
                Row {
                    spacing: 10
    
                    Text {
                        text: titleProperty
                    }
                    Text {
                        text: rightProperty
                    }
                }
            }
        }
    }
    


  • @VFCraig If I have understood correctly, an aspect of each delegate component in your list view is to show the current date and time. As you yourself allude to, the "current time" is a system-wide thing and I can't really see any benefit in trying to do this via the model. I would suggest that a better way to think about is that your delegate makes use of a visual component that displays the current date and time and that this is independent from the parts of the delegate that genuinely depend on the model (i.e. the data that is specific to the current item in the list).


Log in to reply