kuanyui
kuanyui

Reputation: 781

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

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
}

Upvotes: 6

Views: 4011

Answers (3)

dtech
dtech

Reputation: 49289

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

Upvotes: 2

folibis
folibis

Reputation: 12864

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

Upvotes: 4

Teemu Risikko
Teemu Risikko

Reputation: 3265

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

Upvotes: 4

Related Questions