31

I really like QML. I like how I can define components (comparable to classes) and their properties, and instantiate them from somewhere else (comparable to objects).

I can define, let's say, a button, having some look and feel, and a label text on it. This could be done, for example, using this component definition (Button.qml):

Item {
    id: button
    property string label

    anchors.fill: parent

    Rectangle {
        anchors.fill: parent
        radius: 10
        color: "gray"

        Text {
            anchors.centerIn: parent
            font.pixelSize: 20
            text: button.label
            color: "white"
        }
    }
}

and instanciated in this main file (main.qml):

Rectangle {
    width: 300
    height: 200

    Button {
        anchors.centerIn: parent
        anchors.margins: 50
        label: "Hello button!"
    }
}

But I see the following restriction: I can only define a button template with some properties, not with some placeholder. All children defined in the instance will be direct children, at least per default, and I want to change this behavior.

Let's say I want to place an item (let's say an image, but I don't want to tell the definition of Button that it will be an image) in the button. I imagine something like this:

Item {
    id: button
    property Item contents   <-- the client can set the placeholder content here

    anchors.fill: parent

    Rectangle {
        anchors.fill: parent
        radius: 10
        color: "gray"

        Item {
            id: placeholder     <-- where the placeholder should be inserted
        }
    }

    Component.onCompleted: {
        // move the contents into the placeholder...
    }
}

How can I achieve this? I don't know if using Component.onCompleted is the correct way. Note that, however, that in my case the contents will never change afterwards (at least in my current design of the application...).

Also, I want anchoring to work within the placeholder. For example, if I define the contents to be a Text element, being centered in its parent (which will first be the template itself). Then my code moves this Text instance into the placeholder and the parent anchors should then be those of the placeholder item, not the template item.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
leemes
  • 44,967
  • 21
  • 135
  • 183

5 Answers5

41

I found a much nicer answer to this question, suggested in a presentation of the Qt Developer Days 2011 "Qt Quick Best Practices and Design Patterns".

They use default property alias ... to alias the child items to any property of any item. If you don't want to alias the children but give the alias property a name, just remove default. (Literal children are per QML definition the value of the default property.)

Item {
    id: button
    default property alias contents: placeholder.children

    anchors.fill: parent

    Rectangle {
        anchors.fill: parent
        radius: 10
        color: "gray"

        Item {
            id: placeholder     <-- where the placeholder should be inserted
        }
    }
}
BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
leemes
  • 44,967
  • 21
  • 135
  • 183
  • btw, if you're still wondering about the QML scope puzzle from the talk, see: http://stackoverflow.com/questions/11339719/qml-component-scope-puzzle – mlvljr Oct 07 '12 at 21:10
  • 1
    Fantastic, thank you for this link! ;) By the way, the "d-pointer" (also in this talk) can be used to hide private members (also JS functions) better. I find it better than the double underscore. I even name it "priv" to ensure that nobody will accidentally access members of this child object. – leemes Oct 07 '12 at 22:07
  • Interesting, don't remember that (d-ptr), so that's a some sort QML/js pimpl? – mlvljr Oct 07 '12 at 22:28
  • 3
    Adding a Connections QML object to the template this way will give you an error: "Cannot assign object to list property "contents" - how does one deal with that? – Larpon Jan 06 '16 at 15:09
  • The same "Cannot assign object .." error occurs when adding eg a VisualItemModel object. – Marc Van Daele Oct 21 '16 at 08:52
  • @Larpon: you can explicitly add the connection to the resources as shown here http://doc.qt.io/qt-5/qml-qtquick-item.html#data-prop. In your case, it would be something like resources: [ Connections {...} ]. (Edit: I just noted your answer below) – Marc Van Daele Oct 21 '16 at 09:39
  • 1
    @MarcVanDaele Thanks - yeah I eventually found out and added an answer :) Future googlers: see my answer below – Larpon Nov 09 '16 at 20:28
  • Very useful, thanks. Also worth noting that the item inserted into the placeholder is still in the component tree, so can be accessed from C++. – Benp44 Dec 01 '16 at 16:13
13

Necro answering in case someone else end up here as I did.

In Qt5 somewhere along the line the default property became "data" and not "children". This makes it possible to add other object types than "Item". e.g. Connections can be added as well (to answer my own question above)

So in Qt5 you should do:

Item {
    id: button
    default property alias contents: placeholder.data

    anchors.fill: parent

    Rectangle {
        anchors.fill: parent
        radius: 10
        color: "gray"

        Item {
            id: placeholder     <-- where the placeholder should be inserted
        }
    }
}

Note the: placeholder.data instead of placeholder.children Also please note that you don't have to use the alias name contents - this can be anything you like. An example:

