QML Row "fill" / auto-spacing? - qt

Instead of assigning a fixed spacing between items, it is possible to evenly spread the items along the (parent's) width, so as to maximize spacing?
What I currently do is:
Row {
width: parent.width
readonly property real childrenWidth: {
var w = 0
for(var child of children) w += child.width
return w
}
spacing: (width - childrenWidth) / Math.max(1, children.length - 1)
MyElement {}
MyElement {}
...
}
Is there a better way?

I think you are looking for RowLayout
RowLayout {
width: parent.width
MyElement { Layout.fillWidth: true }
MyElement { Layout.fillWidth: true }
}
If needed you can declare minimumWidth, preferredWidth and/or maximumWidth, see the documentation. Without these, the implicitWidth of MyElement is used.

Related

QML Repeater is not drawn correctly after model is changed

I'm writing a QML application to draw brown blocks inside a blue Rectangle. The application performs this task using a ColumnLayout and a Repeater to draw an arbitrary number of blocks (4 by default):
I'm attempting to change the model of the Repeater dynamically when the user clicks on the screen to force the UI to draw a different amount of blocks. Whenever the desired amount of blocks is changed via blockCount, it triggers the recalculation of blockHeight, the height of each block, so that a smaller amount of blocks can occupy more space on the screen. At least that is the theory!
For debugging purposes, clicking on the screen sets blockCount to 2.
Here's a sample image with the expected result on the Left and the current result on the Right:
As you can see on the image above, when the click happens and rectId.blockCount = 2 is executed, it appears to trigger a sequence of calls that:
Ends up changing the model of the Repeater before blockHeight is recalculated;
Or the anchors for the ColumnLayout are reset, for some bizarre reason;
Or something else is going on;
I'm trying to understand what's causing this behavior and also looking for an approach that allows the application to change the number of blocks dynamically while being able to draw them correctly!
What am I missing?
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.15
Window {
id: wndId
property int wndWidth: 200
property int wndHeight: 300
visible: true
width: wndWidth
height: wndHeight
title: qsTr("Testing ColumnLayout")
Rectangle {
id: rectId
property int borderWidth: 5 // width of the blue frame surrounding the window
property int blockCount: 4 // number of blocks to be drawn using Repeater
property int blocksSpace: 8 // minimum space between the blocks
width: wndId.wndWidth
height: wndId.wndHeight
border.color: "blue"
border.width: borderWidth
// size of each inner rectangle is computed dinamically: changing blockCount should update blockHeight
property int blockWidth: rectId.width - (rectId.borderWidth * 4)
property int blockHeight: updateBlockHeight()
function updateBlockHeight(numBlocks)
{
if (numBlocks === undefined)
numBlocks = rectId.blockCount;
var newHeight = (rectId.height - ((rectId.borderWidth + rectId.blocksSpace)*2) - (rectId.blocksSpace * (numBlocks-1))) / numBlocks;
print("updateBlockHeight: newHeight=", newHeight);
return newHeight;
}
Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)
// draw blocks on top of each other with some space between them
ColumnLayout {
spacing: rectId.blocksSpace
Layout.alignment: Qt.AlignBottom
anchors.bottom: parent.bottom
anchors.bottomMargin: rectId.borderWidth + rectId.blocksSpace
anchors.left: rectId.left
anchors.leftMargin: rectId.borderWidth*2
Repeater {
id: repId
model: rectId.blockCount
// each block size is calculated dinamically
Rectangle {
id: blockId
color: "brown"
width: rectId.blockWidth
height: rectId.blockHeight
// Debug:
Component.onCompleted: {
print("Inner Rectangle")
print(" blockCount=" + rectId.blockCount);
print(" blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
print(" blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
}
Component.onDestruction: print("~Inner Rectangle")
} // inner Rectangle
Component.onCompleted: print("Repeater")
Component.onDestruction: print("~Repeater")
} // Repeater
} // ColumnLayout
MouseArea {
anchors.fill: parent
onClicked: {
print("Mouse clicked!");
// since repId uses blockCount as the model, any change to it should automatically recreate the elements of the Repeater
// here we force blockHeight to be recalculated before the model is changed
rectId.blockHeight = rectId.updateBlockHeight(2)
// and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
rectId.blockCount = 2;
print("blockHeight= " + rectId.blockHeight);
}
}
} // outter Rectangle
} // Window
Why are you calculating the blockHeight and blockWidth while you can leverage the power of ColumnLayout?
Use the Layout.fillWidth and Layout.fillHeight properties to signal the ColumnLayout that the blocks should fill the entire width & height, evenly distributed. And then set the correct size to the ColumnLayout and it will do the calculations you try to program itself.
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.3
Window {
id: wndId
property int wndWidth: 200
property int wndHeight: 300
visible: true
width: wndWidth
height: wndHeight
title: qsTr("Testing ColumnLayout")
Rectangle {
id: rectId
property int borderWidth: 5 // width of the blue frame surrounding the window
property int blockCount: 4 // number of blocks to be drawn using Repeater
property int blocksSpace: 8 // minimum space between the blocks
width: wndId.wndWidth
height: wndId.wndHeight
border.color: "blue"
border.width: borderWidth
Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)
// draw blocks on top of each other with some space between them
ColumnLayout {
spacing: rectId.blocksSpace
Layout.alignment: Qt.AlignBottom
anchors.fill: parent
anchors.margins: rectId.borderWidth * 2
Repeater {
id: repId
model: rectId.blockCount
// each block size is calculated dinamically
Rectangle {
id: blockId
color: "brown"
Layout.fillWidth: true
Layout.fillHeight: true
// Debug:
Component.onCompleted: {
print("Inner Rectangle", index)
print(" blockCount=" + rectId.blockCount);
print(" blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
print(" blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
}
Component.onDestruction: print("~Inner Rectangle")
} // inner Rectangle
Component.onCompleted: print("Repeater")
Component.onDestruction: print("~Repeater")
} // Repeater
} // ColumnLayout
MouseArea {
anchors.fill: parent
onClicked: {
print("Mouse clicked!");
// and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
rectId.blockCount = 2;
print("blockHeight= " + rectId.blockHeight);
}
}
} // outter Rectangle
} // Window
EDIT for keeping blockHeight calculation
If you insist on keeping the calculation since it is more difficult in the real world (fair enough), I would suggest to use implicitHeight and implicitWidth. This works because the Layout engine does not trigger on changes on width/height since it is supposed to set these himself, it does however monitor the implicit sizes:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.3
Window {
id: wndId
property int wndWidth: 200
property int wndHeight: 300
visible: true
width: wndWidth
height: wndHeight
title: qsTr("Testing ColumnLayout")
Rectangle {
id: rectId
property int borderWidth: 5 // width of the blue frame surrounding the window
property int blockCount: 4 // number of blocks to be drawn using Repeater
property int blocksSpace: 8 // minimum space between the blocks
width: wndId.wndWidth
height: wndId.wndHeight
border.color: "blue"
border.width: borderWidth
// size of each inner rectangle is computed dinamically: changing blockCount should update blockHeight
property int blockWidth: rectId.width - (rectId.borderWidth * 4)
property int blockHeight: updateBlockHeight()
function updateBlockHeight(numBlocks)
{
var newHeight = (rectId.height - ((rectId.borderWidth + rectId.blocksSpace)*2) - (rectId.blocksSpace * (rectId.blockCount-1))) / rectId.blockCount;
print("updateBlockHeight: newHeight=", newHeight);
return newHeight;
}
Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)
// draw blocks on top of each other with some space between them
ColumnLayout {
spacing: rectId.blocksSpace
Layout.alignment: Qt.AlignBottom
anchors.fill: parent
anchors.margins: rectId.borderWidth * 2
Repeater {
id: repId
model: rectId.blockCount
// each block size is calculated dinamically
Rectangle {
id: blockId
color: "brown"
implicitWidth: rectId.blockWidth
implicitHeight: rectId.blockHeight
onXChanged: print("x[",index,"]=", x)
onYChanged: print("y[",index,"]=", y)
// Debug:
Component.onCompleted: {
print("Inner Rectangle", index)
print(" blockCount=" + rectId.blockCount);
print(" blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
print(" blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
}
Component.onDestruction: print("~Inner Rectangle")
} // inner Rectangle
Component.onCompleted: print("Repeater")
Component.onDestruction: print("~Repeater")
} // Repeater
} // ColumnLayout
MouseArea {
anchors.fill: parent
onClicked: {
print("Mouse clicked!");
// and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
rectId.blockCount = 2;
print("blockHeight= " + rectId.blockHeight);
}
}
} // outter Rectangle
} // Window
Also, I refactored the updateBlockHeight function, it's not needed to explicitly set it, the QML engine is so smart it will even reevaluate the binding when one of the parameters in the function changes!
(From my comments)
With Qt 5.15.1 on Linux get the expected output. With Qt 5.15 get the output you indicate for what is probably a bug that has been fixed in Qt 5.15.1.

How to translate a QQuickItem's position into its new parent

I have 2 QQuickItems like below which I can fetch on C++ side using the QMLEngine like this.
QQuickItem * quick_item_1 = m_qml_engine->rootObjects()[0]->findChild<QQuickItem*>("quickitem1");
QQuickItem * quick_item_2 = m_qml_engine->rootObjects()[0]->findChild<QQuickItem*>("quickitem2");
Note: quick_item_1's immediate parent is different & quick_item_2's immediate parent is also different. But they both are drawn on the same application window into different immediate parents.
I am drawing both of them offscreen on a different QQuickItem. Let's call it new_parent_surface. I draw both these items on new_parent_surface by changing their parent to new_parent_surface like this.
quick_item_1->setParentItem(new_parent_surface);
quick_item_2->setParentItem(new_parent_surface);
This works fine for the objective of drawing them on a new parent QQuickItem. I get the both quick_item_1 & quick_item_2 drawn on new_parent_surface. Even though new_parent_surface is not drawn on UI, but if I take a snapshot using grabToImage of new_parent_surface, I can see the 2 items drawn on them. Fine till here.
However the positioning of quick_item_1 & quick_item_2 is not correct. I want to position them similar to the way they were positioned their original parent item. I can do some percentage math & try positioning them the same way as they were drawn on their original parent but isn't there a QQQuickItem or Qt API to translate this positioning to a new parent?
I tried to look into QQuickItem's mapping APIs like mapToItem & trying them out like this.
quick_item_2->mapToItem(new_parent_surface, quick_item_2->position());
But the positioning is still not correct.
So, how can I map a QQuickItem's position into its new parent QQuickItem after doing a setParentItem?
Items position is always relative to its parent. And position of the parent is relative to its parent and and so on. But you always can get both relative or global position. QML has lots of coordination translation function. Here is small example that could explain the issue:
import QtQuick 2.7
import QtQuick.Window 2.0
import QtQuick.Controls 2.1
Window {
id:container
width: 800
height: 800
visible: true
Component {
id: rect
Rectangle {
property bool imParent: false
x: 50 + Math.round(Math.random() * 550)
y: 50 + Math.round(Math.random() * 550)
width: 100 + Math.round(Math.random() * 100)
height: 100 + Math.round(Math.random() * 100)
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1);
Drag.active: dragArea.drag.active
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
}
Text {
anchors.centerIn: parent
text: imParent ? "I'm parent" : "Drag me"
color: "white"
}
}
}
Rectangle {
id: blk
x: 10
y: 10
z: 100
parent: null
height: 50
width: 50
radius: 5
border.color: "white"
color: "black"
}
Repeater {
id: reptr
model: 5
property int pos: 0
Loader {
id: loader
sourceComponent: rect
onLoaded: {
if(blk.parent == null) {
blk.parent = loader.item;
loader.item.imParent = true;
}
}
}
}
Row {
anchors.horizontalCenter: container.contentItem.horizontalCenter
spacing: 2
Button {
text: "Reparent relative to parent"
onClicked: {
reptr.pos ++;
if(reptr.pos >= reptr.model) {
reptr.pos = 0;
}
var item = reptr.itemAt(reptr.pos).item;
blk.parent.imParent = false;
blk.parent = item;
blk.parent.imParent = true;
}
}
Button {
text: "Reparent relative to scene"
onClicked: {
reptr.pos ++;
if(reptr.pos >= reptr.model) {
reptr.pos = 0;
}
var item = reptr.itemAt(reptr.pos).item;
var coord = blk.mapToGlobal(blk.x, blk.y);
blk.parent.imParent = false;
blk.parent = item;
blk.parent.imParent = true;
coord = blk.mapFromGlobal(coord.x, coord.y);
blk.x = coord.x;
blk.y = coord.y;
}
}
}
}

How to vertical scroll to a specific item

I have a ListView including a lot of radio buttons. The list is bigger than the visible area. One of the radio buttons is checked. Sometimes, if the selected radio button is outside the visible area I want to scroll to it:
ScrollView {
anchors.fill:parent
ListView {
anchors.fill: parent
model: valuesList
delegate: RadioButton {
id: radioBtn
//check of value is index type and do the corresponding checked? test
checked: valueIsIndex ? (parseInt(valueFromParent) == index ? true : false) : (valueFromParent == valueString ? true : false)
onClicked: {
root.selected(valueString, index)
}
Component.onCompleted: {
if(checked)
//Here i want to scroll the list to display this radiobutton
}
}
}
}
Any ideas howto to get the list scrolled? I've played around a lot of with hightlights and contentY but nothing worked.
I've use the ScrollView around the ListView to automatically get the systems scrollbars on the desktop. On mobile devices i have just the flickable ListView.
EDIT
I get it on the way with the help of BaCaRoZzo. Here is my current working example:
ScrollView {
id: scrollView
anchors.fill:parent
property int yOfCheckedRadioButton: 0
ListView {
id:listView
anchors.fill: parent
spacing: Math.round(appWindow.height*0.05)
model: internalValuesList
delegate: RadioButton {
id: radioBtn
//check of value is index type and do the corresponding checked? test
checked: checktest()
style: MyRadioButtonStyle {
myRadioBtn: radioBtn
labelString: value
}
Component.onCompleted: {
//set the position of the checked RadioButton to scroll to it later onContentHeightChange
if(checked) {
var checkedRadioBtnPositionY = Math.round((radioBtn.height + listView.spacing) * index - radioBtn.height * 1.5)
if( checkedRadioBtnPositionY > 0)
scrollView.yOfCheckedRadioButton = checkedRadioBtnPositionY
else
scrollView.yOfCheckedRadioButton = 0
}
}
}
onContentHeightChanged: {
//scroll to the checked RadioButton
contentY = scrollView.yOfCheckedRadioButton
}
}
}
I recall problems with scroll before Qt 5.4 when I found a workaround like:
ScrollView {
anchors.fill: parent // mind how you stretch it
contentItem:
Flow {
id: flow
spacing: 10 // mind gaps
width: parent.parent.width - 20 // select proper width
// Put anything you would like to scroll in here
// Mind that Flow positions items one after another
// left to right, top to bottom
// You can also try containers other than Flow
// but whether it works or not may depend on Qt version
ExclusiveGroup { id: tabPositionGroup }
RadioButton {
text: "RB1"
checked: true
exclusiveGroup: tabPositionGroup
}
RadioButton {
text: "RB2"
exclusiveGroup: tabPositionGroup
}
}
}
Whether ScrollView needs an explicit contentItem or not is another matter and it certainly may not need it but that does not hurt if the SrollView needs to resolve what it actually scrolls.

Wrong property values in Component.onCompleted

My problem is the following: I have a custom QML object based on a Rectangle, and I need to execute some javascript function with the width & height of this object when it's created.
To do that, I use Component.onCompleted to execute the javascript but in this function the width and height properties are wrong (like 0;0 or 0;-30), as if they were not yet created.
Code:
import QtQuick 2.3
import "js/kloggr.js" as Game
Rectangle {
property var kloggr: undefined
MouseArea {
anchors.fill: parent
// outputs the right values (about 400;600)
onClicked: console.log(width+";"+height);
}
Component.onCompleted: {
Game.kloggr = this;
kloggr = new Game.Kloggr(width, height);
console.log(width+";"+height); // outputs 0;-30
}
}
This object is created like this:
Kloggr {
id: kloggr
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: pauseBtn.top
}
(I've removed the irrelevant parts of the code)
So what did I do wrong, or what could I do to get the same result?
You have forget to set the width and height of your Rectangle ...
import QtQuick 2.3
import "js/kloggr.js" as Game
Rectangle {
width: 300
height: 300
property var kloggr: undefined
MouseArea {
anchors.fill: parent
// outputs the right values (about 400;600)
onClicked: console.log(width+";"+height);
}
Component.onCompleted: {
Game.kloggr = this;
kloggr = new Game.Kloggr(width, height);
console.log(width+";"+height); // outputs 0;-30
//now h + w = 300
}
}
If your use anchors, change your Rectangle by Item, for exemple
import QtQuick 2.3
import "js/kloggr.js" as Game
Item{
//without W and Height if you don't use the designer, if you use the designer you should set a default size
property var kloggr: undefined
Rectangle{
anchors.fill : parent
MouseArea {
anchors.fill: parent
// outputs the right values (about 400;600)
onClicked: console.log(width+";"+height);
}
Component.onCompleted: {
Game.kloggr = this;
kloggr = new Game.Kloggr(width, height);
console.log(width+";"+height); // outputs 0;-30
}
}//end of rectangle
} //end of item
I did not find out it if was a bug or something but I found a workaround: instead of using Component.onCompleted I used onVisibleChanged:
onVisibleChanged: {
if (visible && kloggr === undefined) {
Game.kloggr = this;
kloggr = new Game.Kloggr(width, height);
}
}
It seems that the width and height properties are only valid when the object is set to visible...
Thank you #yekmen for your help

QML - tracking global position of a component

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 mapFromItem as follows:
SomeObject {
x: ancestor.mapFromItem(trackedObject, trackedObject.x, 0).x
y: ancestor.mapFromItem(trackedObject, 0, trackedObject.y).y
}
The problem with this approach is that the mapFromItem is evaluated once and doesn't update as one of it's arguments gets updated. Moreover the mapping sometimes returns the new position altered by an offset I'm unable to track in the code (but that's not the matter at hand).
My second idea was to calculate the global position by implementing a function that would recursively sum the offsets, stopping at the provided ancestor (something like calculateOffsetFrom(ancestor)). Still this is just a function and as far as I'm concerned it won't get re-evaluated as one of the ancestors position changes (unless, in that function, I'll bind calling it to the onXChanged signal for each one of the ancestors along the way, which seems like a dirty solution).
So in the end I've added properties to the object I intend to track and then I bind to them:
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
}
But well... yeah... this one doesn't scale at all and is as ugly as it gets.
Does anyone have any idea how this problem might be solved in a cleaner way?
Edit:
As far as I'm concerned I can't use anchors in this case. The SomeObject component is a custom component drawing a bezier curve from one point to another (it will connect two TrackedObjects). For that I need the difference between the coordinates. If I'm correct anchors don't provide any way of calculating the distance between them.
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.
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;
};
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
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.
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.

Resources