1

I am using Qt 5.9.3 commercial version.

Scenario
I have logic to be executed in Qt QML part of the my code. On click of some random button. I want to move a QML rectangle to another position. The other position is a calculated (x, y) position based on current position of the rectangle.

Following is sort of what I want to do:

Code:

import QtQuick 2.9
import QtQuick.Window 2.2

Window {
    visible: true
    width: 800
    height: 600
    title: qsTr("Hello World")

    property bool some_boolean_flag: false

    Rectangle {
        id: my_rect_1
        color: "blue"
        width: parent.width / 4
        height: parent.height / 4
        z:1

        property real current_x
        property real current_y

        x: {
            if (some_boolean_flag) {
                current_x = current_x + current_x/10
                return current_x
            }
            else {
                var default_x = parent.width/6
                current_x = default_x
                return current_x
            }
        }
        y: {

            if (some_boolean_flag) {
                current_y = current_y + current_y/10
                return current_y
            }
            else {
                var default_y = parent.height/6
                current_y = default_y
                return current_y
            }
        }
    }

    Rectangle {
        id: my_rect_2
        color: "yellow"
        width: parent.width/5
        height: parent.height/5
        x: parent.width - parent.width/4
        y: parent.height - parent.height/4
        z:2

        MouseArea {
            anchors.fill: parent
            onClicked: {
                // swapping the boolean flag between true and false
                some_boolean_flag = !some_boolean_flag
            }
        }
    }
}

The objective of this question:
Note that I want to do the calculation declaratively. I can easily create a QML function and do this declaring some global properties which is of course easy but doing this calculation is not the point of the question. My objective is to learn how to to do this declaratively. How to apply a calculation to a QQuickItem::x based on the QQuickItem::x's current value itself..

If you run the example code in this question, you will see that my_rect_1 just keeps swapping between 2 positions back and forth which is not what the logic really intends. The intention of the logic is to keep incrementing my_rect_1::x and my_rect_1::y by a certain factor which would cause my_rect_1 to get moved rightwards and downwards as you keeping clicking on the other rectangle that is my_rect_2.

Other issue
is that I keep getting the following error at runtime when I click on my_rect_2.

qrc:/main.qml:12:5: QML Rectangle: Binding loop detected for property "x"

I understand QML is complaining about the circular dependency of the property x in my_rect_1. How can I avoid this and achieve a declarative way of transforming x and y?

Potential solution I found from my searches
Looks like Translate QML Type can be useful in the logic I want to implement but how?

How can I achieve this behaviour without writing a QML function which does this transformation separately outside my_rect_1?

TheWaterProgrammer
  • 7,055
  • 12
  • 70
  • 159

1 Answers1

1

Well, "declaratively" would simply involve bindings, which are in their essence pretty much functions, with the added benefit of automatic reevaluation upon changes in the values involved in the expressions.

Here is a trivial example that will show a red dot on screen, and when clicking on the window, the dot will move to a location that is derived by the dot's current location and the clicked location, putting it right in the center of the imaginary line between the two coordinates:

ApplicationWindow {
  id: main
  width: 640
  height: 480
  visible: true

  property real xpos: 10
  property real ypos: 10

  Rectangle {
    id: rect
    x: xpos
    y: ypos
    width: 10
    height: 10
    radius: 5
    color: "red"
  }

  MouseArea {
    anchors.fill: parent
    onClicked: {
      xpos = (xpos + mouseX) * .5
      ypos = (ypos + mouseY) * .5
      console.log(ypos)
    }
  }
}

Note that the xpos and ypos properties are practically redundand, and the same thing can be achieved without that, but it would involve manually manipulating the dot coordinates, thus its position will not be declared in a purely "declarative way". This way it is bound to the property values, and will keep on following them regardless of what process controls the values.

As for this part of your code:

  x: {
      if (some_boolean_flag) {
          current_x = current_x + current_x/10
          return current_x
      }
      else {
          var default_x = parent.width/6
          current_x = default_x
          return current_x
      }
  }

It is the binding expression that defines the property value. If you assign a value to a property manually, it breaks the existing binding. What you do is you assign a value to the property in the expression that is supposed to do that automatically - pure nonsense, which also explains why it doesn't work as expected.

The appropriate format would be:

  x: {
      if (some_boolean_flag) {
          return current_x + current_x/10
      }
      else {
          var default_x = parent.width/6
          return default_x
      }
  }

Which can actually be distilled down to:

  x: some_boolean_flag ? current_x + current_x/10 : parent.width/6      

and so on... And as you see, using this format removes the binding loop warnings and now you have behavior that matches the code logic intent.

dtech
  • 47,916
  • 17
  • 112
  • 190
  • couldn't have expected a better answer than this. so the mistake I am doing is `current_x = current_x + current_x/10; return current_x`? doing the logic in 2 lines of code where the first line assigns it to a temporary property **breaks the binding?** Interesting. thanks a lot for this detailed explanation ! – TheWaterProgrammer Feb 24 '18 at 14:45
  • 1
    If you do a `prop = value` it breaks any binding it may have attached, regardless of where this happens. Note that you can create bindings imperatively using `prop = Qt.binding()` = that will attach a new binding unlike a plain assignment. – dtech Feb 24 '18 at 15:00
  • I read about this `Qt.binding()` [here](http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html). Looks like I can still do these free 2 lines (`current_x = current_x + current_x/10; return current_x`) from a `function` using `Qt.binding()`? So there is another way achieve this binding and do current_x = current_x + current_x/10; return current_x` ? – TheWaterProgrammer Feb 24 '18 at 17:45
  • I accepted the answer but I have one follow up question to clarify in above comment. Please reply if you could – TheWaterProgrammer Feb 24 '18 at 17:46
  • 1
    Again, the binding expression will *automatically* assign the return value to the property. It is pointless and makes no sense to assign it yourself, it is wrong and ill-formed and serves no purpose whatsoever. Why do you keep insisting on doing it? – dtech Feb 24 '18 at 17:52
  • I have quite complex calculation that needs to be bound/"done on the fly" with the x and y property. hence was asking about it. but anyways, I got the other way of doing it using a way you suggesting with `xpos` and `ypos` technique. – TheWaterProgrammer Feb 24 '18 at 18:04
  • 1
    What part of that calculation requires to assign to the property value more than once? Anyway, you can do a `var tempvalue = propValue` and use `tempvalue` to assign and whatnot, and finally `return tempvalue`. The entire purpose of the binding expression is to assign the return value to the property, so it makes no sense to manually assign a value to that property in its body. – dtech Feb 24 '18 at 18:07
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/165758/discussion-between-game-of-threads-and-dtech). – TheWaterProgrammer Feb 24 '18 at 18:13