Item {
    id: button
    default property alias foo: placeholder.data
    ...

}
Larpon
  • 812
  • 6
  • 19
  • https://stackoverflow.com/a/10031214/11722 has en example that also shows the addition to the placeholder. – Zitrax May 29 '17 at 10:30
  • 1
    Yeah! excellent note. According to [the official documentation](http://doc.qt.io/qt-5/qml-qtquick-item.html#resources-prop) we SHOULD use `data` not `children` for this purpose. – S.M.Mousavi Sep 06 '18 at 06:23
3

Actually, the correct answer from what I've heard is to use a QML Loader to accomplish what you want.

[that being said; I haven't actually tried it yet but it's on my near-term to-try list and looks fairly straight forward]

Also, search stackoverflow for other "QML Loader" questions as there are a number that will help you get started.

Community
  • 1
  • 1
Wes Hardaker
  • 21,735
  • 2
  • 38
  • 69
  • Thanks, didn't know what the QML Loader is and that it helps in such situations. I'll have a look at it. – leemes Sep 18 '12 at 13:34
  • Like the Misch's answer, your solution doesn't work with anchoring. Example: `Loader { sourceComponent: Rectangle { color: "red"; anchors.fill: parent } }`. The Rectangle should fill the parent of the Loader, but it doesn't. (It's invisible unless I explicitly set a width and height.) – leemes Sep 18 '12 at 13:57
1

You can move the item(s) (if you want to support multiple items within the placeholder) using this piece of code:

property list<Item> contents

Component.onCompleted: {
    var contentItems = [];
    for(var i = 0; i < contents.length; ++i)
        contentItems.push(contents[i]);
    placeholder.children = contentItems;
}

Note that you do not have to provide a list of Items for the contents property, as single values will be accepted by the QML engine also for list properties.

Misch
  • 10,350
  • 4
  • 35
  • 49
  • Yeah, this seems to be the correct code snippet to move the items. It works, but it doesn't respect the anchoring. (If I provide a Text with `anchors.centerIn: parent` for example, the result looks like I didn't set the anchors property.) – leemes Sep 18 '12 at 13:36
  • I don't know how to "update" the anchors property (maybe the content items still think that their parent is the template?) but you can resize them to fill the contents area. Just set their width and height to the placeholder's size. – Misch Sep 18 '12 at 13:39
1

In short (to show the idea):

import QtQuick 1.1

Item {
    width: 200
    height: 100

    //<-- the client can set the placeholder content here
    property Item contents: Rectangle {
        anchors.fill: parent
        anchors.margins: 25
        color: "red"
    }

    Rectangle {
        id: container

        anchors.fill: parent
        radius: 10
        color: "gray"

        //<-- where the placeholder should be inserted
    }

    Component.onCompleted: {
        contents.parent = container
    }
}

Somewhat longer version (supporting contents reassignment):

import QtQuick 1.1

Item {
    width: 200
    height: 100

    //<-- the client can set the placeholder content here
    property Item contents: Rectangle {
        //anchors can be "presupplied", or set within the insertion code
        //anchors.fill: parent
        //anchors.margins: 25
        color: "red"
    }

    Rectangle {
        id: container

        anchors.fill: parent
        radius: 10
        color: "gray"

        //<-- where the placeholder should be inserted
        //Item {
        //    id: placeholder
        //}
    }

    //"__" means private by QML convention
    function __insertContents() {
        // move the contents into the placeholder...
        contents.parent = container
        contents.anchors.fill = container
        contents.anchors.margins = 25
    }

    onContentsChanged: {
        if (contents !== null)
            __insertContents()
    }

    Component.onCompleted: {
        __insertContents()
    }
}

Hope this helps :)

mlvljr
  • 4,066
  • 8
  • 44
  • 61
  • If you have questions or additional requirements, feel free to ask (or request changes) :) – mlvljr Oct 07 '12 at 20:11
  • 2
    Thanks for this solution, but I added another answer by myself quoting something very nice from a Qt Developer Days presentation I found a few days ago. – leemes Oct 07 '12 at 20:45
  • Yeah, that's another trick, but beware that with it 1) you are limited to adding the items statically (unless you manipulate the children property directly, which kind of defeats the initial purpose of aliasing) and 2) (a minor thing) instances inheriting QtObject (like timers) cannot be added this way (as children is a list of Item's, not plain QtObjects). Also, using the reparenting approach, you can position added items arbitrarily (putting each in its own container, etc). – mlvljr Oct 07 '12 at 21:08
  • For problem 2) I think there is the property `resources` or a similar name. I wasn't aware of problem 1), thank your for this hint. But in QML we have only rare situations where we want to take children away, I guess, at least in the particular situation I described (when we want to make the "label" of a component more dynamic than just to be a string) – leemes Oct 07 '12 at 22:04
  • @leems btw, the children aliasing trick is nice to actually walk the children and somehow programmatically set them up (a classic example is a screen stack: you set the screens declaratively --as children, and the item properly sets them up and starts managing them (especially useful if the screens actually expose footer/header/etc parts as QML properties, and simple reparenting isn't enough)). – mlvljr Oct 07 '12 at 22:33