qtqmlqtquick2qt5.5

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


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:

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:


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


Solution

  • 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: {
    
    //    }
    }