Discussion:
Set custom property in onChildrenChanged?
Charley Bay
2010-10-26 12:49:58 UTC
Permalink
Curious problem, this should work, right?

//FILE: MyRect.qml
Rectangle {
property double myDouble: 0
}

//FILE: MyParent.qml
Item {
id: myParent
onChildrenChanged: {
for(var i = 0; i < myParent.children.length; ++i) {
// Error: Cannot assign to non-existent property "myDouble"
myParent.children.[i].myDouble = 1.0
}
}
}

MyRect {
parent: myParent
}

Everything instantiates properly, and "MyRect" is properly
being parented under "MyParent", with "onChildrenChanged"
being called.

Can I not access the custom property? Do I need to cast
to a "MyRect" inside "onChildrenChanged"?

In this case, I *always* know it is a "MyRect", but I thought
the dynamic typing would have found the "myDouble"
property.

Should this code work? (Or what should I do instead?)

Thanks!

--charley
a***@public.gmane.org
2010-10-26 12:59:53 UTC
Permalink
Hi,

Shouldn't the access line read

myParent.children[i].myDouble = 1.0

?

Cheers,

Aaron
________________________________________
From: qt-qml-bounces-Ihz76zOu8S21Z/+***@public.gmane.org [qt-qml-bounces-Ihz76zOu8S21Z/+***@public.gmane.org] on behalf of ext Charley Bay [charleyb123-***@public.gmane.org]
Sent: Tuesday, October 26, 2010 10:49 PM
To: qt-qml-Ihz76zOu8S21Z/+***@public.gmane.org
Subject: [Qt-qml] Set custom property in onChildrenChanged?

Curious problem, this should work, right?

//FILE: MyRect.qml
Rectangle {
property double myDouble: 0
}

//FILE: MyParent.qml
Item {
id: myParent
onChildrenChanged: {
for(var i = 0; i < myParent.children.length; ++i) {
// Error: Cannot assign to non-existent property "myDouble"
myParent.children.[i].myDouble = 1.0
}
}
}

MyRect {
parent: myParent
}

Everything instantiates properly, and "MyRect" is properly
being parented under "MyParent", with "onChildrenChanged"
being called.

Can I not access the custom property? Do I need to cast
to a "MyRect" inside "onChildrenChanged"?

In this case, I *always* know it is a "MyRect", but I thought
the dynamic typing would have found the "myDouble"
property.

Should this code work? (Or what should I do instead?)

Thanks!

--charley
Charley Bay
2010-10-26 13:22:09 UTC
Permalink
Post by a***@public.gmane.org
Shouldn't the access line read
   myParent.children[i].myDouble = 1.0
?
Ooops, yes: I incorrectly re-typed it in the
email with the extra '.' (but the problem still
exists in my code).

Thanks!

--charley
m***@public.gmane.org
2010-10-28 05:03:15 UTC
Permalink
Hi,
Post by Charley Bay
Curious problem, this should work, right?
//FILE: MyRect.qml
Rectangle {
property double myDouble: 0
}
//FILE: MyParent.qml
Item {
id: myParent
onChildrenChanged: {
for(var i = 0; i < myParent.children.length; ++i) {
// Error: Cannot assign to non-existent property "myDouble"
myParent.children.[i].myDouble = 1.0
}
}
}
MyRect {
parent: myParent
}
Everything instantiates properly, and "MyRect" is properly
being parented under "MyParent", with "onChildrenChanged"
being called.
Can I not access the custom property? Do I need to cast
to a "MyRect" inside "onChildrenChanged"?
In this case, I *always* know it is a "MyRect", but I thought
the dynamic typing would have found the "myDouble"
property.
Should this code work? (Or what should I do instead?)
Yes, it should work (with the syntax fix already mentioned). Trying it with HEAD everything seems to work correctly (myDouble is set to 1; no errors), so perhaps its a bug in 4.7.0 that's been fixed since?

