QML - tracking global position of a component

前端 未结 5 797
灰色年华
灰色年华 2020-12-09 11:19

I would like to track a global position of an object (or relative to one of it\'s ancestors) and bind it to some other item\'s position.

I was thinking about using <

相关标签:
5条回答
  • 2020-12-09 11:32

    Tracking certain Item's global positions seems like an important problem if developing some complex graphics interaction. I came up with a relatively simple & graceful solution. Here is my core codes:

    Item{
        id: globalRoot
        signal globalPositionChanged(Item item, real newX, real newY);
    
        function tracking(item){
            var obj = item;
            var objN;
            function onGlobalXYChanged(){
                var pt = mapFromItem(item, item.x, item.y);
                globalRoot.globalPositionChanged(item, pt.x, pt.y);
            }
            do{
                objN = obj.objectName;
                obj.xChanged.connect(onGlobalXYChanged);
                obj.yChanged.connect(onGlobalXYChanged);
                obj = obj.parent;
            }while(objN !== "furthestAncestorObjectName");
        }
    }
    

    The core idea is: what essentially makes an Item's global position change? It maybe itself, its parent or its parent's parent etc. So make a traverse back to its furthest parent and connect each of its ancestor's x/y change signal to a function, within which we get the item's global position and broadcast outside.

    0 讨论(0)
  • 2020-12-09 11:36

    This is a hard point, but here is the hack i used in one of my projects : to make blue rect which is in another parent than green rect move, to stay aligned with it, when green rect moves but also when yellow rect (green rect parent) moves :

    import QtQuick 2.0;
    
    Rectangle {
        id: window;
        width: 800;
        height: 480;
    
        property bool globalBit : true;
    
        function updatePos (item_orig, item_dest, bit) {
            var pos_abs = window.mapFromItem (item_orig.parent, item_orig.x, item_orig.y);
            return window.mapToItem (item_dest.parent, pos_abs.x, pos_abs.y);
        }
    
        Rectangle {
            id: rectYellow;
            width: 400;
            height: 300;
            x: 300;
            y: 200;
            color: "yellow";
    
            onXChanged: { globalBit = !globalBit; }
            onYChanged: { globalBit = !globalBit; }
    
            MouseArea {
                drag {
                    target: rectYellow;
                    minimumX: 0;
                    minimumY: 0;
                    maximumX: (rectYellow.parent.width - rectYellow.width);
                    maximumY: (rectYellow.parent.height - rectYellow.height);
                }
                anchors.fill: parent;
            }
            Rectangle {
                id: rectGreen;
                x: 100;
                y: 100;
                width: 50;
                height: 50;
                color: "green";
    
                MouseArea {
                    drag {
                        target: rectGreen;
                        minimumX: 0;
                        minimumY: 0;
                        maximumX: (rectGreen.parent.width - rectGreen.width);
                        maximumY: (rectGreen.parent.height - rectGreen.height);
                    }
                    anchors.fill: parent;
                }
            }
        }
        Rectangle {
            id: rectBlue;
            x: pos.x + 50;
            y: pos.y + 50;
            width: 50;
            height: 50;
            color: "blue";
    
            property var pos : updatePos (rectGreen, rectBlue, globalBit);
        }
    }
    

    The trick is to bring all coordinates back to the first common ancestor, using both mapfromItem and mapToItem, and to force the function to be re-evaluated, just put a global boolean flag that you pass to the computing function, and that you invert each time a movable element on your map moves... You don't have to put it every where, just on parents of items that can move and are inside the ancestor item.

    So it works, your positions will always be right, and it's quite scalable and doesn't add much code.

    0 讨论(0)
  • I have tried to improve on @Shubhanga's answer a bit by moving the code into its own ItemPositionTracker.qml file:

    import QtQuick 2.3
    
    Item {
        id: root
    
        property Item trackedItem
        property Item movedItem
    
        Component.onCompleted: {
            movedItem.x =
                    Qt.binding(
                        function()
                        {
                            if (trackedItem === null) return 0;
    
                            var docRoot = trackedItem;
                            var x = trackedItem.x;
    
                            while(docRoot.parent)
                            {
                                docRoot = docRoot.parent;
                                x += docRoot.x
                            }
    
                            return x;
                        }
    
                        )
            movedItem.y =
                    Qt.binding(
                        function()
                        {
                            if (trackedItem === null) return 0;
    
                            var docRoot = trackedItem;
                            var y = trackedItem.y
    
                            while(docRoot.parent)
                            {
                                docRoot = docRoot.parent;
                                y += docRoot.y
                            }
    
                            return y;
                        }
    
                        )
        }
    }
    

    The code can now be added to any QML object like this:

    ItemPositionTracker {
        trackedItem: rectGreen
        movedItem: rectBlue
    }
    

    Which makes rectBlue follow rectGreen.

    0 讨论(0)
  • 2020-12-09 11:56

    I don't know whether this will help, but for the above yellow rect,blue rect and green rect problem mentioned by TheBootroo, I used the below code to solve the problem

    import QtQuick 2.0;
    
    Rectangle {
    id: window;
    width: 800;
    height: 480;
    
    
    Rectangle {
        id: rectYellow;
        width: 400;
        height: 300;
        x: 300;
        y: 200;
        color: "yellow";
    
        MouseArea {
            drag {
                target: rectYellow;
                minimumX: 0;
                minimumY: 0;
                maximumX: (rectYellow.parent.width - rectYellow.width);
                maximumY: (rectYellow.parent.height - rectYellow.height);
            }
            anchors.fill: parent;
        }
        Rectangle {
            id: rectGreen;
            x: 100;
            y: 100;
            width: 50;
            height: 50;
            color: "green";
    
            MouseArea {
                drag {
                    target: rectGreen;
                    minimumX: 0;
                    minimumY: 0;
                    maximumX: (rectGreen.parent.width - rectGreen.width);
                    maximumY: (rectGreen.parent.height - rectGreen.height);
                }
                anchors.fill: parent;
            }
        }
    }
    Rectangle {
        id: rectBlue;
        //Need to acheive the below behvior(commented)
        //x: window.x+rectYellow.x+rectGreen.x+50
        //y: window.y + rectYellow.y +rectGreen.y+50
        width: 50;
        height: 50;
        color: "blue";
    
    }
    Component.onCompleted: {
        rectBlue.x =Qt.binding(
                    function()
                    {
                         //Returns window.x+rectYellow.x+rectGreen.x+rectGreen.width
                        var docRoot = null;
                        var x=rectGreen.x;
                        if(!docRoot)
                        {
                           docRoot = rectGreen.parent;
                           x+=docRoot.x;
    
                           while(docRoot.parent)
                           {
                               docRoot = docRoot.parent;
                               x+=docRoot.x
                           }
                        }
                        return x+rectGreen.width;
                    }
    
                    )
        rectBlue.y = Qt.binding(
                    function()
                    {
                        //Returns window.y+rectYellow.y+rectGreen.y+rectGreen.height
                        var docRoot = null;
                        var y=rectGreen.y
                        if(!docRoot)
                        {
                           docRoot = rectGreen.parent;
                           y+=docRoot.y;
    
                           while(docRoot.parent)
                           {
                               docRoot = docRoot.parent;
                               y+=docRoot.y
                           }
                        }
                        return y+rectGreen.height;
                    }
    
                    )
        }
    
    
    }
    

    The idea is to calculate the position of blue rectangle relative to the green rectangle, by calculating the position of green rectangle and its visual ancestors.

    The inspiration behind this solution is -> http://developer.nokia.com/Community/Wiki/How_to_create_a_Context_Menu_with_QML

    0 讨论(0)
  • 2020-12-09 11:57

    The solution below will trigger the qmlElementToTrack.onPropertyNameXChanged() and qmlElementToTrack.onPropertyNameYChanged() events each time one of its parents 'x' or 'y' values change.

    It does this by attaching to each parent's onXChanged() and onYChanged() signals. When one of those values changes, it recalculates the propertyNameX or propertyNameY values by traversing all of qmlElementToTrack's parents.

    To make it 'relative' positioned (instead of 'absolute'), add current !== qmlElementToStopAt to each while() condition.

    ElementToTrack {
      id: qmlElementToTrack
      property real propertyNameX: 0
      property real propertyNameY: 0
    }

    setPositionChangedToParents(qmlElementToTrack);
    
    
    /**
      Connect to each parent's 'onXChanged' and 'onYChanged' signals.
    */
    setPositionChangedToParents = function(current) {
        while (current && current.parent) {
            current.onXChanged.connect(calculatePropertyNameX);
            current.onYChanged.connect(calculatePropertyNameY);
            current = current.parent;
        }
    };
    
    
    /**
      Disconnects the signals set to all parents.
    */
    removePositionChangedFromParents = function(current) {
        while (current && current.parent) {
            current.onXChanged.disconnect(calculatePropertyNameX);
            current.onYChanged.disconnect(calculatePropertyNameY);
            current = current.parent;
        }
    };
    
    
    /**
      When any parent's 'x' changes, recalculate the 'x' value for the 'property name'.
    */
    calculatePropertyNameX = function() {
        var calculatedX, current;
    
        calculatedX = 0;
        current = qmlElementToTrack;
    
        while (current && current.parent) {
            calculatedX += current.x;
            current = current.parent;
        }
    
        propertyNameX = calculatedX;
    };
    
    
    /**
      When any parent's 'y' changes, recalculate the 'y' value for the 'property name'.
    */
    calculatePropertyNameY = function() {
        var calculatedY, current;
    
        calculatedY = 0;
        current = qmlElementToTrack;
    
        while (current && current.parent) {
            calculatedY += current.y;
            current = current.parent;
        }
    
        propertyNameY = calculatedY;
    };

    0 讨论(0)
提交回复
热议问题