How to track the coordinates of draggable, dynamically-created Qt Quick objects?


  • Moderators

    Suppose I want Rectangles that can be dragged around a screen. I also want a Line (adapted from http://www.developer.nokia.com/Community/Wiki/Drawing_lines_in_QML ) that connects the centers of 2 Rectangles. It's straightforward to achieve this using property binding, if everything is created statically:
    @
    import QtQuick 2.0

    Rectangle {
    width: 800
    height: 600

    Rectangle {
        id: draggableBox1
        width: 100
        height: 100
        color: "green"
    
        MouseArea {
            anchors.fill: parent
            drag.target: draggableBox1
        }
    }
    
    Rectangle {
        id: draggableBox2
        width: 100
        height: 100
        color: "green"
    
        MouseArea {
            anchors.fill: parent
            drag.target: draggableBox2
        }
    }
    
    Line {
        x1: midpoint(draggableBox1.x, draggableBox1.width)
        y1: midpoint(draggableBox1.y, draggableBox1.height)
        x2: midpoint(draggableBox2.x, draggableBox2.width)
        y2: midpoint(draggableBox2.y, draggableBox2.height)
    
        function midpoint(origin, delta) {
            return origin+delta/2
        }
    }
    

    }
    @

    However, I can't figure out how to do this dynamically. Here's what I want:

    Draggable boxes are to be created one at a time (e.g. using Qt.createQmlObject()), at the user's command. The first box will not be connected to anything in the beginning. However, the user can select any existing box, and spawn another box from it. The new box will be connected to the selected box via a Line.

    I can't bind the line's endpoints to the boxes' properties, because dynamically-created objects don't have ids. How can I get the Lines to move appropriately when I drag the boxes around?

    Thanks in advance!


  • Moderators

    Ids are not available, but you can use QObject names (in C++ :)) and QObject::findChildren().

    In QML, I think what you could do is to push every object you create onto a JavaScript array and then use that to periodically update the viewport. Or, add a property to your Rectangle object that would hold a reference to Line object... I would need to think about it a bit more to give you a good answer, but maybe those small hints will help :)


  • Moderators

    [quote author="sierdzio" date="1370977017"]Ids are not available, but you can use QObject names (in C++ :)) and QObject::findChildren().[/quote]Thanks for this tip! That led me to "this page":http://qt-project.org/doc/qt-5.0/qtqml/qtqml-cppintegration-interactqmlfromcpp.html#accessing-loaded-qml-objects-by-object-name -- I haven't tried it yet, but it looks like I can use a QTimer to periodically query the Rectangles' positions and update the Lines using QObject::setProperty(). Updating every 150 ms should be fine.

    I'm still interested to see if there's a way to do this directly in QML.

    [quote]In QML, I think what you could do is to push every object you create onto a JavaScript array and then use that to periodically update the viewport. Or, add a property to your Rectangle object that would hold a reference to Line object... I would need to think about it a bit more to give you a good answer, but maybe those small hints will help :)[/quote]Holding references sounds good. If I can get the Lines to remember which 2 Rectangles they're attached to, then the Lines can compute their own endpoints. So the question now is: How do I make a dynamically-created object hold references to other dynamically-created objects in QML?

    EDIT: "This page":http://supportforums.blackberry.com/t5/Cascades-Development/Array-of-QML-objects/td-p/2002103 suggests that referencing QML objects isn't really possible :( The posters talked about using JavaScript objects for storing and manipulating dynamic data, but I'm interested in reading the coordinates of dynamic graphical objects


  • Moderators

    See this, for example:

    Feel free to roam through the whole project. If you take a look at newer commits (say, after September 2012) you will see how it can be done in C++ (I have moved most of "hardcore" functionality to C++). The project requires Qt5, and works well with QtCreator.

    EDIT: thing you really want to take a look at is aimLine, updateAimLine() and aimLineTimer - that stuff looks very much like what you are trying to achieve.


  • Moderators

    sierdzio, thank you for sharing! Your code has very helpful examples on referencing QML objects through JavaScript arrays. This code does the trick:

    @
    import QtQuick 2.0

    Rectangle {
    id: root
    width: 800
    height: 600

    // Arrays to hold references to dynamic objects
    property var rects: new Array
    property var lines: new Array
    
    // Periodically update Line position
    Timer {
        interval: 25
        running: true
        repeat: true
        onTriggered: {
            lines[0].x1 = rects[0].x
            lines[0].y1 = rects[0].y
            lines[0].x2 = rects[1].x
            lines[0].y2 = rects[1].y
        }
    }
    
    Component.onCompleted: {
        // Dynamically create draggable boxes
        for (var i = 0; i < 2; ++i)
        {
            var rect = Qt.createQmlObject(
                         'import QtQuick 2.0\n'
                        +'Rectangle {\n'
                        +'    x: ' + i*100 + '\n'
                        +'    y: ' + i*100 + '\n'
                        +'    color: "lightblue"\n'
                        +'    width: 100\n'
                        +'    height: 100\n'
                        +'    MouseArea {\n'
                        +'        anchors.fill: parent\n'
                        +'        drag.target: parent\n'
                        +'    }\n'
                        +'}',
                root, "dynamicRect");
    
            rects.push(rect)
        }
    
        // Dynamically create Line
        var comp = Qt.createComponent("Line.qml");
        var line = comp.createObject(root)
        lines.push(line)
    }
    

    }
    @

    Interestingly, if I create my Line using Qt.createQmlObject(), I can dynamically bind the Line endpoints and get rid of the timer:
    @
    var line = Qt.createQmlObject(
    'import QtQuick 2.0\n'
    +'Rectangle {\n'

          ...
    
        +'    property real x2: rects['+idx2+'].x\n'
        +'    property real y2: rects['+idx2+'].y\n' // Auto-updates when I move the Rectangle :)
        +'}', root, "dynamicLine.qml")
    

    @

    However, if I try to bind using Qt.createComponent(), it only assigns the initial value -- the values don't update on-the-fly:
    @
    var comp = Qt.createComponent("Line.qml");
    var line = comp.createObject(root, {"x1": rects[0].x, ...) // Once-off assignment, not property binding :(
    @

    Is that expected behaviour?


  • Moderators

    I'm happy to hear that, you are very welcome! :)

    That is a really cool feature you've discovered with Qt.createQmlObject(). I don't know whether the difference with Qt.createComponent() is intentional. You might want to experiment with "Binding":http://qt-project.org/doc/qt-4.8/qml-binding.html element. I have not used it myself.


  • Moderators

    So apparently, the parameters passed into "Component.createObject()":http://qt-project.org/doc/qt-5.0/qtqml/qml-qtquick2-component.html#createObject-method are used for inital value assignment only. To do binding, we need to pass in the value returned by "Qt.binding()":http://qt-project.org/doc/qt-5.0/qtqml/qml-qt.html#binding-method -- I think that's nicer than creating Binding objects, which I'll also have to create dynamically


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.