Unable to set the parent of a dynamically created QML component - qt

I am creating dynamic component in QML as follows:
var component = Qt.createComponent("PlayerWindow.qml")
if (component.status != component.errorString())
console.log(component.errorString())
var playerWin = component.createObject(rootWindow);
Here rootWindow is my main application window. Now, the PlayerWindow is quite simple as:
Window {
id: playerWindow
width: parent.width
height: parent.height
Component.onCompleted: {
console.log(parent.width)
console.log(rootWindow.height)
}
}
The thing is that the values for parent.width and rootWindow.width are really different and this is also evident when the window is displayed. However, rootWindow is set as the parent in the createObject call. So, I am not sure what is happening there but I wanted to know if this is the correct way to set the component parent when they are being dynamically created.

Try to add console.log(parent) in the code. You will see something like qml: QQuickRootItem(0x1e3e4e0). If you check the Qt doc you will find that Item.parent() returns Item but Windows is not Itemdescendant but QQuickWindow. Also from documentation:
A QQuickWindow always has a single invisible root item ...
So, in your case parent and rootWindow are different objects.
P.S. The dynamic object creation in your code can produce an error since component.createObject will be executed although Qt.createComponent returns error. Just copy the code from Qt documentation.

Related

Sharing data across QML files (properties)

I am writing an App and I already managed to resolve some issues, with the latest one being the Screen.Width/Height for adjusting the window size dynamically on different monitors (I use laptop, phone, PC, it's simply convenient).
To write the code efficiently and nicely, I want to obtain that specific information and put it into a single set of 2 "variables", that would then hold this information.
I tried assigning the ApplicationWindow object an id: mainWindow, in order to call upon it from a different QML file, to obtain the property value as:
mainWindow.height, mainWindow.width
I then was told to use another approach, custom QML properties, that are declared like:
property (type) (name): (value)
I then followed the advice and declared those properties in Main.qml (with AppWindow) and it does work. The properties of AppWindow (ApplicationWindow) contain the width and height of the Screen multiplied by a specific coefficient.
Then those variables are accessed by the object itself, drawing the App Window as I want it to be.
The problem is that this approach was meant to solve the issue of sharing code across .QML files, and it doesn't
[go down]
ApplicationWindow {
id: mainWindow
//Wide screen support
//Screen.desktopAvailableWidth / 4
//Screen.desktopAvailableHeight / 6
//The below is monitor cross-compatible (phone, PC, laptop)
property int globalWidth: Screen.width / 2
property int globalHeight: Screen.height / 3
width: globalWidth
height: globalHeight
visible: true
title: qsTr("Redacted")
//setWindowIcon(QIcon(":/path/to/icon.png"));
The piece of code below resides in main.qml. It is called upon from that file. Just ignoring the IDs (IDs supposedly [as stated by users/docs] can't be accessed outside of local scope...), the properties (especially custom properties) should be accessible inside Page1, Page2, Page3 etc.
SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: tabBar.currentIndex
Page1Form {
}
Page2Form {
}
Page3Form {
}
}
The below is the Page2Form.qml file that is called upon (as class definition) in main.qml.
The properties declared in parent objects in main.qml (imo) should be inherited by child objects (imo).
Page {
id: localPage2
width: globalWidth
height: globalHeight
Rectangle {
id: rectangle
x: (localPage2.width / 2) - (width / 2)
y: (localPage2.height / 4) - (height / 2)
width: localPage2.width / 3
height: localPage2.height / 6
color: "#ffffff"
border.color: "#a45c5c"
border.width: 2
TextInput {
id: textInput
x: 0
y: 0
width: localPage2.width / 3
height: localPage2.height / 6
text: qsTr("Redacted")
font.pixelSize: 12
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.weight: Font.Normal
focus: true
}
}
Ok, so what is the problem?
I can try calling those properties as:
mainWindow.globalHeight
globalHeight
mainWindow.height
etc.
They won't be accessed. The form editor will provide me preview of Page object that has 0 size.
The page does render eventually (when compiled and ran), but there is an issue in passing (accessing) the value of that property.
As you can notice, both IDs and custom properties seem to work just fine locally.
Update:
I haven't fixed that issue, I also tried using aliases (references) and the "foreign" QML file will still fail to be assigned proper size (Page width and height).
I then put another custom property of string type with some text in it, I then managed to access that property in Page2.qml and the property is originally in main.qml.
It's bugged, or I have no idea what it is.
I tried 3 approaches:
ID (it's not global as it turns out)
Custom property (kind of works, just not with Screen size...)
Aliases on object's default properties
(property alias globalWidth: mainWindow.width
property alias globalHeight: mainWindow.height)

Custom properties don't exist at compile time? "Cannot assign to non-existent property"

In referencing an item's properties from outside its parent component, I can get a "Cannot assign to non-existent property" error that seems to depend on some compile time order-of-operations.
I have created a small example app that shows some various ways to assign a color to this property, where direct assignment fails, but similar assignment to default properties works, or even later assignment works.
Here is my main.qml:
import QtQuick 2.7
import QtQuick.Window 2.12
Window {
id: application_window
visible: true
width: 640
height: 480
Thing {
// colors.backgroundColor: "red" // Direct assignment of custom property: Fails
// thing.color: "red" // Direct assignment of default property: Works
// Component.onCompleted: colors.backgroundColor = "red" // Run time assignment of custom property: Works
}
}
and a file called Thing.qml in the same dir:
Item {
id: root
height: 50
width: 50
property alias colors: colors
property alias thing: thing
Rectangle {
id: thing
anchors.fill: root
color: colors.backgroundColor
}
Item {
id: colors
property color backgroundColor: "blue"
}
}
By individually uncommenting the lines in main.qml, you can see that directly assigning colors.backgroundColor does not work, but the other ways of changing the color do work, even assigning colors.backgroundColor at runtime. I have also moved the 'colors' Item to a different file, which allows direct assignment (I guess the backgroundColor becomes considered like a default property in this case). Is there any way to directly assign the colors.background color without a separate file or waiting until runtime?
Is there any way to directly assign the colors.background color without a separate file or waiting until runtime?
No.
When you are declaratively setting (using it in the left hand side of a binding) a sub property (property of a grouped property) like foo.bar, the QML engine can only do so if the type of foo has a bar property.
In your example when doing thing.color, thing's type is Rectangle so it does have a color property.
When doing colors.backgroundColor, I guess the type of colors is Item and not the implicit type defined in Thing.qml.
When you are creating a 'colors' Item in a different file, the QML engine is aware of the explicit type of the object and the binding then works.
One could argue that the engine could use the implicit type for the alias property, but I'm not sure if it's the correct way, you are essentially exposing an inline implementation detail to the outside.
You could alway open a bug about that, at least to clarify things even if the behaviour is not changed.

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

Spawn QtQuick UI file from a normal QML Window class

How does one display a QtQuick UI fileset (ui.qml and .qml) from a normal QML Window?
To display other QML windows from my parent Window class, the call is, roughly:
var component = Qt.createComponent("SubWindow.qml")
var window = component.createObject(window)
window.show()
I've tried calling both the QtQuickUISubwindow.ui.qml and QtQuickUISubwindow.qml, but neither works.
Are QtQuickUI files not meant to be sub windows?
Window and ApplicationWindow are not of Type Item (or QQuickItem). This however is a requirement for being placed as a root item in a .ui.qml-file as stated in the documentation:
The following features are not supported:
[...]
Root items that are not derived from QQuickItem or Item
So the answer is:
No, you can't use QtQuickUI files neither as windows nor as sub windows.
You can however easily use them within a sub window
// main.qml
Window {
id: root
width: 800
height: 600
visible: true
Loader { // creates my sub window without JS
id: winLoader
active: false // Change to create the window.
sourceComponent: Window {
width: srcLoader.item ? srcLoader.item.width : 0
height: srcLoader.item ? srcLoader.item.height : 0
visible: srcLoader.item
Loader {
id: srcLoader
source: "QtQuickUISubwindow.ui.qml" // Your ui.qml-file *here*
active: true
}
onClosing: winLoader.active = false
}
}
Button {
text: "Show sub window!"
onClicked: winLoader.active = true
}
}
This code has not been tested by me. Maybe I'll do so later once I have access to a Qt machine. How to initialize multiple windows with a repeater and a ListModel you can find here: https://stackoverflow.com/a/47018205/2056452
You might pass the source of the srcLoader to the model, and read it from there if you want to open multiple different windows.
You can ofc modify the QtQuickUISubwindow.qml-file and add a Window or ApplicationWindow of the appropriate size as the root element. Then you can create everything as you used to do, using JS - and hope that the garbage collector plays nicely.

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.

Resources