Search code examples
qtqmlqt-quick

Qt Quick bindings not updated at startup


Trying to make a very simple node editor in Qt Quick.

The most tricky part seems to be the Connection item, which has to track the two absolute positions of the source and destination IOPorts.

The following code works fine, except that the bindings are not evaluated (or are evaluated in the wrong order) at startup, resulting in the connection line to be in the wrong position at startup:

which is then adjusted to the correct position as soon as either node is moved:

I thought about adding:

Component.onCompleted: {
    start = mapFromItem(from, 0, 0)
    end = mapFromItem(to, 0, 0)
}

to Connection, which fixes the startup problem, but breaks the bindings.

So I resorted to this ugly hack:

Component.onCompleted: {
    from.x = from.x+1
    from.x = from.x-1
}

Is there a better solution to this problem?

(files available in GitHub repository, link at bottom)

main.qml:

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 420
    height: 280
    visible: true
    title: qsTr("Node editor")

    Node {
        x: 20
        y: 40
        width: 120
        height: 50

        IOPort {id: o1; x: 0; y: parent.height}
    }

    Node {
        x: 260
        y: 140
        width: 120
        height: 50

        IOPort {id: i1; x: 0; y: 0}
    }

    Connection {
        from: o1; to: i1
    }
}

Node.qml:

import QtQuick 2.0

Rectangle {
    id: rect
    border.color: "black"
    border.width: 2
    color: "#eee"

    MouseArea {
        anchors.fill: parent
        drag.target: rect
    }
}

IOPort.qml:

import QtQuick 2.0

Item {
    id: root
    property real size: 15
    property real globalX: x + parent.x
    property real globalY: y + parent.y
    property real globalZ: 1 + parent.z

    Rectangle {
        id: rect
        x: -width/2
        y: -height/2
        width: root.size
        height: root.size
        border.color: "black"
        color: "gray"
    }
}

Connection.qml:

import QtQuick 2.0
import QtQuick.Shapes 1.15

Shape {
    id: root
    property IOPort from
    property IOPort to
    x: Math.min(from.globalX, to.globalX)
    y: Math.min(from.globalY, to.globalY)
    z: Math.max(from.globalZ, to.globalZ) + 0.5
    width: Math.abs(from.globalX - to.globalX)
    height: Math.abs(from.globalY - to.globalY)
    property point start: from.globalX, from.globalY, to.globalX, to.globalY, mapFromItem(from, 0, 0)
    property point end: from.globalX, from.globalY, to.globalX, to.globalY, mapFromItem(to, 0, 0)

    ShapePath {
        strokeWidth: 4
        strokeColor: "black"
        startX: root.start.x
        startY: root.start.y
        PathLine {
            x: root.end.x
            y: root.end.y
        }
    }
}

GitHub repository: https://github.com/fferri/qtquick-simple-node-editor.git


Solution

  • I cannot currently reproduce the error but I think I found a problem:

    Your Connection root's start and end properties are missing a "forced" dependency on that same object's x and y

    For some reason you decided to not only to set the coordinates for path's points but also move the whole Connection element itself. So if root's x and y happen to update after start and end did, the latter still contain relative coordinates of the point when x and y were still default (probably 0,0) and thus the line becomes displaced by x,y pixels.

    Try changing them this way (I also removed dependencies you shouldn't need):

    property point start: from.globalX, from.globalY, x, y, mapFromItem(from, 0, 0)
    property point end: x, y, to.globalX, to.globalY, mapFromItem(to, 0, 0)
    

    (There were more suggestions here in case this didn't work but they were removed because the above seems to have solved the issue)