How to find QML item by its string id? - qt

I have a string id of an object I need to find in QML tree.
For example:
var idToFind = "myBtnId"
Can I do something like the following?
var objectThatINeed = myMainWindow.findObjectById(idToFind)
As far as I understand I can use objectName for this purpose (at least from C++). Can I still reuse existing ids somehow without introducing the names?
I want to use this object as a parent for some other dynamically created controls.

No, you have to use objectName or some other property.
The id Attribute:
Once an object instance is created, the value of its id attribute cannot be changed. While it may look like an ordinary property, the id attribute is not an ordinary property attribute, and special semantics apply to it; for example, it is not possible to access myTextInput.id in the above example.

If you know the IDs of all items you want beforehand, you can create an object map for them and use that to look them up. For example:
Item {
property var idMap: ({ foo:foo, bar:bar })
Rectangle { id:foo }
Item { id:bar }
function findItemById(id) {
return idMap[id];
}
Component.onCompleted: console.log(findItemById('foo'), findItemById('bar'))
// qml: QQuickRectangle(0x1479e40) QQuickItem(0x148fbf0)
}

QJSValue MyEngine::getQmlObjectById(const QString& id) {
QV4::ExecutionEngine *v4 = QV8Engine::getV4(m_JSEngine);
QV4::Scope scope(const_cast<QV4::ExecutionEngine *>(v4));
QV4::ScopedString name(scope, v4->newString(id));
return QJSValue(v4, v4->currentContext->getProperty(name));
}

Based on the answer from Phrogz, if you don't want an explicit map, you can assign the components to properties and then reference these with the [] operator:
Item {
id: page
property Component foo: Rectangle { color: "yellow" }
property Component bar: Item { }
Component.onCompleted: console.log(page["foo"], page["bar"])
//qml: QQuickRectangle(0x...) QQuickItem(0x...)
}

Related

QML Question about Calling Nested Variables

So I want to call a variable from an object within an object several levels down. The only way I've figured out how to do it successfully is as follows:
//object doing the calling
Text {
text: lv1.lv1Out
}
//object containing the variable I want
Rectangle {
id: lv1
property var lv1Out: lv2Out
Rectangle {
id: lv2
property var lv2Out: variableIWant
Rectangle {
id: lv3
property var variableIWant: 1
}
}
}
Basically I have to define variables at every level, and define the variable I want all the way out of the containing object tree. Is there a more elegant way of doing this? Calling the following doesn't work for me:
Text {
text: lv1.lv2.lv3.variableIWant;
}
Calling a.b.c for nested definitions will not work because children object are contained in parent object but not as variable/property. For this type of inspection you need to use attribute children[i] - in your case children of children and loop over children.
But in your case it is is unnecessary. You have children object named by id so can can do:
Text {
text: lv3.variableIWant;
}

Is there a way to distinguish internally defined children from externally defined ones in QML?

I have defined a MyElement element (in the MyElement.qml file) as the following:
Rectangle {
Item {
}
Component.onCompleted: {
console.warn(children.length)
}
}
Let's call the Item element defined within MyElement the internal child. Now, I'm using the MyElement element in the following way:
MyElement {
Item {
}
}
Here another one Item element is used as a child of MyElement. Let's call this Item element the external child. To understand my question below one should clearly understand the difference between internal and external children.
The output for the presented code will be 2, i.e. both Item elements are calculated as children.
In the future I want to iterate in the block Component.onCompleted only over external children, not over internal (external children go after internal). But for this I have to know a children index from which I have to start (in the given example this index is 1). Is there a way to get this index or, in other words, the number of internal children? Thanks.
There is no internal mechanism in Qt to distinguish internal children from those which are defined outside of the own QML definition.
My workaround is as follow
//MyElement.qml
Rectangle {
id: root
readonly property Item last_item: lastone
Item {
id: someitem
}
Item {
id: lastone
}
Component.onCompleted: {
var external_started = false;
for(var i = 0 ; i < root.children.length ; ++i)
{
if(external_started)
console.log(root.children[i].toString(), 'is external');
else if(root.children[i]===last_item)
external_started = true;
}
}
}
and
MyElement {
Item {
objectName: 'I am external'
}
}
It's a dirty hack but it works.
I'm saving a reference to the last item in a readonly property called last_item and that will distinguish my last item in qml definition.
Other items which are added outside of the qml file, will be placed after this item in the children list.

pass data from one window to another (inside same QML file)

