How to get id of child in qml - qt

When I tried to get children ID's slightly modifying this http://qmlbook.github.io/en/ch01/index.html example
// animate.qml
import QtQuick 2.0
Item
{
id: root
width: background.width
height: background.height
property string information: "this is root"
property int rotationStep: 45
Image {
id: background
source: "images/background.png"
}
Image {
id: pole
property string information: "this is pole"
x: (root.width - width)/2+2
y: root.height - height
source: "images/pole.png"
}
Image {
id: pinwheel
anchors.centerIn: parent
source: "images/pinwheel.png"
property string information: "this is pinweel"
// visible: false
Behavior on rotation {
NumberAnimation { duration: 125 }
}
}
Image {
id: blur
opacity: 0
anchors.centerIn: parent
source: "images/blur.png"
property string information: "this is blur"
// visible: false
Behavior on rotation {
NumberAnimation { duration: 125 }
}
Behavior on opacity {
NumberAnimation { duration: 125 }
}
}
// M1>>
focus: true
Keys.onLeftPressed: {
blur.opacity = 0.8
pinwheel.rotation -= root.rotationStep
blur.rotation -= root.rotationStep
}
Keys.onRightPressed: {
blur.opacity = 0.5
pinwheel.rotation += root.rotationStep
blur.rotation += root.rotationStep
}
Keys.onReleased: {
blur.opacity = 0
}
Keys.onSpacePressed:
{
for (var i=0; i<root.children.length; ++i)
console.info(root.children[i].information)
}
Keys.onDeletePressed:
{
for (var i=0; i<root.children.length; ++i)
console.info(root.children[i].id)
}
// <<M1
}
Unfortunately pressing Delete key gives me an error:
qml: undefined
qml: undefined
qml: undefined
qml: undefined
as opposed to pressing spacebar:
qml: undefined
qml: this is pole
qml: this is pinweel
qml: this is blur
Why this script returns undefined id's ?
I need to traverse some objects and be able to tell what is what - so I need to know how to traverse root tree to get id's of its childs and their object types.
Unfortunately I was unable to print the most trival id's and had to add some simple property to get it done but this means a lot of work in real life project since every object needs information property :(
So to reiterate:
Why the id's in this example are undefined?
How to traverse object tree using qml and print its id's and types ?

Id is not an ordinary object property, so it is undefined when you try to assess it through js. And qml doesn't provide operators like typeof. So you need to add type or objectname property manually. I would consider subclassing Image and adding type. Ref: How to do a "is_a", "typeof" or instanceof in QML?
Item
{
id: root
Image {
id: background
type: "image"
}
Image {
id: pole
type: "image"
}
function iter(){
for(var i = 0; i < root.children.length; ++i)
if(root.children[i].type==="image"){
//balabala
}
}
}
}

Related

Dynamically bind values from qml to repeater created object

