Qt QML: in-file definition of reusable objects
Asked Answered
F

4

8

I have a Qml component that is reasonably large so that I want to make it a reusable component but it is too small/non-generic such that I want to avoid creating its own .qml file. It seems like Components are the right method to define reusable objects in the same file, but when I do that I don't know how to access and change properties of the contained objects.

More specifically, imagine having an in-file definition like this

Component {
id: myReusableComponent
// This cannot be set because component does not allow properties
// (Would work fine in a separate file myReusableComponent.qml)
// property alias string innerText: innerText.text
Rectangle {
    id: rect
    width: 200
    height: 200
    color: "red"
    Text {
        id: innerText
        text: "Want to set text later"
    }
}

How can I reuse this component later, while changing some properties of it? I know the following is not valid syntax, but I want to use it similarly to this:

Loader {
id: hello
sourceComponent: myReusableComponent
item.innerText: "Hello" }

Text { text: "Some other stuff in between" }

Loader {
id: world
sourceComponent: myReusableComponent
item.anchors.left: hello.right
item.rect.width: 100
item.rect.color: "blue"
item.innerText: "World" }

Loader {
id: excl
sourceComponent: myReusableComponent
item.rect.color: "green"
item.innerText: "!!!" }
etc...

Any ideas? Or is there a fundamentally different way of doing this?
What I essentially want is reusability of QML objects that are defined in place while still being able to change the properties of them.

This seems to be related but does not solve the problem of creating multiple objects. Repeaters seem to be useful but don't give me the flexibility I want.


NOTE: I will add some remarks about the answers here since they might get overlooked in the comments. I like all three answers from Blabbouze, derM and ddriver! I accepted Blabbouze's answer because it provides a concrete solution that is closest to what I was looking for. However, I also didn't know about the overhead of Loaders and might consider using a different approach after reading derM's answer. Finally, dynamic object creation as suggested by ddriver is not exactly what I was looking for but might be useful for others. Thanks everyone!

Fringe answered 30/11, 2016 at 14:32 Comment(3)
How about using States (doc.qt.io/qt-5/qml-qtquick-state.html) ?Reinhold
@Reinhold will states allow me to create multiple copies of an object? I have only used it to do transitions on one and the same object so far....Fringe
I thought more like using the same component over an over again, but give each object a different state, so it will have different properties.Reinhold
R
5

I think you are looking for onLoaded() signal. It is emitted when the Loader have successfully created the Component.

You can then access your loaded type properties with item.

import QtQuick 2.7
import QtQuick.Controls 2.0


ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")


    Component {
        id: myReusableComponent
        Rectangle {
            id: rect
            property alias innerText: innerText.text

            width: 200
            height: 200
            color: "red"
            Text {
                id: innerText
                text: "Want to set text later"
            }
        }
    }


    Loader {
        id: hello
        sourceComponent: myReusableComponent
        onLoaded: {
            item.innerText = "hello"
        }
    }

    Loader {
        id: world
        sourceComponent: myReusableComponent
        anchors.left: hello.right
        onLoaded: {
            item.width =  100
            item.color =  "blue"
            item.innerText =  "World"
        }
    }
}
Regiment answered 30/11, 2016 at 15:27 Comment(1)
You can also bind to the properties of the loaded item in case the value is not fixed like in this example. To do that you use a Binding element with the loader's item as the targetInsubordinate
D
4

In Qt5.15 there is a new inline component syntax:

component <component name> : BaseType {
    // declare properties and bindings here
}

In the following example, I define NormalText from Text and, subsequently, I define HeadingText from NormalText, then, I use them all in the same QML file:

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