Regards,
Michael
Charley Bay
2010-10-30 17:11:54 UTC
Permalink
<snip, set custom property in "onChildrenChanged">
Yes, it should work <snip>
Yes, it works, it was my error. What I'm doing is a little
more complicated, and there was enough obfuscation
for me to confuse myself. ;-)

I now have something that "works", but I want to get
opinions on whether my solution is "stupid" or "best practice".

After further investigation, here's what's going on:

- I create a "myParent" Item{}
- I create "child" items that are not yet parented to my "myParent"
- I explicitly parent the child items with "{ parent: myParent }"

The above works. My confusion was based on:

- the "child" items have application-specific properties
to be set when re-parented (e.g., layout attributes)

- the "myParent" had *additional* (hard-coded) children,
like background rendering, that do not have the
application-specific properties of these "transient"
children.

Because my "onChildrenChanged()" in "myParent"
merely iterated all its children, it attempted to access
application-specific attributes in the "hard-coded"
children (when it should have only accessed those
attributes in the transient children).

Because QML is typesafe, these failed accesses
on the hard-coded children triggered an exception.
IMHO, this is "good" (I am pleased QML provides this
safety -- it backs me up in my design decisions).

QUESTION: What is the preferred mechanism to
"test" children for type, or for "id:", or any distinguishing
characteristic?

For example, my solution is to "test" for my application-
specific attribute, at which point I "conclude" this is
a transient child:

Item {
id: myParent
onChildrenChanged: {
for(var i = 0; i < myParent.children.length; ++i) {
if(myParent.children[i].myApplicationSpecificAttribute === undefined) {
// Not transient child
} else {
// Is transient child
myParent.children[i].myApplicationSpecificAttribute = 1.0;
}
}
}
}

This works. However, I wanted to "see" what was in the child
to understand what I could work with, so I printed it out:

...inserting this snippet to inspect each child...

for (var myKey in myParent.children[i]) {
console.log("child[" + myKey + "] == (" +
mySdGlobe.children[i][myKey] + ")");
}

...I get something like:

-----------------------------------------
child[childrenChanged] == ([object Object])
child[scaleChanged] == ([object Object])
child[heightChanged] == ([object Object])
child[deleteLater] == ([object Object])
child[colorChanged] == ([object Object])
child[yChanged] == ([object Object])
child[gradient] == (null)
child[state] == ()
child[top] == (QVariant(QDeclarativeAnchorLine))
child[opacityChanged] == ([object Object])
child[smooth] == (false)
child[width] == (0)
child[visible] == (true)
child[visibleChanged] == ([object Object])
child[rotationChanged] == ([object Object])
child[transitions] == ([object Object])
child[childrenRectChanged] == ([object Object])
child[data] == ([object Object])
child[clip] == (false)
child[resources] == ([object Object])
child[anchors] == (QDeclarativeAnchors(0x60382b8))
child[right] == (QVariant(QDeclarativeAnchorLine))
child[parent] == (QDeclarativeItem(0x5ef5850))
child[verticalCenter] == (QVariant(QDeclarativeAnchorLine))
child[xChanged] == ([object Object])
child[pos] == ([object Object])
child[myDoubleChanged] == ([object Object])
child[focus] == (false)
child[activeFocusChanged] == ([object Object])
child[states] == ([object Object])
child[color] == (#ffffff)
child[baselineOffset] == (0)
child[updateMicroFocus] == ([object Object])
child[horizontalCenter] == (QVariant(QDeclarativeAnchorLine))
child[myDouble] == (0) <=========MY APPLICATION-SPECIFIC ATTRIBUTE
child[bottom] == (QVariant(QDeclarativeAnchorLine))
child[mapFromItem] == ([object Object])
child[border] == (QDeclarativePen(0x6040890))
child[forceActiveFocus] == ([object Object])
child[baselineOffsetChanged] == ([object Object])
child[baseline] == (QVariant(QDeclarativeAnchorLine))
child[transformOriginPoint] == ([object Object])
child[scale] == (1)
child[left] == (QVariant(QDeclarativeAnchorLine))
child[parentChanged] == ([object Object])
child[transformOriginChanged] == ([object Object])
child[radiusChanged] == ([object Object])
child[activeFocus] == (false)
child[childrenRect] == ([object Object])
child[enabledChanged] == ([object Object])
child[effect] == (null)
child[children] == ([object Object])
child[opacity] == (1)
child[rotation] == (0)
child[zChanged] == ([object Object])
child[clipChanged] == ([object Object])
child[focusChanged] == ([object Object])
child[childAt] == ([object Object])
child[mapToItem] == ([object Object])
child[transform] == ([object Object])
child[transformOrigin] == (4)
child[smoothChanged] == ([object Object])
child[objectName] == ()
child[x] == (0)
child[enabled] == (true)
child[y] == (0)
child[z] == (0)
child[widthChanged] == ([object Object])
child[stateChanged] == ([object Object])
child[height] == (0)
child[radius] == (0)
-----------------------------------------

Good stuff. I can see exactly what's going on.

I don't want to dig too deeply into QML internals (I assume some
of these keys may change), but I was looking for an "id:" field to
uniquely identify the object, and didn't find it (and the "objectName"
field is empty, so I assume that is for different purposes).