I control the position of some elements of my scene using alias properties likes this : If I have a file Foo.qml containing
Item {
property alias myprop1: id1
property alias myprop2: id2
Node {id:id1,...}
Node {id:id2,...}
On my main, I can then call
Slider{
id:myslider
}
foo{
myprop1.x: myslider.value
}
Now if my Foo.qml contains an unknow number of properties (lets say they are all called mypropX). If I have 10 properties I want to create 10 sliders, one for each property. It is possible with a repeater and loop like mentioned in last answer here
Foo{
id:myfoo
}
Column {
Repeater {
id: myrepeater
delegate: Slider {
from:0
to:400
y: 12*index
}
Component.onCompleted: {
let propArray = [];
for(var prop in myfoo){
//select only the properties I'm interested in
//a "onXXXChanged" is created on each properties so I also have to remove it
if(prop.substring(0, 6)==="myprop" && prop.substring(prop.length-7,prop.length)!=="Changed"){
propArray.push(prop)
}
}
myrepeater.model = propArray
}
}
}
The problem is now that I don't know how to bind those 10 sliders to my properties.
I tried adding to my Foo instance in main
Component.onCompleted: {
let i=0
for(var prop in myfoo){
if(prop.substring(0, 6)==="myprop" && prop.substring(prop.length-7,prop.length)!=="Changed"){
//equivalent to myprop1.x: myslider.value when there was no repeater
myfoo.prop.x = Qt.binding(function() {
return myrepeater.itemAt(i).value
})
i++
}
}
}
But it return
QQmlEngine::setContextForObject(): Object already has a QQmlContext
qrc:/main.qml:145: Error: Cannot assign to non-existent property "prop"
The problem is that in the for loop, prop is a string. I am also not sure that at the moment the onCompleted is executed, the repeater has already created all the slidders.
I could use the QML type Bindings{} which takes a target (myrepeater.itemAt(i).value) and the property name as a string, but I don't know how to call the Bindings{} type from javascript
You can use the [] operator to read the properties from myfoo and as discussed I would use a Binding object inside the delegate:
import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Item {
id: myfoo
property int myprop_upper_threshold
onMyprop_upper_thresholdChanged: console.log("upper_threshold", myprop_upper_threshold)
property int myprop_lower_threshold
onMyprop_lower_thresholdChanged: console.log("lower_threshold", myprop_lower_threshold)
}
ColumnLayout {
Repeater {
id: myrepeater
delegate: Slider {
id: myslider
from: 0
to: 400
Text {
text: modelData
}
Binding {
target: myfoo
property: modelData
value: myslider.value
}
}
Component.onCompleted: {
let propArray = [];
for(var prop in myfoo)
{
//select only the properties I'm interested in
//a "onXXXChanged" is created on each properties so I also have to remove it
if(prop.substring(0, 6)==="myprop" && prop.substring(prop.length-7,prop.length)!=="Changed")
{
propArray.push(prop)
}
}
myrepeater.model = propArray
}
}
}
}

QML: Bind loop detected without double assignment

As far as I know the bind loop happens when I try to assign two properties each other. Example:
CheckBox {
checked: Settings.someSetting
onCheckedChanged: {
Settings.someSetting = checked;
}
}
but in my scenario I can't see such a "double assignment". I report here the full code:
import QtQuick 2.7
import QtQuick.Window 2.3
Window {
visible: true;
width: 500
height: 500
Rectangle {
id: main
anchors.fill: parent
color: "black"
property bool spinning: true
property bool stopping: false
Rectangle {
x: 0.5 * parent.width
y: 0.5 * parent.height
width: 10
height: 200
radius: 5
color: 'red'
transformOrigin: Item.Top
rotation: {
if (main.stopping)
{
main.spinning = false;
main.stopping = false;
}
return timer.angle
}
}
Timer {
id: timer
interval: 5
repeat: true
running: true
onTriggered: {
if (main.spinning) angle += 1;
}
property real angle
}
MouseArea {
id: control
anchors.fill: parent
onClicked: {
main.stopping = true;
}
}
}
}
When you click with the mouse you will get the warning:
qrc:/main.qml:17:9: QML Rectangle: Binding loop detected for property "rotation"
I don't see my mistake. I'm using flags (bool variables) to control the execution of my code. I know in this case I can just stopping the timer directly, but the actual program is more complex than this example.
The binding is in the following lines:
rotation: {
if (main.stopping)
{
main.spinning = false;
main.stopping = false;
}
return timer.angle
}
The change of rotation is triggered by the change of main.stopping: let's say that change main.stopping is given by the mouseArea, then it will be called a rotation, but inside this there is an if, and in this you are changing back to main.stopping , where he will call rotation back.
If a property in QML changes all the elements that depend on it will change

How to refer to the parent of the parent correctly?

