QML object and nest object onCompleted order - qt

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.

Related

How to update ListElement (QML) value field with dynamically updating property

In the below code, the property __operatingModus_season updating every 30 seconds. But in frontend, I dont see any value or changes. Please give some heads up if i am wrong.
ListModel{
id: tileListModelBetriebsmodus
Component.onCompleted: {
if(__is_automatik_mode_ON){
tileListModelBetriebsmodus.append({"tileListSource": "../../images/HausScreen/operatingModeAuto.png",
"tileListText": getFrontendText("AUTO"),
"tileListValue": __operatingModus_season
})
}else{
tileListModelBetriebsmodus.append({"tileListSource": "../../images/HausScreen/hamburgerfinger.png",
"tileListText": getFrontendText("MANUAL"),
"tileListValue": __operatingModus_season
})
}
}
}
This way of adding items to the ListModel doesn't make them dynamic, the values are stored on the spot.
You might be able to Create Property Bindings like this:
ListModel{
id: tileListModelBetriebsmodus
Component.onCompleted: {
if(__is_automatik_mode_ON){
tileListModelBetriebsmodus.append({"tileListSource": "../../images/HausScreen/operatingModeAuto.png",
"tileListText": getFrontendText("AUTO"),
"tileListValue": Qt.binding(function() { return __operatingModus_season })
})
}else{
tileListModelBetriebsmodus.append({"tileListSource": "../../images/HausScreen/hamburgerfinger.png",
"tileListText": getFrontendText("MANUAL"),
"tileListValue": Qt.binding(function() { return __operatingModus_season }
})
}
}
}
Note I did not test this, if you would add a minimal working example I could have done so.
Amfasis' answer suggesting the use of Qt.binding() is on the right track, except Qt won't allow assigning a binding object directly to a ListElement. However, it allows assigning plain JS function objects. So you have to do it in two steps instead:
Step 1: Define your dynamically evaluated ListElement role as a JS function (tip: you can also use the arrow syntax to make this more readable), for example:
ListElement {
dynamicLabel: () => config.useLabelA ? labelA : labelB
}
Step 2: Set up a binding inside your delegate between the target item and the model role:
ListView {
delegate: Text {
Component.onCompleted: {
text: Qt.binding(model.dynamicLabel)
}
}
}
Note the need to go through Component.onCompleted as you can't do text: Qt.binding(model.dynamicLabel) directly (QML spits out an error "Invalid use of Qt.binding() in a binding declaration.").
You can't just do text: model.dynamicLabel either as that would only evaluate the JS function once at initial assignment, but not update if e.g. config.useLabelA changed. That's what you need the Qt.binding() wrapper for.
Building on Romain Pokrzywka's answer:
text: valuedUpdated() ? model.dynamicLabel : "" will be evaluated whenever value is updated. valuedUpdated() is a Q_PROPERTY, which returns true. Notify signal can be emitted whenever value is updated.

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.

Dynamically retranslate Qt Quick UI

I want to dynamically retranslate Qt Quick GUI strings.
There is intrusive trick to retranslate affected string properties, whose notifications about changes cannot be centralized.
Is it possible to make qsTr (and others) to return string-like objects, which behaves exactly like string, but also behaves like global properties connected to common "valueChanged" signal (which I want to emit, when QEvent::LanguageChange in QCoreApplication occured).
I think I can use twitching of Loader's active property, which contains entire top level GUI element to make all the user-visible strings retranslated, but this approach results in lost of the state of all the items and components, connected to the Loader and not differs from complete application restart for me.
Is it possble to create such myQsTr function?
From Qt 5.10 onwards, you can call QQmlEngine::retranslate() after you have installed a new translator with QCoreApplication::installTranslator(), to ensure that your user-interface shows up-to-date translations.
You could opt to use your own, 100% QML solution like that:
// Tr.qml
// also put `singleton Tr Tr.qml` in the qmldir file
pragma Singleton
import QtQuick 2.7
QtObject {
function t(s) {
if (lang === eng) return s
var ts = lang[s]
return ts ? ts : s
}
property var lang: eng
readonly property var eng : {
"hello" : "hello",
"goodbye" : "goodbye"
}
readonly property var ger : {
"hello" : "hallo",
"goodbye" : "auf wiedersehen"
}
readonly property var esp : {
"hello" : "hola"
}
}
// test it out
import QtQuick 2.7
import QtQuick.Controls 2.1
import "." // same old singleton bug
ApplicationWindow {
id: main
visible: true
width: 640
height: 480
color: "darkgray"
Column {
Text { text: Tr.t("hello") }
Text { text: Tr.t("goodbye") }
Button { text: "Eng"; onClicked: Tr.lang = Tr.eng }
Button { text: "Ger"; onClicked: Tr.lang = Tr.ger }
Button { text: "Esp"; onClicked: Tr.lang = Tr.esp }
}
}
The different language objects act like a map<string, string> and every time you change lang this will cause the binding expressions to reevaluate and refresh the value form the current language dictionary.
This solution will also fallback to the default language string if a translation is not found. You can easily customize the behavior and you don't rely on any external tooling. Clean, simple, self-contained and totally under your control.

If Loader's sourceComponent Item references its parent, and I set "loader.active = false", I get an error

My code:
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
Item {
id: loaderParent
Loader {
id: loader
active: true
sourceComponent: Item {
parent: loaderParent
x: parent.x
}
}
}
Item {
focus: true
Keys.onPressed: {
loader.active = false;
}
}
}
When I press any key, I get this error:
qrc:/main.qml:16: TypeError: Cannot read property of null
Thought I suspect this error is harmless, I'd like an explanation or any idea for a fix/workaround?
Reported here.
I found a workaround: instead of fetching parent.x, fetch loaderParent.x. Still want to know why the problem happens.
The Loader appears to set the item parent to null on destruction. QML objects are not deleted immediately, instead they use deleteLater() which leaves the object alive for another event loop cycle.
This leads to a reevaluation of the binding expression, which is no longer possible since the parent is now null. I've had a more severe encounter with this behavior described here.
A simple way to avoid it would be to not use the parent property which you already found, or to use a more complex binding expression such as x: loader.active ? parent.x : someFailsafeValue.
By using onParentChanged: console.log(parent) you can verify that the parent indeed changes to null when the loader is deactivated.

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

Resources