i got two Windows inside the same .qml file.
Window1 has a textinput1 and a button, and when I press the button, i'd like to send the string value from that textinput to the Window2 textInput2.
I'm new to Qt and QML, been reading a lot on signals, Loaders, properties and can't materialize this kind of transfer. Can anyone do a simple 10-line example of such transfer please?
Window {
id:window
TextInput {
id:text1
text: "This value is needed in the second Window!"
}
Button {
onClicked: window2.open()
}
Window {
id.window2
function open(){
visible = true
}
Text {
text: text1.text
}
}
}
If I do this it gives me ReferenceError: text1 is not defined, how can I reference the text1 from the first Window?
I would prefer to use signals in such case:
Window {
id: window1
title: "window 1"
visible: true
width: 600
height: 600
signal someSignal()
Button {
anchors.centerIn: parent
text: "Open window"
onClicked: window1.someSignal()
}
Window {
id: window2
title: "window 2"
width: 400
height: 400
// you can use this instead of Connections
//Component.onCompleted: {
// window1.someSignal.connect(show);
//}
}
Connections {
target: window1
onSomeSignal: {
window2.show();
}
}
}
I think this is more ... how do you say? ... more imperative -)
i got two Windows inside the same .qml file.
If you did then your code will work. Since it doesn't work, I will assume each window is defined in its own qml file, and you only instantiate the two windows in the same qml file.
If I do this it gives me ReferenceError: text1 is not defined, how can
I reference the text1 from the first Window?
You will have to be able to reference the window first, and it should provide an interface to reference the text.
Keep in mind that ideally ids should only be used to reference stuff in the same source. On rare occasions you could go further, and reference ids down the object tree, but only parents, and none of their out-of-line children, it will however work for in-line children that are given ids in that same source. Meaning that if window2 is created inside window then you will be able to reference window from inside window2. But if both windows are siblings in another object, the id won't resolve.
Obj1
Obj2
Obj4
Obj3
In this example object tree, Obj1 will resolve from any of the objects. However, Obj3 will not be able to resolve Obj2 if the id is given inside Obj2, but will resolve if the id for Obj2 is given inside Obj1. But there is no way to resolve Obj4 from Obj3. because the id doesn't act like a property, you can't do someId.someOtherId, that's only possible for properties. You cannot do somePropertyObject.someId neither. You can only begin with either an id or a property, and continue only with sub-properties.
When the id is not applicable, can expose objects or properties either as properties or property aliases. The first is useful when you want to expose a whole object, the second when you want to expose a particular property of an object:
Item {
property Item innerItem: inner // gives you access to the "entire" inner object
property alias innerWidth: inner.width // gives you access to a property of inner
Item {
id: inner
}
}
You can also have aliases to aliases.

Qml - passing property value between two components

This is a simplification of the situation I am dealing with in main.qml file:
Component {
id: component1
property string stringIneedToPass: "Hello"
Text { text: stringIneedToPass }
}
Component {
id: component2
Rectangle {
id: myRectangle
property string stringIneedToReceive = component1.stringIneedToPass; //this doesn't work
}
}
Obviously my situation is more complicated. But in the end I just need to understand how this kind of transfer should be done!
Thank you all!
First of all, a Component element cannot have properties. Components are either loaded from files, or defined declaratively, in the latter case they can contain only one single root element and an id.
Second - you cannot do assignment in the body of an element, only bindings.
Third - you cannot reference properties defined inside an element inside a component from the outside, as that object doesn't exist until the component is instantiated. Such objects can only be referenced from inside.
Other than that, it will work as expected, if you can reference it, you can bind or assign it to a property, depending on what you want.
So you can simply have the string property external:
property string stringIneedToPass: "Hello"
Component {
id: component1
Text {
text: stringIneedToPass
}
}
Component {
id: component2
Rectangle {
id: myRectangle
property string stringIneedToReceive: stringIneedToPass
}
}

How to access the public properties of a qml page loaded through a Loader?

AA.qml
Item
{
id: drawLinesOnC
property string lineColour
property int lineDrawingSourceType
property variant startEndPointArray
}
main.qml
Loader
{
id: drawLineLoaderA
source: "AA.qml"
}
-
How to access the public properties of AA.qml page loaded through Loader drawLineLoaderA?
Solution is as follows:
drawLineLoaderA.source = "DrawLineLoader.qml"
if (drawLineLoaderA.status == Loader.Ready)
{
if (drawLineLoaderA.item && drawLineLoaderA.item.lineColour)
{
drawLineLoaderA.item.lineColour = "black"
drawLineLoaderA.item.lineDrawingSourceType = 2
}
}
In addition to what #TheIndependentAquarius said, you can declare property of the corresponding type in your loader:
Loader {
id: drawLineLoaderA
readonly property AA aa: item
source: "AA.qml"
}
And then use it like this:
if (drawLineLoaderA.aa) {
drawLineLoaderA.aa.color = "black"
}
Now you clearly stated that you deal with item of type AA and no other, and you'll get autocompletion on loaded item's properties as a bonus.
Note 1: Configuration of loaded item's properties should be done either in AA.qml itself (default values) or in Loader's onLoaded handler, as #troyane suggested.
Note 2: In your AA.qml you declared property string lineColour. You might be interested in color QML type. If you declare property color lineColour, QML will check that you assign valid values to this property. Moreover, color value is automatically converted to QColor when passed to C++ (and from QColor when passed from C++, of course).

Resources