How to define a "template" with child placeholders in QML?

13,807

Solution 1

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

Solution 2

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

}

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

Solution 4

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.

Solution 5

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

Share:
13,807
leemes
Author by

leemes

Updated on June 14, 2022

Comments

  • leemes
    leemes about 2 years

    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.

  • leemes
    leemes almost 12 years
    Thanks, didn't know what the QML Loader is and that it helps in such situations. I'll have a look at it.
  • leemes
    leemes almost 12 years
    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.)
  • Misch
    Misch almost 12 years
    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.
  • leemes
    leemes almost 12 years
    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.)
  • mlvljr
    mlvljr over 11 years
    If you have questions or additional requirements, feel free to ask (or request changes) :)
  • leemes
    leemes over 11 years
    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.
  • mlvljr
    mlvljr over 11 years
    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
    mlvljr over 11 years
    btw, if you're still wondering about the QML scope puzzle from the talk, see: stackoverflow.com/questions/11339719/qml-component-scope-puz‌​zle
  • leemes
    leemes over 11 years
    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
    leemes over 11 years
    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.
  • mlvljr
    mlvljr over 11 years
    Interesting, don't remember that (d-ptr), so that's a some sort QML/js pimpl?
  • mlvljr
    mlvljr over 11 years
    @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)).
  • Larpon
    Larpon over 8 years
    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?
  • Marc Van Daele
    Marc Van Daele over 7 years
    The same "Cannot assign object .." error occurs when adding eg a VisualItemModel object.
  • Marc Van Daele
    Marc Van Daele over 7 years
    @Larpon: you can explicitly add the connection to the resources as shown here 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)
  • Larpon
    Larpon over 7 years
    @MarcVanDaele Thanks - yeah I eventually found out and added an answer :) Future googlers: see my answer below
  • Benp44
    Benp44 over 7 years
    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++.
  • Zitrax
    Zitrax about 7 years
    stackoverflow.com/a/10031214/11722 has en example that also shows the addition to the placeholder.
  • S.M.Mousavi
    S.M.Mousavi almost 6 years
    Yeah! excellent note. According to the official documentation we SHOULD use data not children for this purpose.