In your QML code, you will encounter times when you will have to execute something slightly later. Qt offers a method called Qt.callLater(), using which you can schedule a function for later execution. Additionally, it also ensures that multiple calls to the same function are batched into a single call. This is really very useful in QML code. Let’s look a very simple use-case:
Rectangle {
// ....
onWidthChanged: Qt.callLater(sizeGotChanged)
onHeightChanged: Qt.callLater(sizeGotChanged)
function sizeGotChanged() {
// This function gets called whenever width
// or height changes.
}
// ....
}
Here, the function sizeGotChanged() gets called whenever either width or height changes for whatever reason. If they both get changed back to back, then sizeGotChanged() is callLater()‘ed twice, but this will result in a single call to sizeGotChanged() at some point in the very near future.
I routinely use this mechanism to delay loading of heavy delegates in ListViews. For example, consider that we have a ListView which hooks up to a model and makes use of heavy delegates. By heavy, I mean a delegate that takes considerable amount of time (30ms+) to instantiate. Such delegates cause scrolling in ListView to get slow and thereby makes the view sluggish to use. Instead of loading the whole delegate, we could load only critical parts of the delegate upon instantiation and load non-critical parts later. For example:
ListView {
// ....
model: myModel
delegate: myDelegate
// ...
}
Component {
id: myDelegate
Item {
id: lightPart
// ...
Loader {
active: false
anchors.fill: parent
sourceComponent: Item {
id: heavyPart
// ...
}
function initialize() { active = true }
Component.onCompleted: Qt.callLater(initialize)
// ....
}
// ...
}
}
In the code snippet above, you can see how the delegate is broken down into light and heavy parts. The light part is loaded instantly, whereas the heavy part is loaded later by a Loader item. This is accomplished by calling later the initialize() method in the Loader, which basically sets active to true.
This works great for the most part, until you realize that this causes heavy part of the delegates to get loaded when users are not scrolling the ListView as fast as callLater() is calling the initialize() method. To address this, you may want to delay call to the initialize() method by 100ms for instance. During that time if the delegate scrolls into view and scrolls out, the heavy part is not loaded. To keep the user from getting perplexed about empty looking delegates, we could load a place holder image for those 100ms while the user is busy scrolling rapidly. This is a common technique used by many web-apps and even desktop apps.

