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

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).

Related

how to bind to `MouseArea`'s `onDragChanged`?

Qt Creator suggests that the onDragChanged slot exists in MouseArea.
MouseArea {
id: mouseArea
...
onDragChanged: console.log('Drag changed')
}
But at runtime it fails with:
Cannot assign to non-existent property "onDragChanged"
The proper way would be:
drag.onActiveChanged: console.log("Drag active:", drag.active)
This is because drag is a group of properties (under the hood it's a QObject or alike), so you need to reference that group first.
Your initial attempt doesn't work because drag is declared as CONSTANT Q_PROPERTY, which doesn't have a on...Changed signal
Silly workaround (but it works...)
readonly property bool _dragActive: drag.active
on_DragActiveChanged: {
... = drag.active
}

QML object and nest object onCompleted order

I have a QML file which contains an Item, the Item contains a object:
QtObject {
id: something
Component.onCompleted: {
console.log("A");
}
}
At the file Item I also have:
Component.onCompleted: {
console.log("B");
}
What I see in the Application Output is
B
A
What I want is for A to be processed first, is there anyway to do this or do I have to change use a function in the child object and call it from the onCompleted of the parent?
Well, AFAIK you can't change the "completed" order. But you could control when the QtObject loads. For example:
Item {
property QtObject something: null
property Component objectComponent: QtObject {
objectName: "something"
Component.onCompleted: console.log("A");
}
Component.onCompleted: {
something = objectComponent.createObject(this);
console.log("B", something);
}
}
qml: A
qml: B QObject(0x351d750, "something")
There's also the Loader type, which basically does the createObject() for you, though it's meant more for loading visual components on-demand.
Also the "something" QtObject could be in an external file, of course.
ADDED: Apparently this also works and loads in the desired order, though I'm not sure I'd prefer it myself (but it's more "declarative" I suppose :).
Item {
property QtObject something: objectComponent.createObject(this);
property Component objectComponent: QtObject {
objectName: "something"
Component.onCompleted: console.log("A");
}
Component.onCompleted: console.log("B", something);
}
As you can read from the official documentation (https://doc.qt.io/qt-5/qml-qtqml-component.html#completed-signal), "the order of running the onCompleted handlers is undefined". So you can not trust in your console output for know the object creation order.
If you want load components in a specific order, you can use the Loader item.

Repater: Using C++ property as model

I have a C++ property
Q_PROPERTY(QList<qreal> XTickPos MEMBER _xTickPos);
which I want to use in a Repeater. In the same QML file, the c++ class has been given the id
id: pw
The repeater looks like this
Item {
anchors.fill: parent
visible: true
Repeater {
model: pw.XTickPos.length
Rectangle{
height: 50
width: 2
x: pw.XTickPos[index]
y:10
visible: true
color: "black"
border.width: 2
}
}
}
However, nothing is drawn on the screen. If instead I make property in the QML file:
var xTickPos = []
and set it via a Q_Invokable function in c++
Q_INVOKABLE QList<qreal> getXTickPositions();
and in QML
root.xTickPos=pw.getXTickPositions();
and use the QML property xTickPos as model in the above repeater it is working. I checked that pw.XTickPos is correctly filled via a console.log
What am I missing here?
This one is kinda tricky.
The documentation states that you can use a JS array as a model, and it does state that QList<qreal> is automatically converted to a JS array when returned to QML.
But it seems that you can't use a QList<qreal> that is automatically converted to a JS array as a model. Go figure...
Naturally, it is preferable to have a proper model with its proper notifications for top efficiency. But in case you really want to go for the list property, it appears you will have to do the conversion manually in a getter:
QVariantList model() {
QVariantList vl;
for (auto const & v : yourList) vl.append(v);
return vl;
}
Amazingly, although Qt supposedly makes that conversion automatically, it doesn't seem to be able to make a QVariantList from a QList<qreal>.
That's Qt for you...

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 find QML item by its string id?

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...)
}

Resources