5

How to make some reusable QML object, which can inject another object?

I've ever tried to use Component & Loader , but seems not what I want. (It still encapsulate the whole QML type and lacks of elasticity, hard to reuse)

Usage example:

Card.qml

import QtQuick 2.0
import QtQuick.Layouts 1.3

Rectangle {
    default property var innerObject
    property string titleText: "[Hello Untitled Title]"
    id: root
    color: "#fff"
    ColumnLayout {
        anchors.fill: parent
        Rectangle {
            id: header
            height: 10
            width: parent.width
            color: "#666"
            RowLayout {
                Text { text: titleText; color: "#fff" }
            }
        }

        // How to inject innerObject in here ?

    }
}

main.qml

import QtQuick 2.0
import QtQuick.Layouts 1.3

Card {
    titleText: "Image Information"
    ColumnLayout { /* .......*/ }   // innerObject
}

Card {
    titleText: "Image Viewer"
    Rectangle { /* .......*/ }      // innerObject
}
kuanyui
  • 782
  • 13
  • 23
  • 3
    Possible duplicate of [How do you assign a QML Item to a component property in QML and then use that object inside the component?](http://stackoverflow.com/questions/5021350/how-do-you-assign-a-qml-item-to-a-component-property-in-qml-and-then-use-that-ob) – Teemu Risikko Sep 21 '16 at 10:22
  • See especially: http://stackoverflow.com/a/10031214/6845813 – Teemu Risikko Sep 21 '16 at 10:22

3 Answers3

4

The answer I linked works like this:

Main.qml

Card {
    titleText: "Image Viewer"
    innerObject: Rectangle {
        Component.onCompleted: {
            console.log(parent.objectName)
        }
    }
}

Card.qml

Rectangle {
    property string titleText: "[Hello Untitled Title]"

    default property alias innerObject : innercolumn.children


    id: root
    color: "#fff"
    ColumnLayout {
        id: innercolumn
        objectName: "column"
        anchors.fill: parent
        Rectangle {
            id: header
            height: 10
            width: parent.width
            color: "#666"
            RowLayout {
                Text { text: titleText; color: "#fff" }
            }
        }
    }
}
Teemu Risikko
  • 1,205
  • 11
  • 16
  • 2
    Actually, you don't need a placeholder item at all, you can directly `default property alias innerObject: innerColumn.children` – dtech Sep 21 '16 at 12:01
  • As for `default property` not kicking in when using it "locally" it looks like a bug. It shouldn't really matter. – dtech Sep 21 '16 at 12:08
4

I also want to suggest a solution based on default property and reparenting:

The Item which can embed another Item:

MyItem.qml

import QtQuick 2.7
import QtQuick.Layouts 1.2

Rectangle {
    id: root
    default property Item contentItem: null
    border {
        width: 1
        color: "#999"
    }
    ColumnLayout {
        anchors.fill: parent
        Rectangle {
            Layout.fillWidth: true
            height: 30
            color: "lightgreen"
        }
        Item {
            id: container
            Layout.fillWidth: true
            Layout.fillHeight: true
        }
    }
    onContentItemChanged: {
        if(root.contentItem !== null)
            root.contentItem.parent = container;
    }
}

Can be used as below:

main.qml

import QtQuick 2.7
import QtQuick.Window 2.0

Window {
    visible: true
    width: 600
    height: 600

    MyItem{
        width: 400
        height: 400
        anchors.centerIn: parent
        Text {
            text: "Hello!"
            anchors.centerIn: parent
        }
    }
}

But I still agree with @ddriver that Loader is the best solution for this case

folibis
  • 12,048
  • 6
  • 54
  • 97
2

It is not mandatory that you use a Loader with a component. You can just go:

Loader {
   source: "Something.qml"
}

When the source is something that can be loaded synchronously, you can directly use the loader's item for stuff like bindings, without worrying about whether or not it is created. If you load over network, you have to delay the bindings until the item is completed and use either a Binding element or Qt.binding() to do it respectively in a declarative or imperative manner.

In your case, a loader would be appropriate, and the property for the inner dynamic object outta be a Component. This way you can populate it either with an inline component, or with Qt.createComponent() from existing source.

property Component innerObject
...
innerObject: Component { stuff }
...
innerObject: Qt.CreateComponent(source)

Of course, there are even more advanced ways to do it, for example, the "generic QML model object" I have outlined here. It allows to quickly and easily create arbitrary data structure trees both declaratively and imperatively, and since the object is also a model, you can directly use listviews or positioner elements with repeaters to layout the gui without actually writing the UI code each and every time.

Also, from your main.qml code example - you cannot have more than one root element in a qml file.

Edit: The default property approach actually works if the element is moved to its own qml file, so also basically you could just:

default property alias innerObject: innerColumn.children

where innerColumn is the id of your ColumnLayout. Also, innerObject could be whatever legal name, since as a default property, it will not actually be used.

There is also the option to not use a default property, which is useful when the root item still needs to have its own children, but still have the ability to redirect declarative objects to be children of a sub-object:

property alias content: innerColumn.children
// and then
content: [ Obj1{}, Obj2{}, Obj3{} ] // will become children of innerColumn
Community
  • 1
  • 1
dtech
  • 47,916
  • 17
  • 112
  • 190
  • `Card { titleText: "Image Viewer" data: Rectangle { Component.onCompleted: { console.log(parent.objectName) } } } ` will print the inner object name if it is defined in the Card.qml as default property alias data : inner_space.data, where inner_space is an `Item { id: inner_space; objectName: "inner" }`. – Teemu Risikko Sep 21 '16 at 11:47
  • @TeemuRisikko - it actually works if it is moved to a separate qml file. I removed that part from the answer. – dtech Sep 21 '16 at 11:53