In such cases, Qt.callLater() is insufficient because it does not allow us to to specify how much later we want a method to be called. One way to address this would be to use a Timer.
ListView {
// ....
model: myModel
delegate: myDelegate
// ...
}
Component {
id: myDelegate
Item {
id: lightPart
// ...
Loader {
id: heavyPartLoader
anchors.fill: parent
active: false
sourceComponent: Item {
id: heavyPart
// ...
}
Timer {
interval: 100
running: true
repeating: false
onTriggered: heavyPartLoader.active = true
}
// ....
}
// ...
}
}
While this works, the one issue with this approach is that we have a Timer object for each delegate instance, which has no purpose once the initial 100ms have passed and the heavy part of the delegate is loaded.
Instead of creating Timer objects statically like we did here, lets create them dynamically so that they auto-destroy themselves once they invoke the required callback.
/ Filename: utils.js
function execLater(contextObject, delay, callback, args) {
var timer = Qt.createQmlObject("import QtQml 2.15; Timer { }", contextObject);
timer.interval = delay === undefined ? 100 : delay
timer.repeat = false
timer.triggered.connect(() => {
callback(args)
timer.destroy()
})
timer.start()
}
The execLater() method in this JavaScript code creates a Timer item, which self-destroys after invoking a callback upon timeout. The Timer item is created as a child of contextObject, which means that if for some reason the contextObject gets destroyed before the timeout duration, the Timer object itself gets destroyed.
Now in our QML code, we import this code into a name space and call the execLater() method. We also show a place holder image until the heavy part is actually loaded.
import "utils.js" as Utils
Component {
id: myDelegate
Item {
id: lightPart
// ...
Loader {
id: heavyPartLoader
anchors.fill: parent
active: false
sourceComponent: Item {
id: heavyPart
// ...
}
function initialize() { active = true }
Component.onCompleted: {
Utils.execLater(heavyPartLoader, 100, initialize)
}
Image {
id: placeHolderImage
anchors.fill: parent
visible: parent.status !== Loader.Ready
// ...
}
// ....
}
// ...
}
}
This approach solves three problems
- We get to schedule call to a function a stated amount of time later, in this case 100 ms.
- We get to delete from memory the Timer object once its purpose is served.
- Until the heavy part of the delegate is loaded, we get to show a place holder image, letting the user know that some content is coming.
However, multiple calls to initialize() from Utils.execLater() won’t get batched into a single call to initialize(), like it does in Qt.callLater().
Additionally if you observe carefully, we have unwittingly thrown Loader and Image objects into memory, for each instance of the delegate. These objects are not needed once the heavy part is loaded. We will now need to further refactor our code to eliminate these objects.
import "utils.js" as Utils
Component {
id: myDelegate
Item {
id: lightPart
// ...
property Item placeHolderItem
Component.onCompleted: {
placeHolderItem = placeHolderImageComponent.createObject(lightPart)
Utils.execLater(lightPart, 100, loadHeavyPart)
}
property Item heavyPartItem
function loadHeavyPart() {
heavyPartItem = heavyPartComponent.createObject(lightPart)
placeHolderItem.destroy()
}
// ...
}
}
Component {
id: heavyPartComponent
Item {
anchors.fill: parent
// ...
}
}
Component {
id: placeHolderImageComponent
Image {
anchors.fill: parent
// ...
}
}
Here, you can notice that we have created the place holder image dynamically, which means we can destroy it once its purpose is served. You can also notice how we create the heavy part dynamically without using Loader.
The code below brings all of these concepts together into a working example:
// File: utils.js
function execLater(contextObject, delay, callback, args) {
var timer = Qt.createQmlObject("import QtQml 2.15; Timer { }", contextObject);
timer.interval = delay === undefined ? 100 : delay
timer.repeat = false
timer.triggered.connect(() => {
callback(args)
timer.destroy()
})
timer.start()
}
function boundRandom(min, max) {
return min + Math.random() * (max-min)
}
// File: example.qml
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import "utils.js" as Utils
Window {
width: 640
height: 480
visible: true
ListView {
id: myView
anchors.fill: parent
model: 300
delegate: myDelegate
ScrollBar.vertical: ScrollBar { }
}
Component {
id: myDelegate
Item {
id: lightPart
width: myView.width
height: Utils.boundRandom(50, 300)
required property int index
property Item placeHolderItem
Component.onCompleted: {
placeHolderItem =
placeHolderComponent.createObject(lightPart, {"index": index})
Utils.execLater(lightPart, 1000, loadHeavyPart)
}
property Item heavyPartItem
function loadHeavyPart() {
heavyPartItem =
heavyPartComponent.createObject(lightPart, {"index": index})
placeHolderItem.destroy()
}
}
}
Component {
id: heavyPartComponent
Rectangle {
required property int index
anchors.fill: parent
color: Qt.rgba( Utils.boundRandom(0.6,0.9),
Utils.boundRandom(0.6,0.9),
Utils.boundRandom(0.6,0.9), 1)
Text {
anchors.centerIn: parent
text: "Heavy Part: " + index
font.pixelSize: 18
}
}
}
Component {
id: placeHolderComponent
Rectangle {
required property int index
anchors.fill: parent
color: Qt.rgba( Utils.boundRandom(0.6,0.9),
Utils.boundRandom(0.6,0.9),
Utils.boundRandom(0.6,0.9), 0.3)
Text {
anchors.centerIn: parent
text: "Placeholder: " + index
font.pixelSize: 18
}
}
}
}