How to define a “template” with child placeholders in QML?

匿名 (未验证) 提交于 2019-12-03 02:45:02

问题:

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.

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


回答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     ...  } 


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



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



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



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!