I'm sure there are a zillion options. For example, I could
create a "myType" field to define my own types, check
for specific fields like I'm doing, or come up with other
heuristics (for example, in my debugging, I uniquely identified
objects by setting universally unique values to "height").

However, I was curious if there was a "preferred/best-practice"
method, since I ASSUME this is a COMMON case:

?COMMON_CASE: Rich application-specific QML element
with (hard-coded) internal children, but which *also* manages
transient (child) elements that are periodically re-parented
among different QML elements.

For example, this is the common case for any QML element
that performs layout upon its children. Because QML is
OUTSTANDING for such dynamic re-parenting (through
its use of states and transitions), I'd assume many applications
want to iterate "transient" children separate from "hard-coded"
children.

SUMMARY QUESTION: What is best practice to separately iterate
"transient" children from "hard-coded" children?

Thanks!

--charley
Charley Bay
2010-10-30 17:21:23 UTC
Permalink
Ooops, typo (on parent name), that inserted code to inspect
each property within a child (more complete example with
pre-pended index) should be:

Item {
id: myParent

onChildrenChanged: {
for(var i = 0; i < myParent.children.length; ++i) {
console.log("--------------");
for (var myKey in myParent.children[i]) {
console.log("(" + i + ")child[" + myKey + "] == (" +
myParent.children[i][myKey] + ")");
}
}
}
}

Sorry about that. ;-))

--charley
m***@public.gmane.org
2010-11-01 03:32:27 UTC
Permalink
Post by Charley Bay
<snip, set custom property in "onChildrenChanged">
Yes, it should work <snip>
Yes, it works, it was my error. What I'm doing is a little
more complicated, and there was enough obfuscation
for me to confuse myself. ;-)
I now have something that "works", but I want to get
opinions on whether my solution is "stupid" or "best practice".
- I create a "myParent" Item{}
- I create "child" items that are not yet parented to my "myParent"
- I explicitly parent the child items with "{ parent: myParent }"
<snip>
?COMMON_CASE: Rich application-specific QML element
with (hard-coded) internal children, but which *also* manages
transient (child) elements that are periodically re-parented
among different QML elements.
For example, this is the common case for any QML element
that performs layout upon its children. Because QML is
OUTSTANDING for such dynamic re-parenting (through
its use of states and transitions), I'd assume many applications
want to iterate "transient" children separate from "hard-coded"
children.
SUMMARY QUESTION: What is best practice to separately iterate
"transient" children from "hard-coded" children?
One alternative might be to add a level of indirection. Rather than "parent: myParent":

* Have a function addChild() on myParent, that could take care of the parenting and also add the items to a separate, transient-child-only list
* Use property aliasing and an "inner" wrapper element to contain all the transient children (e.g. default property alias myChildren: innerElement.children). This is probably more applicable in the static case though (see e.g. Flickable, which has a default property of flickableChildren, which reparents its items to the contentItem).

Regards,
MIchael

Loading...