How to omit the "Binding loop detected for property" warning?

10,259

Solution 1

The solution is simply use Connections:

import QtQuick 2.5

MultiPointTouchArea {
    id: myTouchSurface

    // this is object I want to respond to touch gestures
    property Item target: parent

    // this is object I want to keep target in. I won't let target get out of it
    property Item container: parent.parent

    property double targetX: target.x
    property double targetY: target.y
    property double centerX: target.width / 2
    property double centerY: target.height / 2
    property double lastTouchX
    property double lastTouchY
    property double lastXDrag
    property double lastYDrag

    // here I calculate received touches and move target
    onPressed: {
        lastTouchX = touchPoints[0].sceneX
        lastTouchY = touchPoints[0].sceneY
        if (slidingAnimation.running)
            slidingAnimation.stop()
    }
    onTouchUpdated: {
        if (touchPoints.length) {
            target.x += touchPoints[0].sceneX - lastTouchX
            target.y += touchPoints[0].sceneY - lastTouchY
            lastXDrag = touchPoints[0].sceneX - lastTouchX
            lastYDrag = touchPoints[0].sceneY - lastTouchY
            lastTouchX = touchPoints[0].sceneX
            lastTouchY = touchPoints[0].sceneY
        }
        else
            startSliding()
    }

    // when lifting fingers off screen I want to slide target across it's container
    function startSliding() {
        slidingAnimation.toX = target.x + lastXDrag * 10
        slidingAnimation.toY = target.y + lastYDrag * 10
        slidingAnimation.restart()
    }

    // This is animation responsible for sliding the target
    ParallelAnimation {
        id: slidingAnimation
        property double toX
        property double toY
        property int animationDuration: 250
        NumberAnimation {
            target: myTouchSurface.target
            property: "x"
            to: slidingAnimation.toX
            duration: slidingAnimation.animationDuration
            easing.type: Easing.OutQuad
        }
        NumberAnimation {
            target: myTouchSurface.target
            property: "y"
            to: slidingAnimation.toY
            duration: slidingAnimation.animationDuration
            easing.type: Easing.OutQuad
        }
    }

    Connections{
        target:myTouchSurface.target
        onXChanged:{
            if (container != null) {
                if (target.x + centerX < 0)
                    target.x = -centerX
                else if (target.x + centerX > container.width)
                    target.x = container.width - centerX
            }
        }

        onYChanged:{
            if (container != null) {
                if (target.y + centerY < 0)
                    target.y = -centerY
                else if (target.y + centerY > container.height)
                    target.y = container.height - centerY
            }
        }
    }

    // this is how I keep target object within container
//    onTargetXChanged: {

//    }
//    onTargetYChanged: {

//    }
}

Solution 2

I think your problem is that you update either targetX or target.x (resp. for y) inside of the property update handler (onTargetXChanged()).

This might be picked up as a biding loop by the QML engine as if you change target.x from onTargetXChanged() the signal onTargetXChanged() will trigger again as it's bound to it in property double targetX: target.x.

You can try something like this:

TouchArea {
  property Item target  //  target is the Image object being moved
  property double targetX: target.x
  property double targetY: target.y

  onTargetXChanged: {
    /* if object is out of specified area move x respectively */
    performPropertyUpdate(args)
  }
  onTargetYChanged: {
    /* if object is out of specified area move y respectively */
    performPropertyUpdate(args)
  }

  function performPropertyUpdate(args) {
    /* actual setting of target.x or target.y */
  }
}

Solution 3

To solve the issue myX should be an alias of x,

property alias myX: rectangle2.x

You're getting this problem cause myX is changing the value of x and x is changing the value of myX: a loop.

Share:
10,259
Filip Hazubski
Author by

Filip Hazubski

I do what I love. I code :) To the extent possible under law, Filip Hazubski has waived all copyright and related or neighboring rights to Stack Exchange sites comments, questions and answers written by me. This work is published from: Polska. http://creativecommons.org/publicdomain/zero/1.0/

Updated on June 30, 2022

