How to pass “this” from a QML element to a JS Function - qt

Similar to the this keyword in C++, I’d like to either have a QML element to pass itself into a JS function, or have it set a property on another element to itself. Is this possible?
For example:
Rectangle{
id:theParent
property var theElement
SomeElement{
id:theChild
MouseArea {
anchors.fill:parent
onClicked: {
someJsFunction(*whatGoesHere*)
parent.theElement=*whatGoesHere*
}
}
Or, Consider this:
Rectangle{
id:theParent
property var theElement
SomeElement{
id:theChild
}
Then, in SomeElement.qml:
Rectangle{
MouseArea {
anchors.fill:parent
onClicked: {
someJsFunction(*whatGoesHere*)
parent.theElement=*whatGoesHere*
}
}
}
In this case, the *whatGoesHere* would be the instance of SomeElement where these are being called from.
Is this possible in QML? I would think the id property would make sense, but according to the docs, you cannot query the value of the id field, and anyway the id wouldn't be available if my SomeElement was described in a separate file, and the whatGoesHere handling above appeared in that separate file rather than in a particular instance.

I have two complementary proposals :
First, for a single usage, pass the ID as it's basically a pointer to the item :
MouseArea {
id: myClicker;
onClicked: { callFunc (myClicker); }
}
Then if you need multiple items to share this behavior, that means you're using MVC so the ID will work exactly the same :
Repeater {
model: 100;
delegate: MouseArea {
id: myClicker;
onClicked: { callFunc (myClicker); }
}
}
That is the classical part.
But to todo even better if you create your own components, keep in mind to create a 'self' helper property that does the 'this' job properly :
MouseArea { // component root definition
id: base;
property var self : base; // bind self on the I
}
Then use it like this :
Repeater {
model: 100;
delegate: MyComponent {
onClicked: { callFunc (self); }
}
}
Use this as often as you want !

Instance of your SomeElement is its id property value i.e. theChild. You can use it as this. No other built-in way exists as far as I can tell. But you can try to add your own QML item hierarchy with property which will return this.
Another way is to get children of some parent and use them. So you get children, locate the child you need and use that particular instance

If you define your element in a separate file, then you can simply assign an id and use it. It will be valid just within the context of that instance:
SomeElement.qml
Rectangle{
id: thisElement
MouseArea {
anchors.fill: parent
onClicked: {
someJsFunction(thisElement);
parent.theElement = thisElement;
}
}
}

Related

Is it possible to bind QML ListModel to PathPolyLine .path property?

In my previous question I figured out that QML PathPolyLine.path property can be filled with JS array of objects. But changing that array doesn't lead to PathPolyLine.path property change, thus I have to reassign it every time.
So can this property be bind to QML ListModel? Like QML Repeater.model property. And how to set it properly?
Something like that:
ListModel { id: modelID }
PathPolyLine
{
id: pplID
path: modelID
}
Button
{
property int i: 0
onCicked(): { modelID.append(Qt.point(i, i+10)); i++; }
}
That way button clicks only fill ListModel and doesn't affect PathPolyLine.path property.

Re-using Component of TextField responding to wheel events, with signal handling

I enriched a TextField (displaying a float) so that it can be changed by mouse wheel, all while being still editable by hand.
I found the quirk with forceActiveFocus and onClicked here (I wish I could just let all events pass down the widget stack like in Qt) and use onWheel to change the value (please let me know if this is not the best way to do this):
TextField{
text: cxxObject.floatAttribute.toString()
onEditingFinished: { cxxObject.floatAttribute=parseFloat(text); }
MouseArea{
anchors.fill: parent
propagateComposedEvent: true
onClicked: { parent.forceActiveFocus(); }
onWheel: { parent.text=parseFloat(parent.text)-.5*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
I would like to re-use this component instead of TextField in several places (without copy&paste), so I tried to declare the component like this:
Component{
id: wheeledFloatTextField
property real initValue: 0.
property real dWheel: 0.5
signal editingFinished(real value);
TextField{
text: parent.initValue.toString();
// re-emit signal to the component
// so that user-defined slot can be defined when re-used
onEditingFinished: parent.editingFinished(parseFloat(text));
// validator: ...
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onClicked: { parent.forceActiveFocus(); }
onWheel: {
parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
}
}
and re-use:
Loader{
sourceComponent: wheeledFloatTextField
initValue: cxxObject.floatAttribute;
onEditingFinished: { cxxObject.floatAttribute=value; }
}
I am however getting (at the line where Component is used):
Component objects cannot declare new properties.
What is wrong? I was some posts (like How do you assign a QML Item to a component property in QML and then use that object inside the component? and https://developer.blackberry.com/native/documentation/dev/custom_components/index.html) from which I am gathering I might need to wrap the inside of Component (which is as-if it were a separate .qml file and does not define a scope) in something like Item or Container but I am not sure what to do. Any hint?
I would like to keep the definition inline first, later move to a separate file.
If you have the component declared in a separate file, you can (should) omit the top-level Component. For maximum reusability of components, it is reccomended to declare them in a separate file.
A Component can not have any properties declared. It is basically stopping the object creation in a prototypical state. That is useful if you want to configure the object, for later creation, for example lazy initialization (delegates).
If you have a property of type Component and you use the myProp: SomeType {...} syntax, it will automatically just create a component from that.
I think the best solution is to put your TextField in a seperate file, and add the properties to the root-node so it is customizable.
File1 (e.g. "CustomTextField.qml")
TextField{
property real initValue: 0.
property real dWheel: 0.5
signal editingFinished(real value);
text: initValue.toString();
// re-emit signal to the component
// so that user-defined slot can be defined when re-used
onEditingFinished: editingFinished(parseFloat(text));
// validator: ...
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onClicked: { parent.forceActiveFocus(); }
onWheel: {
parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
}
You can then reuse the Component in all known ways like in a Loader:
Loader {
sourceComponent: CustomTextField { // Property type is component, so it automatically creates a Component instead of the full-blown object, until it is loaded.
initValue: 12
dWheel: 42
}
...
}
or without Loader
CustomTextField {
...
}
Of course you can keep it inline, but even then, you have to add the properties to the root-element inside the Component.
Component {
id: componentId // only thing you can set besides one Object in a Component
TextField{
id: componentRoot // You can't reference this id from outside the Component!!!
property real initValue: 0.
property real dWheel: 0.5
signal editingFinished(real value);
text: initValue.toString();
// re-emit signal to the component
// so that user-defined slot can be defined when re-used
onEditingFinished: editingFinished(parseFloat(text));
// validator: ...
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onClicked: { parent.forceActiveFocus(); }
onWheel: {
parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
}
}
This has the down-side that you will always need a separate object to instantiate the Component, like a Loader which adds overhead and complicates communication in the file between the objects, since to address it, you will need to use: loaderId.item.property which might be expensive in lookup, you need to ensure that item is defined e.t.c.

Set the parent context separately from the parent in QML components

When I define a Component in QML I can define a alias property to expose properties of QtObjects that are otherwise hidden by the Components root.
Especially interesting is the default property alias to build powerful reusable and adaptable Components. But there is no generic way to have alter the nameresolution, which is bad for properties and signals.
Example: MyComp1.qml
Item {
id: rootItem
default property alias content: childItem.data
signal sig()
Item {
id: childItem
}
}
main.qml
MyComp {
id: myObj
signal sig1()
onSig1: console.log('here')
Button {
id: button
onClicked: {
parent.sig()
parent.sig1()
}
}
}
Here button is now a child of childItem, but both signals: sig() and sig1() are defined in rootItem, which leads to an error. To the user of of my nicely designed Component it is not transparent. He would need to look in the code of the Component to realize, why the buttons parent is not myObj/rootItem but in fact of childItem. This might lead to bugs.
Therefore I am looking for a way to prevent it.
For signal sig() this would be possible by the modifying the MyComp and manually forward the signal from childItem to rootItem:
Item {
id: rootItem
default property alias content: childItem.data
signal sig()
Item {
id: childItem
signal sig()
onSig: rootItem.sig()
}
}
But is there a way to shortcut this, so that the user does not need to us myObj.sig1() but might use parent.sig1().
Same goes for properties, that are added uppon instantiation:
MyComp {
signal sig1()
property int num: 5
Button {
text: parent.num // <--- Error
}
}
It would be nice, if the properties and signals defined uppon instantiation would be available as parent.propertyName to the Objects added by a default-property aswell, I think.
Is there a way (to maybe hack this)?
There is no way to do this.
What you can do is stop assuming the outer scope in QML code is always the parent.
You could either access it directly by id:
MyComp {
id: myComp
signal sig1()
property int num: 5
Button {
text: myComp.num
onClicked: myComp.sig1()
}
}
Or if the object you want to access is the root item, you can access its properties implicitely:
MyComp {
signal sig1()
property int num: 5
Button {
text: num
onClicked: sig1()
}
}
One good practice (albeit maybe too extreme for most case) that can be deduced from this situation, is to only use parent when you actually want to access the parent Item in the visual hierarchy, for things like anchors or width and height.

QML: public variable

I have two QML files.
In First.qml I can make visible Second.qml. In Second.qml I have selectedParts variable.
I want to set selectedParts to value 1 always, when I make Second.qml visible. That works only when I load
Second.qml for first time. If I make Second.qml invisible and then visible, selectedParts value is 2. Is there anyway
to make selectedParts variable public and set its value always when I click on myImage?
First.qml
Item {
Image {
id: myImage
MouseArea{
anchors.fill: parent
onClicked: {
second.visible = true
...
}
}
}
}
Second.qml
Item {
property int selectedParts: 1
Image {
id: myImage2
MouseArea{
anchors.fill: parent
onClicked: {
selectedParts = 2
...
}
}
}
}
QML public variable? Look up for MessageBoard in Defining QML types from C++. We are using that approach. All you need is to create C++ MessageBoard object, put some data in there and reference it via the QML context given to every QML root object:
m_quickView.engine()->rootContext()->setContextProperty("myMsgBoard", MyQmlMsgBoard::instance());
And in QML:
Rectangle {
id: topRect
scale: myMsgBoard.scale // or anywhere in QML
// ....
}
Of course that "message board" C++ object exposes to QML something like:
Q_PROPERTY(qreal scale READ scale CONSTANT);
I solved my problem by adding back button into the Second.qml file. And in this button I put statement selectedParts = 1.

QML iterating over custom elements and calling function

I have a custom element called MenuButton:
import QtQuick 1.1
import VPlay 1.0
Image {
property alias text: buttontext.text
property alias mouseArea: area
property alias fontBold: buttontext.font.bold
property alias textSize: buttontext.font.pixelSize
id: button
source: "img/cloudButton.png"
opacity: 1
Text {
id: buttontext
color: "black"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 50
font.bold: true
}
MouseArea {
id: area
anchors.fill: parent
onPressed: button.opacity = 0.7
onReleased: button.opacity = 1.0
onCanceled: button.opacity = 1.0
}
function doStuff {
// do something here
}
width: 200
height: 60
}
Now, in my main View, I have a Column with 5 of those MenuButtons. And I want to iterate over them and call the function doStuff(). How do I do that?
I tried with column.childAt(i) and stuff like that, nothing worked.
MainView.qml
Rectangle {
width: 480; height: 320
// HERE IS MY PROBLEM, how do I iterate over all my elements in the column?
function update() {
for(var i = 0; i < 5; i++) {
column.childAt(i).doStuff(); // THIS IS WHAT I WANT TO DO
}
}
Column {
id: column
spacing: 5
anchors.centerIn: parent
Repeater {
id: repeater
model: 5
MenuButton {
id: levelbutton
text: "Level " + (modelData+1);
source: "img/cloud4.png"
}
}
}
}
The problem is in my update function in the MainView.qml
I dont know how to iterate over the elements and call the doStuff() function.
You may use Component.onCompleted attached signal, like this:
import QtQuick 1.0
Rectangle {
height: 600
width: 600
Repeater {
model: 5
Item {
Component.onCompleted: console.log('Component ' + index + ' completed!')
}
}
}
However, please consider, that this imperative operation is not good, because it will be called all time after model update. Probably you have problem X and asking how to get Y, that (you think) will solve your X?
From what I've seen in the source code in the QDeclarativePositioners class you cannot access the children elements!
But you could turn the way you call your doStuff() method: when do you want it to be called? After some time has passed (then add a Timer element to your MenuButton), or when a signal occurs? In the latter case, you could use the Connections element and listen to the signal beign emitted in your calling qml file where you use the Column and Repeater.
Cheers, Chris
You can access the children of an element via the children property that exists on all QtObjects. It contains an array of child elements and can be freely acccessed in javascript.
eg. element.children[0].doStuff()
Generally speaking, you should avoid actions that require you to manually iterate over the children. However, if you are attempting to write code that generically calls something on each of the children you do not always have a choice.

Resources