9

If I have a simple Binding object of the form:

Rectangle {
    height: 400
    width: 500

    property var someObj: null

    Binding on color {
        when:  someObj
        value: someObj.color
    }
}

Then I would expect that when someObj is not null, someObj's color property is bound to this object's color property. What I actually get is a runtime error message:

TypeError: Cannot read property 'color' of null

Any reason why this doesn't work?

Doing the almost equivalent JavaScript expression:

color: {
    if ( someObj != null ) {
        return someObj.color;
    } else {
        return "black";
    }
}

Works as expected.

cmannett85
  • 21,725
  • 8
  • 76
  • 119
  • 3
    Looks like a bug to me. – Mitch Feb 18 '15 at 10:12
  • Uhm...did you try to reduce to a minimum example to isolate the (possible) bug? if you could post that minimum example it would be interesting to check it out. – BaCaRoZzo Feb 18 '15 at 10:39
  • 1
    @BaCaRoZzo I've edited a minimal example into the question. – cmannett85 Feb 18 '15 at 10:45
  • 3
    @Mitch There's already a bug report for this, quite an old one too: https://bugreports.qt.io/browse/QTBUG-22005 – cmannett85 Feb 18 '15 at 11:04
  • Yes, BUG indeed as guessed by @Mitch. It works if you set an id and use it to refer the `someObj`. Using the id to refer to the parent: very good. :/ – BaCaRoZzo Feb 18 '15 at 11:12
  • 3
    just a simple `color: someObj? someObj.color: "black"` is pretty much enough here, btw (note that you don't need to instantiate `Binding` objects when you are "inside" of the item which properties you'd like to bind) – mlvljr Apr 18 '15 at 16:37
  • Perhaps you want to answer and accept this yourself? – Mitch Oct 31 '15 at 10:46

3 Answers3

2

As mentioned in the comment by BaCaRoZzo, the minimal example given in the question does not work because it gives a ReferenceError: someObj is not defined. However, after fixing this with an id for the Rectangle, then the example actually works despite the TypeError:

Rectangle {
    id: rect

    height: 400
    width: 500

    property var someObj: null

    Binding on color {
        when:  rect.someObj
        value: rect.someObj.color
    }
}

This correctly sets the color as expected when rect.someObj is set and contains a color property.

The reason for the TypeError is that the expression rect.someObj.color is evaluated already when the Binding is created (see QTBUG-22005). So to prevent the TypeError, one can simply check for rect.someObj to be set in the value expression of the Binding:

    Binding on color {
        when:  rect.someObj
        value: rect.someObj ? rect.someObj.color : undefined
    }
Ignitor
  • 2,907
  • 33
  • 50
1

I would do it in the following way:

import QtQuick 2.0
import QtQuick.Controls 1.4

Rectangle {
    height: 400
    width: 500

    property var someObj

    color: someObj ? someObj.color : "black"

    Button {
        id: buttonTest
        text: "test"
        onClicked: parent.someObj = test
    }
    Button {
        id: buttonTest2
        anchors.left: buttonTest.right
        text: "test2"
        onClicked: parent.someObj = test2
    }

    QtObject {
        id: test
        property color color: "red"
    }

    QtObject {
        id: test2
        property color color: "blue"
    }
}

If someObj is undefined the color of the rectangle is black, if someObj is defined, the Value of the color property is chosen.

Edit: I've seen to late, that that's only what mlvljr suggested in the comments, sorry.

Florian Schmidt
  • 369
  • 3
  • 13
0

The QML syntax defines that curly braces on the right-hand-side of a property value initialization assignment denote a binding assignment. This can be confusing when initializing a var property, as empty curly braces in JavaScript can denote either an expression block or an empty object declaration. If you wish to initialize a var property to an empty object value, you should wrap the curly braces in parentheses.

For example:

Item {
    property var first:  {}   // nothing = undefined
    property var second: {{}} // empty expression block = undefined
    property var third:  ({}) // empty object
}

In the previous example, the first property is bound to an empty expression, whose result is undefined. The second property is bound to an expression which contains a single, empty expression block ("{}"), which similarly has an undefined result. The third property is bound to an expression which is evaluated as an empty object declaration, and thus the property will be initialized with that empty object value.

Similarly, a colon in JavaScript can be either an object property value assignment, or a code label. Thus, initializing a var property with an object declaration can also require parentheses:

Item {
    property var first: { example: 'true' }    // example is interpreted as a label
    property var second: ({ example: 'true' }) // example is interpreted as a property
    property var third: { 'example': 'true' }  // example is interpreted as a property
    Component.onCompleted: {
        console.log(first.example) // prints 'undefined', as "first" was assigned a string
        console.log(second.example) // prints 'true'
        console.log(third.example) // prints 'true'
    }
}

So the code should be as follow:

Rectangle {
    height: 400
    width: 500

    property var someObj: ({color: ''})

    Binding on color {
        when: someObj.color
        value: someObj.color
    }
}
Vedanshu
  • 2,230
  • 23
  • 33