Saturday, May 23, 2020

The Dreaded QML Binding Loop (Part 1)

What is a Binding Loop

The dreaded
Binding loop detected for property <xxx>
is a familiar error message for many QML developers. It is also a dreaded message because finding out where the binding loop is takes time. One cannot find it with a debugger. Once found, fixing it will be another time consuming task because it requires re-architecting or re-designing your code.

A binding loop is where one property depends on the value of another property (usually embedded in another QML type), and the other property somehow, through a long chain of dependencies, depends on the value of the original property.


The diagram below is a simplified binding loop where:
  • Rectangle.width depends on Text.wdith and
  • Text.wdith depends on Rectangle.width
Both Rectangle and Text do not know their widths and both Rectangle and Text have delegated the computation of their width to the other.


QML Binding Loop
QML Binding Loop



A binding loop can manifest itself between a QML property and another QML property, or a QML property with another C++ QObject property or a C++ QObject with another C++ QObject.

Example 1: A Simple Real World Example Of a Binding Loop

Let us assume there are two SCRUM teams working on a large QML project:
  • TeamA works on the main window, and
  • TeamB works on a text type called MyLabel.qml
TeamA embeds  TeamB's  MyLabel type into its main window.

TeamB and TeamB both know the MyLabel type should be centered in the main window. Being good responsible developers, TeamB decides that MyLabel should automatically center itself.

The code below demonstrates an example of the situation described above.

File: main.qml (from TeamA)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.0

Window {
    visible: true
    width: myText.width * 1.2
    height: 480
    title: qsTr("Hello World")

    readonly property string loremipsum: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

    MyLabel {
        id: myText
        text: loremipsum
    }
}


File: MyLabel.qml (from TeamB)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import QtQuick 2.12
import QtQuick.Controls 2.0

Label {
    background: Rectangle { color: "yellow" }

    y: 20
    height: 300
    wrapMode: Text.Wrap

    x: parent.x + parent.width * 0.1
    width: parent.width * 0.8
}


In file main.qml, line 7, main window re-sizes itself to be 20% wider than MyLabel.

In file Mylabel.qml, line 11-12, MyLabel re-sizes itself to be 20% smaller than its parent and centers itself horizontally.

Let it be noted that there are better ways to center a QML object and this method is chosen to demonstrate binding loops in QML. Interestingly, it is not uncommon to see many developers choosing to center a QML object using this technique.

Fixing the Binding Loop

In order to fix the binding loop described above, the developer has to understand this:
  • main window (i.e. Window) sizes its width to a percentage of MyLabel
  • MyLabel sizes itself to a percentage of main window

Method 1: Fixing the Binding Loop The Easy Way

The simplest approach to fixing the binding loop is:
  • MyLabel size itself to 80% of its parent
  • main window sets its preferred size independent of the size of Mylabel
The code below demonstrates an example of the fix.


File: main.qml (from TeamA)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.0

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

    readonly property string loremipsum: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

    MyLabel {
        id: myText
        text: loremipsum
    }
}


File: MyLabel.qml (from TeamB)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import QtQuick 2.12
import QtQuick.Controls 2.0

Label {
    background: Rectangle { color: "yellow" }

    y: 20
    height: 300
    wrapMode: Text.Wrap

    x: parent.x + parent.width * 0.1
    width: parent.width * 0.8
}


In file main.qml, line 7, main window is only interested in setting its own size, which is hardcoded to 640.

In file Mylabel.qml, line 11-12, MyLabel re-sizes itself to be 20% smaller than its parent and centers itself horizontally.

Since main window does not compute its width using MyLabel's width, there is no binding loop between main window and MyLabel.

The program, when compiled and run, looks like the screen capture below.





Method 2: Fixing the Binding Loop Using a Better Approach

A better way to fix the binding loop problem above is to use the implicitWidth property of QML types. MyLabel will tell its parent its preferred width and it is up to the parent to honour the width request of MyLabel. No matter what width is chosen by the parent, MyLabel continues to be able to display correctly.

MyLabel extends from Label. The implicitWidth property of Label is readonly for good reasons. In order to better depict the implicitWidth example,  we can make MyLabel extend an Item which embeds a Label.

This is the preferred "handshaking" mechanism of QML. That is, the child type recommends a preferred size and the parent has the final say.

The code below demonstrates an example of using implicitWidth.

File: main.qml (from TeamA)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.0

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

    readonly property string loremipsum: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

    MyLabel {
        id: myText
        text: loremipsum

        x: parent.width * 0.1
        width: parent.width * 0.8
    }
}


File: MyLabel.qml (from TeamB)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import QtQuick 2.12
import QtQuick.Controls 2.0

Item {
    implicitWidth: 300

    property alias text: label.text
    y: 20
    height: 300

    Label {
        id: label
        anchors.fill: parent
        background: Rectangle { color: "yellow" }

        wrapMode: Text.Wrap
    }
}


Of course, implicitWidth is ignored by the parent (i.e. main window) in this example. Yes, implicitWidth can be omitted in this example. It is only included to highlight how QML works. That is, how developers should use implicitWidth versus width.

The example above should also explain why implicitWidth of Text and Label is readonly. The implicitWidth of both types are set by QML's core engine to be equal to the total length of the string contained in text. That is, Text and Label are requesting for text to be displayed in one single line but Text and Label are still able to display its text correctly when sized otherwise.

Click here for Part 2 of Binding Loop: https://qmlpoison.blogspot.com/2020/05/blog-post.html


1 comment:

uctridcahow said...

Casino City | Jtm Hub
casino 오산 출장샵 city on the way to the 보령 출장안마 casino. The city is where you're 서울특별 출장안마 headed. 경주 출장마사지 The casino, the 아산 출장마사지 hotel and casino are all here. The hotel, entertainment, shopping, gaming