Comments

  • Filip Hazubski
    Filip Hazubski almost 2 years

    I decided to rewrite this question. I tried to make it clear and short but it didn't work out.

    What I am trying to achieve is an object I can put on anything (Rectangle, Image and so on) and make it respond to touch gestures. Unfortunately I ran into a problem I cannot solve. I couldn't find help so I try here.

    Here is simple main.qml:

    import QtQuick 2.5
    import QtQuick.Controls 1.3
    import QtQuick.Window 2.2
    import QtQuick.Dialogs 1.2
    
    ApplicationWindow {
        title: qsTr("Touch surface test")
        width: 640
        height: 480
        visible: true
    
    
        Rectangle {
            width: 200
            height: 60
            color: "blue"
    
            MyTouchSurface {
                id: myTouchSurface
                anchors.fill: parent
            }
        }
    
    }
    

    and here is MyTouchSurface.qml

    import QtQuick 2.5
    
    MultiPointTouchArea {
        id: myTouchSurface
    
        // this is object I want to respond to touch gestures
        property Item target: parent
    
        // this is object I want to keep target in. I won't let target get out of it
        property Item container: parent.parent
    
        property double targetX: target.x
        property double targetY: target.y
        property double centerX: target.width / 2
        property double centerY: target.height / 2
        property double lastTouchX
        property double lastTouchY
        property double lastXDrag
        property double lastYDrag
    
        // here I calculate received touches and move target
        onPressed: {
            lastTouchX = touchPoints[0].sceneX
            lastTouchY = touchPoints[0].sceneY
            if (slidingAnimation.running)
                slidingAnimation.stop()
        }
        onTouchUpdated: {
            if (touchPoints.length) {
                target.x += touchPoints[0].sceneX - lastTouchX
                target.y += touchPoints[0].sceneY - lastTouchY
                lastXDrag = touchPoints[0].sceneX - lastTouchX
                lastYDrag = touchPoints[0].sceneY - lastTouchY
                lastTouchX = touchPoints[0].sceneX
                lastTouchY = touchPoints[0].sceneY
            }
            else
                startSliding()
        }
    
        // when lifting fingers off screen I want to slide target across it's container
        function startSliding() {
            slidingAnimation.toX = target.x + lastXDrag * 10
            slidingAnimation.toY = target.y + lastYDrag * 10
            slidingAnimation.restart()
        }
    
        // This is animation responsible for sliding the target
        ParallelAnimation {
            id: slidingAnimation
            property double toX
            property double toY
            property int animationDuration: 250
            NumberAnimation {
                target: myTouchSurface.target
                property: "x"
                to: slidingAnimation.toX
                duration: slidingAnimation.animationDuration
                easing.type: Easing.OutQuad
            }
            NumberAnimation {
                target: myTouchSurface.target
                property: "y"
                to: slidingAnimation.toY
                duration: slidingAnimation.animationDuration
                easing.type: Easing.OutQuad
            }
        }
    
        // this is how I keep target object within container
        onTargetXChanged: {
            if (container != null) {
                if (target.x + centerX < 0)
                    target.x = -centerX
                else if (target.x + centerX > container.width)
                    target.x = container.width - centerX
            }
        }
        onTargetYChanged: {
            if (container != null) {
                if (target.y + centerY < 0)
                    target.y = -centerY
                else if (target.y + centerY > container.height)
                    target.y = container.height - centerY
            }
        }
    }
    

    I want to have all calculating and functions within MyTouchSurface so it is easy to apply on other object and to use it in another project.

    The problem I have is I don't know how to prevent target object from moving out of container. Well, the code here is working very nice but it also generates very ugly errors about binding loop.

    What is going on is:

    • I check target's x and y when they changes
    • If the x or y is out of specified area I move them back
    • I receive signal "x changed" or "y changed" again
    • I receive error about loop because my function caused itself to execute again

    So I am asking: Is there any other simple way to prevent object from flying off screen and not receive binding loop errors at the same time?

    I know I can use Timer to check target's x and y from time to time, and bring them back.

    I know I can change the easing and duration of the animation so it stops on the container bounds itself but looks like it was stopped.

    Seriously. Is there no simple way?


    Things that won't work:

    • stopping animation after object was detected outside container (if it would move really fast it will stop out of the screen )
    • stopping animation and then changing target's x or y

    Thanks everyone helping me so far. It is nice to know I am not alone here :)