How to track the coordinates of draggable, dynamically-created Qt Quick objects?
-
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.0Rectangle {
width: 800
height: 600Rectangle { 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!
-
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 :)
-
[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
-
See this, for example:
- "accessing children":https://github.com/sierdzio/closecombatfree/blob/1129caa84a07fad9001a371bc22d9e00ef93bca9/qml/scenarios/Scenario.qml#L275
- "children arrays as properties":https://github.com/sierdzio/closecombatfree/blob/1129caa84a07fad9001a371bc22d9e00ef93bca9/qml/scenarios/Scenario.qml#L41 and "this":https://github.com/sierdzio/closecombatfree/blob/1129caa84a07fad9001a371bc22d9e00ef93bca9/qml/maps/Map.qml#L39
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.
-
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.0Rectangle {
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?
-
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.
-
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