How to modify QML dynamic objects from JavaScript - qt

I have QML file which describes button(moduleButton.qml):
import QtQuick 2.0
Rectangle {
id: button;
width: 100; height: 20
Text {
id: buttonText;
text: "Hello World";
}
}
From other QML form I load this button via Qt.createComponent method:
var moduleButton = Qt.createComponent("moduleButton.qml");
moduleButton.createObject(mainRect);
I tried to set/get width of moduleButton:
moduleButton.width = 30;
But received following error: Cannot assign to non-existent property "width"
How to access dynamic object attributes and child elements?
P.S. Qt.createQmlObject method perfectly works, but I need to load QML from file, not from string.

createObject() returns the new object. Your code should look like this:
var moduleButton = Qt.createComponent("moduleButton.qml");
var myButton = moduleButton.createObject(mainRect);
myButton.width = 40
The moduleButton is a component (a factory), used to instantiate the item.
Documentation:
http://qt-project.org/doc/qt-5/qtqml-javascript-dynamicobjectcreation.html

Related

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.

How to inject data into QT item shown with loader

In my application I have a global system that handles navigation between "screens". In QML I can simply call something like:
appNavigation.show("MyScreen.qml", NavigationType.FADE)
this calls a C++ part of the code which handles the current stack of screens and uses signals to report back to QML to do the actual animation. At the end in QML some Loader will load the input qml ("MyScreen.qml" in this case) and show it as defined.
My issue here is how to inject data into newly loaded screen. Essentially I would like to do something like the following:
function showMyScreen() {
MyScreen screen = appNavigation.show("MyScreen.qml", NavigationType.FADE)
screen.someData = "some data here"
}
but is this possible? Could I somehow return the screen that is loaded by the loader?
I am guessing not so I would satisfy with sending the data with the navigation itself like:
function showMyScreen() {
MyScreen screen = appNavigation.show("MyScreen.qml", NavigationType.FADE, "some data here")
}
I could forward the data to the point where I set source to the loader but still what then? How or where would that specific screen that is going to be loaded get the data. To reduce is this is what I get:
function setNewItemWithData(newItem, data) {
loader.source = newItem
loader.concreteScreen.data = data // Not really doable
}
again I assume this is not doable and I need to forward the data down to loader and use onLoaded event. So what I would do is something like:
onLoaded: {
myLoadedScreen.data = data
}
I assume something like this is possible but how? What am I missing here, how do I get myLoadedScreen and how to access its properties?
What I am currently doing now is dumping the data in C++ part and then collecting it in the loaded QML. So like the following:
appNavigation.injectedData = "some data here"
and then in the newly loaded item:
property data = appNavigation.injectedData
It works but this seems like extremely poor coding. Any of the alternatives would be helpful.
Thank you for your patience.
Since the request for MCVE was made:
This is a general problem and I expect it to have multiple solutions. I would be looking forward to any of them. However the minimal example to produce this is creating a new project and adding a loader with another qml to which some property should be changed:
main:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Loader {
anchors.fill: parent; anchors.margins: 20
source: "MyScreen.qml"
// TODO: make screen green (loadedScreen.color = "green")
}
}
MyScreen:
import QtQuick 2.0
Rectangle {
color: "red"
}
Current result is seeing a red rectangle and desired result is to see a green one. The point being that the main screen needs to tell what color the loaded screen needs to use.
You have to use the item property of the Loader to get the object loaded:
Loader {
id: loader
anchors.fill: parent; anchors.margins: 20
source: "MyScreen.qml"
onLoaded: loader.item.color = "green"
}
To do that, you might as well use Component (If you use it when reacting to an event)
Component {
id: myScreenComponent
MyScreen {
anchors.fill: parent
}
}
function showMyScreen() {
myScreenComponent.createObject(this, {"color: "green"});
}
Alternatively, given your first code, I would recommend you to use StackView.
The push method seems to be similar to your appNavigation.show one.
You can give it an url, some properties, and a transition type (that you can customize).

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.

Does qml supports object oriented programming

I'm using QML for my project, I want to know if am instantiating a file in another file, is it like instantiating object for a c++ class?
File.qml
Rectangle {
id: idRect1
.
.
}
File2.qml
Rectangle {
id: idRect2
File1 {
id:idFile1
.
.
}
}
In File2.qml i have initialized File1, does it means i have created an object of type File1? Please share some knowledge(links) on how all this mechanism works. Thanks in Advance
In QML when creating a file with first letter uppercase, you're creating a component. Components are implemented using OOP aggregation (not subclassing). That mean if I write
// MyButton.qml
import QtQuick 2.0;
Rectangle {
id: base;
width: 120;
height: 40;
color: "lightgray";
Text {
text: "foobar";
anchors.centerIn: parent;
}
}
... I haven't subclassed Rectangle, I just created a component that contains a Rectangle as root object, and configurates it in a certain way, and adds a Text object in it.
As soon as a component is created, it can be instanciated by simply writing :
MyComponent { id: myNewInstance; }
Because that's a way it works in QML.
The component name is a kind of class (but not in the C++ or JS way to define it) and it can also be used as a type for a property :
property MyComponent theComponent : myNewInstance;
Then it can hold the ID of an object created with the given component, acting somewhat like a C/C++ pointer : the property holds a link to the actual object.
But because of the way QML was designed, even if it's more aggregating than subclassing, a property of the type of the root object of a custom component can also hold ID of a derived component, in my case :
property Rectangle theComponent : myNewInstance;
Will work, but if I try to put an ID of an Image or Text or something else, QML engine will throw incompatible types error.
I hope it helps you.

Resources