【问题标题】:QML - tracking global position of a componentQML - 跟踪组件的全局位置
【发布时间】:2013-07-29 11:02:29
【问题描述】:

我想跟踪一个对象的全局位置(或相对于它的一个祖先)并将它绑定到其他项目的位置。

我正在考虑使用mapFromItem如下:

SomeObject {
  x: ancestor.mapFromItem(trackedObject, trackedObject.x, 0).x
  y: ancestor.mapFromItem(trackedObject, 0, trackedObject.y).y
}

这种方法的问题是 mapFromItem 被评估一次,并且不会随着其中一个参数的更新而更新。此外,映射有时会返回由我无法在代码中跟踪的偏移量更改的新位置(但这不是手头的问题)。

我的第二个想法是通过实现一个函数来计算全局位置,该函数将递归地求和偏移量,在提供的祖先处停止(类似于calculateOffsetFrom(ancestor))。仍然这只是一个函数,就我而言,它不会随着祖先位置的变化而重新评估(除非在该函数中,我会将每个函数绑定到 onXChanged 信号一路上的祖先之一,这似乎是一个肮脏的解决方案。

所以最后我为要跟踪的对象添加了属性,然后绑定到它们:

TrackedObject {
  property real offsetX: x + parent.x + parent.parent.x + parent.parent.parent.x ...
  property real offsetY: y + parent.y + parent.parent.y + parent.parent.parent.y ...
}

SomeObject {
  x: trackedObject.globalX
  y: trackedObject.globalY
}

但是好吧...是的...这个根本无法缩放,而且很丑。

有谁知道如何以更简洁的方式解决这个问题?

编辑: 就我而言,在这种情况下我不能使用锚点。 SomeObject 组件是一个自定义组件,它从一个点到另一个点绘制一条贝塞尔曲线(它将连接两个TrackedObjects)。为此,我需要坐标之间的差异。如果我是正确的,锚点不会提供任何计算它们之间距离的方法。

【问题讨论】:

    标签: qt qml qt-quick qtquick2


    【解决方案1】:

    这是一个难点,但这是我在我的一个项目中使用的技巧:使在另一个父级中的蓝色矩形而不是绿色矩形移动,保持与它对齐,当绿色矩形移动时,也当黄色移动时rect(绿色矩形父)移动:

    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);
        }
    }
    

    诀窍是使用 mapfromItem 和 mapToItem 将所有坐标带回第一个共同祖先,并强制重新评估函数,只需放置一个传递给计算函数的全局布尔标志,然后每次地图上的可移动元素移动时,您都会反转...您不必将它放在任何地方,只需放在可以移动且位于祖先项目内的项目的父项上。

    所以它起作用了,你的位置总是正确的,而且它的可扩展性很强,不会添加太多代码。

    【讨论】:

    • 非常感谢。没想到会考虑使用该全局标志来强制重新评估。这看起来很脏,但确实有效。
    • 请注意,您不必在 args 中包含 globalBit 来触发重新评估。 JS 逗号运算符适用于这种情况:property var pos: globalBit, updatePos(rectGreen, rectBlue)
    【解决方案2】:

    我不知道这是否会有所帮助,但是对于TheBootro提到的上述黄色矩形,蓝色矩形和绿色矩形问题,我使用以下代码解决了问题

    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;
                    }
    
                    )
        }
    
    
    }
    

    这个想法是计算蓝色矩形相对于绿色矩形的位置,通过 计算绿色矩形及其视觉祖先的位置。

    这个解决方案背后的灵感是 -> http://developer.nokia.com/Community/Wiki/How_to_create_a_Context_Menu_with_QML

    【讨论】:

    • 与上面提到的彩色矩形解决方案类似,您可以将TrackedObject的x和y坐标绑定到遍历可视化树并返回祖先坐标的javascript函数。
    【解决方案3】:

    我试图通过将代码移动到它自己的 ItemPositionTracker.qml 文件中来稍微改进@Shubhanga 的答案:

    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;
                        }
    
                        )
        }
    }
    

    现在可以像这样将代码添加到任何 QML 对象中:

    ItemPositionTracker {
        trackedItem: rectGreen
        movedItem: rectBlue
    }
    

    这使得 rectBlue 跟随 rectGreen。

    【讨论】:

      【解决方案4】:

      以下解决方案将触发 qmlElementToTrack.onPropertyNameXChanged()qmlElementToTrack.onPropertyNameYChanged() 事件,每次其父 'x' 或 'y' 值之一发生变化时.

      它通过附加到每个父母的 onXChanged()onYChanged() 信号来做到这一点。当其中一个值发生变化时,它会通过遍历所有 qmlElementToTrack 的 父级来重新计算 propertyNameXpropertyNameY 值。

      要使其“相对”定位(而不是“绝对”),请将 current !== qmlElementToStopAt 添加到每个 while() 条件。

      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;
      };

      【讨论】:

      • 非常好。请注意:除了连接到onXChangedonYChanged 之外,它还应该连接到onParentChanged,以便跟踪任何重新育儿技巧。
      【解决方案5】:

      如果开发一些复杂的图形交互,跟踪某些项目的全局位置似乎是一个重要的问题。我想出了一个相对简单而优雅的解决方案。这是我的核心代码:

      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");
          }
      }
      

      核心思想是:本质上是什么让Item的全局位置发生变化?它可能是它自己、它的父级或它的父级的父级等。所以遍历它最远的父级并将其每个祖先的 x/y 更改信号连接到一个函数,在该函数中我们获取项目的全局位置并在外部广播。

      【讨论】:

        猜你喜欢
        • 2020-08-13
        • 1970-01-01
        • 2018-05-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-15
        • 1970-01-01
        • 2011-04-05
        • 2013-01-02
        相关资源
        最近更新 更多