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
- 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 } } } }
- 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 }) } } } } }
- 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).