There's an element FlowChart with properties p1,p2,p3 ... pn.
SensorValuesSetter must be able to access these properties by using the element Button which is located deeply in it.
I imagine the solution like that:
// main.qml
// ...
FlowChart {
id: flowChart
anchors.fill: parent
SensorValuesSetter {
id: valueSetterWindow
}
// ...
}
// SensorValuesSetter.qml
ApplicationWindow {
id: valueSetterWindow
// ...
GridLayout {
// ...
Label { text: "Давление 1: "; Layout.fillWidth:true; }
ValueInputField { id: p1_val_field; }
Label { text: "Давление 2: "; Layout.fillWidth:true; }
ValueInputField { id: p2_val_field; }
// ....
Button {
id: button
text: qsTr("Применить")
onPressed: {
valueSetterWindow.parent.p1.value = Number.fromLocaleString(p1_val_field.text)
valueSetterWindow.parent.p2.value = Number.fromLocaleString(p2_val_field.text)
// ...
}
}
But in this case errors TypeError: Cannot read property 'p1' of undefined or TypeError: Cannot read property 'p1' of undefined appear.
Could you please explain me what's the problem? I guess the clue is that I tried to refer to the parent's parent with wrong way.
ApplicationWindow is no Item and therefore has no property parent.
You can either rely on dynamic scoping (see more on the scopes here)
Or you pass the parent explicitly in a new property:
// SensorValuesSetter.qml
ApplicationWindow {
property Item parent // Add the missing property. Maybe some other name...
[...]
}
And where you instantiate it:
SomeItem {
id: myItem
SensorValuesSetter {
parent: myItem // Set it explicitly
}
}

QML: How to move an object from a previous set postion using states

I have an object that I want to move from it's previously set position every time that particular state is set. I've tried making a separate property called xPos to get around the binding loop error which is set by the object's new position of x after the state is set, then entering a default state just to be able to switch back to that specific state again since calling the same state does nothing but it doesn't seem to work.
Here is a snippet of my code:
property int xPos: 0
states: [
State {
name: "nextStep"
PropertyChanges {
target: progressBar_Id
x: -1*(progressBar_Id.step.width) + xPos
}
},
State {
name: "previousStep"
PropertyChanges {
target: progressBar_Id
x: progressBar_Id.step.width + xPos
}
},
State {
name: "default"
PropertyChanges {
target: progressBar_Id
x: xPos
}
}
]
transitions: [
Transition {
from: "*"
to: "nextStep"
NumberAnimation {properties: "x"; easing.type: Easing.Linear; duration: 1000}
onRunningChanged: {
if(!running) {
xPos = progressBar_Id.x;
console.info("xPos = " + xPos);
state = "default";
}
}
},
Transition {
from: "*"
to: "previousStep"
NumberAnimation {properties: "x"; easing.type: Easing.Linear; duration: 1000}
onRunningChanged: {
if(!running) {
xPos = progressBar_Id.x;
console.info("xPos = " + xPos);
state = "default";
}
}
}
]
xPos seems to get set the first time from the console output but never applies the new xCoord to the object.
Explicitly specify the id of the item on which you wish to set the state, e.g:
idOfComponent.state = "default"
All QML Items and derivatives have a property called state, so the code needs to be more specific.
Actually came up with a much better alternative using a ListView.
ListView {
id: listView_Id
width: contentWidth
height: bubbleSize
anchors.centerIn: parent
layoutDirection: Qt.RightToLeft
orientation: ListView.Horizontal
spacing: 0
delegate: Item {
width: bubbleSize
height: width
ProgressBubble {
stateText: stateNumber
currentState: state
}
}
model: ListModel { id: progressModel_Id }
}
Another qml file:
progressModel.insert(0, {stateNumber: number++, state: "current"});
But I ran into another problem, is there anyway to change a state within a delegate after it's been added to the ListView?
I've already tried progressModel.set and progressModel.setProperty but doesn't seem to work.
state is a property for qml types, so when you are assigning "state" to "currentState" for "ProgressBubble", its taking the value of state in which " ProgressBubble" is currently present.
Rename "state" to something else like "presentState" and then try.
Moreover id of ListView model(progressModel_Id) and the one used to insert model elements(progressModel) in different file are different, both of them must refer to same id.
After these changes, you can try set property of model. Sample code snippet:
import QtQuick 2.0
Rectangle {
id: root
width: 360
height: 360
property int number: 1
ListView {
id: listView_Id
width: 100 //contentWidth // this creates property binding issue
height: 100 // bubbleSize // I was not sure about this value
anchors.centerIn: parent
layoutDirection: Qt.RightToLeft
orientation: ListView.Horizontal
spacing: 0
delegate: Item {
width: 100 // bubbleSize // I was not sure about this value
height: width
ProgressBubble {
state: "default"
stateText: stateNumber
currentState: presentState
MouseArea {
anchors.fill: parent
onClicked: {
progressModel_Id.set(index,{stateNumber: stateNumber, presentState: "not current"})
}
}
}
}
model: ListModel { id: progressModel_Id }
}
Component.onCompleted:
{
progressModel_Id.insert(0, {stateNumber: number++, presentState: "current"});
}
}

Clear QML anchor

I have a MouseArea that I want to start off centered and then have an absolute position once the up/down/left/right keys are pressed. My problem is that I don't know how to clear the anchor on the MouseArea so that I can specify an absolute position:
import QtQuick 2.0
import QtQuick.Window 2.0
Window {
id: screen
width: 360
height: 360
visible: true
Rectangle {
anchors.fill: parent
states: [
State {
name: "moved"
AnchorChanges {
target: mouseArea
anchors.bottom: undefined
anchors.left: undefined
anchors.right: undefined
anchors.top: undefined
}
}
]
MouseArea {
id: mouseArea
anchors.centerIn: parent
width: 250
height: 250
focus: true
onClicked: console.log("clicked!")
onPositionChanged: console.log("position changed!")
function moveMouseArea(x, y) {
mouseArea.x += x;
mouseArea.y += y;
mouseArea.state = "moved";
mouseAreaPosText.text = 'Mouse area was moved... new pos: '
+ mouseArea.x + ', ' + mouseArea.y;
}
Keys.onPressed: {
if (event.key === Qt.Key_Up)
moveMouseArea(0, -1);
if (event.key === Qt.Key_Down)
moveMouseArea(0, 1);
if (event.key === Qt.Key_Left)
moveMouseArea(-1, 0);
if (event.key === Qt.Key_Right)
moveMouseArea(1, 0);
}
Rectangle {
anchors.fill: parent
border.width: 2
border.color: "black"
color: "transparent"
}
Text {
id: mouseAreaPosText
anchors.centerIn: parent
}
}
}
}
At first I just tried setting mouseArea.anchors to undefined but got an error about anchors being a read-only property. I then discovered AnchorChanges, but I can't find a way to remove/clear the anchor; setting anchors.bottom etc. to undefined doesn't work.
According to docs, setting an anchor attribute to undefined should work. I don't quite get why AnchorChanges did not allow to set anchors.centerIn, but you can workaround it in your moveMouseArea function:
function moveMouseArea(x, y) {
mouseArea.anchors.centerIn = undefined; // <-- reset anchor before state change
mouseArea.pos.x += x;
mouseArea.pos.y += y;
mouseArea.state = "moved";
mouseAreaPosText.text = 'Mouse area was moved... new pos: '
+ mouseArea.pos.x + ', ' + mouseArea.pos.y;
}
Thanks for your help guys. I have found that setting undefined within a state works (if by works you mean only that it doesn't give errors), however once the element moves to yet another state, the anchors magically (and very frustratingly) return. This happens EVEN if you set all anchors undefined in the final state. As mentioned above however, setting undefined in the function before changing state works great. In my case, I set it in my mouseArea in onPressed.
onPressed: {
plotWindow04Frame.anchors.bottom = undefined
plotWindow04Frame.anchors.left = undefined
plotWindow04Frame.state = "inDrag"
}
I found it was not necessary to mention the anchor in the onReleased, just the next state.
onReleased: {
plotWindow04Frame.state = "dropped"
}
Also, I should mention, that the final "dropped" state does not mention anchors either, just opacity.
states: [
State {
name: "inDrag"
PropertyChanges {
target: plotWindow04Frame
opacity: .5
}
},
State {
name: "dropped"
PropertyChanges {
target: plotWindow04Frame
opacity: 1
}
}
]
transitions: Transition {
NumberAnimation { properties: "opacity"; duration:200 }
}
}
(The idea here was that these plot windows would become translucent (opacity: 0.5) while dragging, but return to opaque (opacity: 1) when the user drops them)
What is nice is that the plotwindow "rectangles" are initially anchored to the bottom of the GUI, but once the user picks them up, they can put them where ever they like.

Resources