{"id":173,"date":"2023-01-02T13:59:07","date_gmt":"2023-01-02T13:59:07","guid":{"rendered":"https:\/\/www.vcreatelogic.com\/?p=173"},"modified":"2023-04-04T18:49:44","modified_gmt":"2023-04-04T18:49:44","slug":"schedule-a-call-for-later-in-qml","status":"publish","type":"post","link":"https:\/\/www.vcreatelogic.com\/index.php\/2023\/01\/02\/schedule-a-call-for-later-in-qml\/","title":{"rendered":"Delayed Loading Of Delegates in QML Views"},"content":{"rendered":"\n<p>In your QML code, you will encounter times when you will have to execute something slightly later. Qt offers a method called <a class=\"code-snippet\" href=\"https:\/\/doc.qt.io\/qt-6\/qml-qtqml-qt.html#callLater-method\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\" (opens in a new tab)\">Qt.callLater()<\/a>, using which you can schedule a function for <em>later<\/em> 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&#8217;s look a very simple use-case:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Rectangle {\n    \/\/ ....\n    \n    onWidthChanged: Qt.callLater(sizeGotChanged)\n    onHeightChanged: Qt.callLater(sizeGotChanged)\n\n    function sizeGotChanged() {\n        \/\/ This function gets called whenever width\n        \/\/ or height changes.\n    }\n\n    \/\/ ....\n}<\/code><\/pre>\n\n\n\n<p>Here, the function <span class=\"code-snippet\">sizeGotChanged()<\/span> gets called whenever either width or height changes for whatever reason. If they both get changed back to back, then <span class=\"code-snippet\">sizeGotChanged()<\/span> is <span class=\"code-snippet\">callLater()<\/span>&#8216;ed twice, but this will result in a single call to <span class=\"code-snippet\">sizeGotChanged()<\/span> at some point in the very near future.<\/p>\n\n\n\n<p>I routinely use this mechanism to delay loading of heavy delegates in <span class=\"code-snippet\">ListView<\/span>s. For example, consider that we have a <span class=\"code-snippet\">ListView<\/span> 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 <span class=\"code-snippet\">ListView<\/span> 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:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ListView {\n    \/\/ ....\n\n    model: myModel\n    delegate: myDelegate\n\n    \/\/ ...\n}\n\nComponent {\n    id: myDelegate \n\n    Item {\n        id: lightPart\n        \/\/ ...\n\n        Loader {\n            active: false\n            anchors.fill: parent\n            sourceComponent: Item {\n                id: heavyPart\n                \/\/ ...\n            }\n            \n            function initialize() { active = true }\n            Component.onCompleted: Qt.callLater(initialize)\n\n            \/\/ ....\n        }\n\n        \/\/ ...\n    }\n}<\/code><\/pre>\n\n\n\n<p>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 <em>later<\/em> by a Loader item. This is accomplished by calling later the <span class=\"code-snippet\">initialize()<\/span> method in the <span class=\"code-snippet\">Loader<\/span>, which basically sets <span class=\"code-snippet\">active<\/span> to <span class=\"code-snippet\">true<\/span>.<\/p>\n\n\n\n<p>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 <span class=\"code-snippet\">ListView<\/span> as fast as <span class=\"code-snippet\">callLater()<\/span> is calling the <span class=\"code-snippet\">initialize()<\/span> method. To address this, you may want to delay call to the <span class=\"code-snippet\">initialize()<\/span> 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.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/www.vcreatelogic.com\/wp-content\/uploads\/2023\/04\/image-1024x576.png\" alt=\"\" class=\"wp-image-174\" srcset=\"https:\/\/www.vcreatelogic.com\/wp-content\/uploads\/2023\/04\/image-1024x576.png 1024w, https:\/\/www.vcreatelogic.com\/wp-content\/uploads\/2023\/04\/image-300x169.png 300w, https:\/\/www.vcreatelogic.com\/wp-content\/uploads\/2023\/04\/image-768x432.png 768w, https:\/\/www.vcreatelogic.com\/wp-content\/uploads\/2023\/04\/image.png 1920w\" sizes=\"auto, (max-width: 706px) 89vw, (max-width: 767px) 82vw, 740px\" \/><figcaption><a href=\"https:\/\/www.scrite.io\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\"Scrite (opens in a new tab)\">Scrite<\/a> uses place holder images to render scenes in a screenplay, while user is rapidly scrolling. Watch this video to know more: \u201c<a rel=\"noreferrer noopener\" aria-label=\"Closing the Gaps \u2013 QML on Desktop\u201d \u2013 Qt Developer Conference (qtdevcon.com) (opens in a new tab)\" href=\"https:\/\/youtu.be\/IFHOM9cZ-u0?t=213\" target=\"_blank\">Closing the Gaps \u2013 QML on Desktop\u201d \u2013 Qt Developer Conference (qtdevcon.com)<\/a>&#8220;<\/figcaption><\/figure>\n\n\n\n<p>In such cases, <span class=\"code-snippet\">Qt.callLater()<\/span> 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 <span class=\"code-snippet\">Timer<\/span>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ListView {\n    \/\/ ....\n\n    model: myModel\n    delegate: myDelegate\n\n    \/\/ ...\n}\n\nComponent {\n    id: myDelegate \n\n    Item {\n        id: lightPart\n        \/\/ ...\n\n        Loader {\n            id: heavyPartLoader\n            anchors.fill: parent\n            active: false\n            sourceComponent: Item {\n                id: heavyPart\n                \/\/ ...\n            }\n\n            Timer {\n                interval: 100\n                running: true\n                repeating: false\n                onTriggered: heavyPartLoader.active = true\n            }\n\n            \/\/ ....\n        }\n\n        \/\/ ...\n    }\n}<\/code><\/pre>\n\n\n\n<p>While this works, the one issue with this approach is that we have a <span class=\"code-snippet\">Timer<\/span> object for each delegate instance, which has no purpose once the initial 100ms have passed and the heavy part of the delegate is loaded. <\/p>\n\n\n\n<p>Instead of creating <span class=\"code-snippet\">Timer<\/span> objects <em>statically<\/em> like we did here, lets create them dynamically so that they auto-destroy themselves once they invoke the required callback.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/ Filename: utils.js\nfunction execLater(contextObject, delay, callback, args) {\n    var timer = Qt.createQmlObject(\"import QtQml 2.15; Timer { }\", contextObject);\n    timer.interval = delay === undefined ? 100 : delay\n    timer.repeat = false\n    timer.triggered.connect(() => {\n                                callback(args)\n                                timer.destroy()\n                            })\n    timer.start()\n}<\/code><\/pre>\n\n\n\n<p>The <span class=\"code-snippet\">execLater()<\/span> method in this JavaScript code creates a <span class=\"code-snippet\">Timer<\/span> item, which self-destroys after invoking a callback upon timeout. The <span class=\"code-snippet\">Timer<\/span> item is created as a child of <span class=\"code-snippet\">contextObject<\/span>, which means that if for some reason the <span class=\"code-snippet\">contextObject<\/span> gets destroyed before the timeout duration, the <span class=\"code-snippet\">Timer<\/span> object itself gets destroyed.<\/p>\n\n\n\n<p>Now in our QML code, we import this code into a name space and call the <span class=\"code-snippet\">execLater()<\/span> method. We also show a place holder image until the heavy part is actually loaded.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import \"utils.js\" as Utils\n\nComponent {\n    id: myDelegate \n\n    Item {\n        id: lightPart\n        \/\/ ...\n\n        Loader {\n            id: heavyPartLoader\n            anchors.fill: parent\n            active: false\n            sourceComponent: Item {\n                id: heavyPart\n                \/\/ ...\n            }\n\n            function initialize() { active = true }\n            Component.onCompleted: {\n                Utils.execLater(heavyPartLoader, 100, initialize)\n            }\n\n            Image {\n                id: placeHolderImage\n                anchors.fill: parent\n                visible: parent.status !== Loader.Ready\n                \/\/ ...\n            }\n\n            \/\/ ....\n        }\n\n        \/\/ ...\n    }\n}<\/code><\/pre>\n\n\n\n<p>This approach solves three problems<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>We get to schedule call to a function a stated amount of time later, in this case 100 ms.<\/li><li>We get to delete from memory the <span class=\"code-snippet\">Timer<\/span> object once its purpose is served.<\/li><li>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.<\/li><\/ol>\n\n\n\n<p>However, multiple calls to <span class=\"code-snippet\">initialize()<\/span> from <span class=\"code-snippet\">Utils.execLater()<\/span> won&#8217;t get batched into a single call to <span class=\"code-snippet\">initialize()<\/span>, like it does in <span class=\"code-snippet\">Qt.callLater()<\/span>.<\/p>\n\n\n\n<p>Additionally if you observe carefully, we have unwittingly thrown <span class=\"code-snippet\">Loader<\/span> and <span class=\"code-snippet\">Image<\/span> 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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import \"utils.js\" as Utils\n\nComponent {\n    id: myDelegate \n\n    Item {\n        id: lightPart\n        \/\/ ...\n\n        property Item placeHolderItem    \n        Component.onCompleted: {\n            placeHolderItem = placeHolderImageComponent.createObject(lightPart)\n            Utils.execLater(lightPart, 100, loadHeavyPart)\n        }\n\n        property Item heavyPartItem\n        function loadHeavyPart() {\n            heavyPartItem = heavyPartComponent.createObject(lightPart)\n            placeHolderItem.destroy()\n        }\n\n        \/\/ ...\n    }\n}\n\nComponent {\n    id: heavyPartComponent\n\n    Item {\n        anchors.fill: parent\n        \/\/ ...\n    }\n}\n\nComponent {\n    id: placeHolderImageComponent\n\n    Image {\n        anchors.fill: parent\n        \/\/ ...\n    }\n}<\/code><\/pre>\n\n\n\n<p>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 <span class=\"code-snippet\">Loader<\/span>.<\/p>\n\n\n\n<p>The code below brings all of these concepts together into a working example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ File: utils.js\nfunction execLater(contextObject, delay, callback, args) {\n    var timer = Qt.createQmlObject(\"import QtQml 2.15; Timer { }\", contextObject);\n    timer.interval = delay === undefined ? 100 : delay\n    timer.repeat = false\n    timer.triggered.connect(() => {\n                                callback(args)\n                                timer.destroy()\n                            })\n    timer.start()\n}\n\nfunction boundRandom(min, max) {\n    return min + Math.random() * (max-min)\n}\n\n\/\/ File: example.qml\nimport QtQuick\nimport QtQuick.Window\nimport QtQuick.Controls\nimport \"utils.js\" as Utils\n\nWindow {\n    width: 640\n    height: 480\n    visible: true\n\n    ListView {\n        id: myView\n        anchors.fill: parent\n        model: 300\n        delegate: myDelegate\n        ScrollBar.vertical: ScrollBar { }\n    }\n\n    Component {\n        id: myDelegate\n\n        Item {\n            id: lightPart\n            width: myView.width\n            height: Utils.boundRandom(50, 300)\n\n            required property int index\n\n            property Item placeHolderItem\n            Component.onCompleted: {\n                placeHolderItem = \n                  placeHolderComponent.createObject(lightPart, {\"index\": index})\n                Utils.execLater(lightPart, 1000, loadHeavyPart)\n            }\n\n            property Item heavyPartItem\n            function loadHeavyPart() {\n                heavyPartItem = \n                  heavyPartComponent.createObject(lightPart, {\"index\": index})\n                placeHolderItem.destroy()\n            }\n        }\n    }\n\n    Component {\n        id: heavyPartComponent\n\n        Rectangle {\n            required property int index\n\n            anchors.fill: parent\n            color: Qt.rgba( Utils.boundRandom(0.6,0.9),\n                            Utils.boundRandom(0.6,0.9),\n                            Utils.boundRandom(0.6,0.9), 1)\n\n            Text {\n                anchors.centerIn: parent\n                text: \"Heavy Part: \" + index\n                font.pixelSize: 18\n            }\n        }\n    }\n\n    Component {\n        id: placeHolderComponent\n\n        Rectangle {\n            required property int index\n\n            anchors.fill: parent\n            color: Qt.rgba( Utils.boundRandom(0.6,0.9),\n                            Utils.boundRandom(0.6,0.9),\n                            Utils.boundRandom(0.6,0.9), 0.3)\n\n            Text {\n                anchors.centerIn: parent\n                text: \"Placeholder: \" + index\n                font.pixelSize: 18\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<style>\n.code-snippet {\n   font-family: Monospace;\n   font-weight: bold;\n   color: darkgreen;\n}\n<\/style>\n","protected":false},"excerpt":{"rendered":"<p>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 &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.vcreatelogic.com\/index.php\/2023\/01\/02\/schedule-a-call-for-later-in-qml\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Delayed Loading Of Delegates in QML Views&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-173","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.vcreatelogic.com\/index.php\/wp-json\/wp\/v2\/posts\/173","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.vcreatelogic.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.vcreatelogic.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.vcreatelogic.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.vcreatelogic.com\/index.php\/wp-json\/wp\/v2\/comments?post=173"}],"version-history":[{"count":17,"href":"https:\/\/www.vcreatelogic.com\/index.php\/wp-json\/wp\/v2\/posts\/173\/revisions"}],"predecessor-version":[{"id":191,"href":"https:\/\/www.vcreatelogic.com\/index.php\/wp-json\/wp\/v2\/posts\/173\/revisions\/191"}],"wp:attachment":[{"href":"https:\/\/www.vcreatelogic.com\/index.php\/wp-json\/wp\/v2\/media?parent=173"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.vcreatelogic.com\/index.php\/wp-json\/wp\/v2\/categories?post=173"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.vcreatelogic.com\/index.php\/wp-json\/wp\/v2\/tags?post=173"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}