Page {
    anchors.fill: parent
    component NormalText : Text {
        Layout.fillWidth: true
        font.pointSize: 12
        wrapMode: Text.WordWrap
    }
    component HeadingText : NormalText {
        font.pointSize: 14
        font.bold: true
    }
    ColumnLayout {
        width: parent.width
        HeadingText { text: "Introduction" }
        NormalText { text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." }
        NormalText { text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." }
    }
}

You can Try it Online!

References:

Dicker answered 28/9, 2022 at 22:6 Comment(0)
A
1

At first I will try to explain why you do not want to do it

If the Component is reused - even in one file and not only by the means of Repeaters, ListViews e.t.c - you should consider creating a seperate file, to keep your file clean and readable.

If you create inline-components, and then create the instances by the means of a Loader just to be able to create them, they come with an overhead, that can be tolerated if the component is really enormous, or you really need the Loader for the possibility to change the source dynamically. Otherwise, it helps you only to make everything complex, and raise the ressource consumption.

There is almost no penalty for outlaying the code into multiple files. So do it, when ever you have a reasonable, logically enclosed unit - and especially when you are going to reuse it multiple times in a file.

Use the Component when you are planning to use this as a delegate. Some perfere to directly declare the delegate where it is used. I do so, if there are only few properties to set.
Especially if you have multiple Views that share the same delegate-prototype, it is a good idea to use the Component.

If you really need to use a Loader (for the reasons you need a Loader and not for non-dynamic object creation) then you can use a Component to avoid the need of the onLoaded-event. It enables you to preconfigure the Component, and set the event-handlers without the need of a Connections-Object.

Ok - now a short answer to your question:
You can create instances of a Component by many means:

  • Loader
  • Views (ListView, GridView, Repeater ...)
  • Dynamic Object Creation with JS (componentID.createObject(parent)) at any point where you can execute JS code.

Read: http://doc.qt.io/qt-5/qtqml-javascript-dynamicobjectcreation.html#creating-objects-dynamically

America answered 30/11, 2016 at 15:48 Comment(9)
You can perfectly well reuse components that are in different qml files, all you need is a reference to them, just expose them as properties. I for one can testify qml project management gets very ugly as the number of qml files increase, there is really no nice way to give them structure.Intarsia
Yes, you can. I do sometimes, but only if the Component is needed as a Component because I want to use dynamic object creation. To create single objects with Loader et. al. comes with too large an overhead, if there is no other excuse for the Loader. Dynamic JS Object Creation comes with the problems of Object Lifetime Management and Ownership IssuesAmerica
I greped my projects file and the only time I use Component is when I need to use Component.createObject to create items dynamically. For simple delegates ( < ~5 lines ) I just declare them inline, for all the other use cases, I create a new file.Whack
There is one penalty/advantage that would come, if you use the source-property of a Loader rather then a sourceComponent. A sourceComponent is loaded slightly faster, especially for the first time, as it has been compiled when the file including the Component has been compiled, while the source gets compiled when it is loaded for the first time.America
It would have been very beneficial if you could do something like this Component { id: comeComp ... } and then someComp { }, but alas...Intarsia
@Whack - well, it doesn't really make sense to use it anywhere else really, delegates as well as other properties that expect a Component will actually work without it, for example you can property Component comp : Rectangle {} and it will work as expected, loaders included. Seems like there is some implicit conversion, or maybe every object instance is a component subclass or something.Intarsia
@ddriver That's what I was saying more or less.Whack
@GrecKo, AFAIK it is generally recommended to not have cross-file-references. If you do not use In-File-Components for complex delegates and still stay true to the statement with the < ~5 lines, I don't know how you use e.g. the model-data. Therefore I usually create the component as an external file, exposing all necessary properties and signals, and then create an In-File-Component of this, where I assign the model data, the signals and so on, and use this Component as delegate. So I have the delegate: ... clean (to one line), and avoid cross-file-references.America
This also allows me, to easily change the delegate.America
I
0

You could also try to use dynamic object creation with components.

So you can have a property Rectangle obj: null, and then:

Component.onCompleted: {
  obj = myReusableComponent.createObject(parentItem, {"width" : 100, "color" : "blue" }
}

In addition to Blabbouze's answer, aside from assignments (which are not auto updated as bindings) you can also have bindings if you use the format:

item.prop = Qt.binding(function() { bindingExpressions... })
Intarsia answered 30/11, 2016 at 15:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.