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

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.

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

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

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.

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.

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

Related

In Qt Quick, how to ensure a ListView's delegate's width to equal the view's width?

Here's my QML view:
// Imports ommitted
Item {
id: paymentMethods
required property PaymentMethodsModel model
ColumnLayout {
anchors.fill: parent;
Text {
text: "Payment methods";
}
ListView {
Layout.fillHeight: true
Layout.fillWidth: true
model: paymentMethods.model
delegate: PaymentMethods.Item { }
}
ToolBar { }
}
}
The problem is, it looks like this:
I think it's because the delegate doesn't specify width, because if I do this:
delegate: PaymentMethods.Item {
width: parent.width
onPmSaved: {
ListView.view.model.rename(index, newName)
}
}
It looks much better:
The problem is, when I do edits that reorder the items, I get this error:
qrc:/PaymentMethods.qml:32: TypeError: Cannot read property 'width' of null
Is there a good way to set a QML ListView's delegate's width to full parent's width?
From the ListView documentation:
Delegates are instantiated as needed and may be destroyed at any time. As such, state should never be stored in a delegate. Delegates are usually parented to ListView's contentItem, but typically depending on whether it's visible in the view or not, the parent can change, and sometimes be null. Because of that, binding to the parent's properties from within the delegate is not recommended. If you want the delegate to fill out the width of the ListView, consider using one of the following approaches instead:
ListView {
id: listView
// ...
delegate: Item {
// Incorrect.
width: parent.width
// Correct.
width: listView.width
width: ListView.view.width
// ...
}
}
In the transition of the reordering, the item does not have a parent, so the error indicates it, a possible solution is to set the width of the item depending on whether it has a parent or not.
width: parent ? parent.width : 40 // default value

What is the correct understanding about ScrollBar syntax in QtQuick/QML?

Recently, I used a Scrollbar with a TableView. I referred to
QML documentation for ScrollBar and I can see an example:
Flickable {
focus: true
Keys.onUpPressed: scrollBar.decrease()
Keys.onDownPressed: scrollBar.increase()
ScrollBar.vertical: ScrollBar { id: scrollBar }
}
I thought that ScrollBar.vertical is a bool variant, but why there is an object ScrollBar { id: scrollBar } after colon?
Is there any documentation about this syntax?
What is the difference between using
ScrollBar.vertical: ScrollBar { id: scrollBar }
and
ScrollBar { id: scrollBar; orientation: Qt.Vertical }
The same confusion came to me with the code below:
Flickable {
anchors.fill: parent
contentWidth: parent.width * 2
contentHeight: parent.height * 2
ScrollBar.horizontal: ScrollBar { id: hbar; active: vbar.active }
ScrollBar.vertical: ScrollBar { id: vbar; active: hbar.active }
}
On the line anchors.fill: parent, anchors is lower-case.
I thought that ScrollBar.vertical is a bool variant, but why there is an object ScrollBar { id: scrollBar } after the colon?
The answer is simply because ScrollBar.vertical is neither a bool nor a variant but has a type of ScrollBar. This is stated in the documentation.
ScrollBar.vertical : ScrollBar
This property attaches a vertical scroll bar to a Flickable.
Flickable {
contentHeight: 2000
ScrollBar.vertical: ScrollBar { }
}
Note the subheader tells us the type after the colon: ScrollBar.
Is there any documentation about this syntax?
Yes there is. I copied the above from this page.
What is the difference between using [...]
I'll walk through each confusing line of code and label each one with its name.
// Attached Property
ScrollBar.vertical: ScrollBar { id: scrollBar }
// Child Object
ScrollBar { id: scrollBar; orientation: Qt.Vertical }
// Grouped Property
anchors.fill: parent
Let's go through these one-by-one.
Attached Properties
Attached properties [...] are mechanisms that enable objects to be annotated with extra properties or signal handlers that are otherwise unavailable to the object. In particular, they allow objects to access properties or signals that are specifically relevant to the individual object.
References to attached properties [...] take the following syntax form:
<AttachingType>.<propertyName>
For example, the ListView type has an attached property ListView.isCurrentItem that is available to each delegate object in a ListView. This can be used by each individual delegate object to determine whether it is the currently selected item in the view:
import QtQuick 2.0
ListView {
width: 240; height: 320
model: 3
delegate: Rectangle {
width: 100; height: 30
color: ListView.isCurrentItem ? "red" : "yellow"
}
}
In this case, the name of the attaching type is ListView and the property in question is isCurrentItem, hence the attached property is referred to as ListView.isCurrentItem.
(source)
In our particular case, ScrollBar is the attaching type and vertical is the property.
Keep in mind that there are several differences between ListView.isCurrentItem and ScrollBar.vertical. The former is of type bool while the latter is of type ScrollBar. Additionally, the former is a read-only property, meaning that we can't assign or change it. On the other hand, you can assign to ScrollBar.vertical.
If ListView.isCurrentItem wasn't read-only, we could've assigned it like we did with ScrollBar.vertical.
delegate: Rectangle {
ListView.isCurrentItem: true
}
But since it is read-only, this raises an error.
Child Objects
This is QML basics right here. Here's an example:
ApplicationWindow {
visible: true
width: 800; height: 600
// child object of ApplicationWindow
Rectangle {
width: 200; height: 200
color: "red"
// child object of Rectangle
Text { text: "Hello World" }
}
// child object of ApplicationWindow
Rectangle {
x: 400
width: 200; height: 200
color: "blue"
}
}
Looking back at ScrollBar:
Flickable {
ScrollBar { id: scrollBar; orientation: Qt.Vertical }
}
This will instantiate a child object ScrollBar but that's it. No added functionality.
Grouped Properties
In some cases properties contain a logical group of sub-property attributes. These sub-property attributes can be assigned to using either the dot notation or group notation.
For example, the Text type has a font group property. Below, the first Text object initializes its font values using dot notation, while the second uses group notation:
Text {
// dot notation
font.pixelSize: 12
font.b: true
}
Text {
// group notation
font { pixelSize: 12; b: true }
}
(source)
Another common example of a grouped property is anchors (as you may have noted).
Don't let the dot notation confuse you. Try to spot a generic difference between the two properties below:
anchors.top
ScrollBar.vertical
The important distinction to make is that properties must begin with a lower-case letter whereas QML types begin with an upper-case letter. With this in mind, we can see that anchors is clearly a property while ScrollBar is a type.
With those out of the way, I think we can try to address one more issue.
Why use attached properties instead of defining ScrollBar as a child object?
Because of better automation. From documentation:
When ScrollBar is attached vertically or horizontally to a Flickable, its geometry and the following properties are automatically set and updated as appropriate:
orientation
position
size
active
An attached ScrollBar re-parents itself to the target Flickable. A vertically attached ScrollBar resizes itself to the height of the Flickable, and positions itself to either side of it based on the layout direction. A horizontally attached ScrollBar resizes itself to the width of the Flickable, and positions itself to the bottom.
(source)
This allows you to focus on other things, instead of worrying about the position of the scrollbar.
But sure, instantiating ScrollBar as a child object (non-attached) also has it merits.
It is possible to create an instance of ScrollBar without using the attached property API. This is useful when the behavior of the attached scroll bar is not sufficient or a Flickable is not in use. [...]
When using a non-attached ScrollBar, the following must be done manually:
Layout the scroll bar (with the x and y or anchor properties, for example).
Set the size and position properties to determine the size and position of the scroll bar in relation to the scrolled item.
Set the active property to determine when the scroll bar will be visible.
(source)

QML: referencing root window by parent reference is unreliable

Qt/QML question. Using Qt 5.7.
Take the following simple QML program that displays a red rectangle and a blue rectangle aligned vertically. Click handlers for both rectangles attempt to change the color of the parent host window. But with a subtle difference. The red rectangle references the host window directly by it's id (rootWindow). The blue click handler changes color via a parent reference.
The former case works fine. The latter case does not work. It seems like the root window is treated specially and isn't directly part of the parent/child hierarchy, even if the Rectangles are logically nested in the code that way.
Can someone explain the rule around this?
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
id: rootWindow
color: "#ffffee"
Rectangle {
id: rect1; width: 50; height: 50; color:"red"
MouseArea {
anchors.fill: parent;
onClicked: {
print("rect1 clicked");
rootWindow.color = "green"; // works fine
}
}
}
Rectangle {
id: rect2; width: 50; height: 50; color:"blue"
anchors.top: rect1.bottom
MouseArea {
anchors.fill: parent;
onClicked: {
print("rect2 clicked");
rect2.parent.color = "pink"; // does not work
}
}
}
}
If you add the following line to the onClicked handler, you'll see that its parent isn't the Window:
print(rect2.parent)
Output:
qml: QQuickRootItem(0x18b18147bc0)
This is explained not-so-visibly in the documentation for Window:
If you assign an Item to the data list, it becomes a child of the Window's contentItem, so that it appears inside the window. The item's parent will be the window's contentItem, which is the root of the Item ownership tree within that Window.
The window itself isn't an item, so it uses contentItem instead so that child items can have a parent.
However, in Qt 5.7, Window got an attached property that can be used to access the window of an item:
rect2.Window.window.color = "pink";
Whichever item comes before the Window.window part will be the item that the attached property is used on. You could use it on any item in this scene (e.g. the MouseArea), as they all belong to the same window.
Note that attached properties create a QObject-derived object for each unique item they're used on, so be mindful of how you use them, especially in items that are created in very large numbers.

ListView signals and slots for menu elements

I'm trying to implement some sort of custom Menu with custom elements. The ultimate goal is to create some sort of popup menu with text and icons. But during creation I faced with some issues. I can show 2 primary problems:
There is a strange menu element with title Hello world at the first position (looks like it's read title of application window):
From time to time I'm getting errors like qrc:/BreezeQuickMenu.qml:45: TypeError: Property 'clicked' of object QQuickListView(0x1120830) is not a function
Here is my actual code:
main.qml
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Hello World")
width: Screen.width
height: Screen.height
visible: true
id: win
color: brPalette.normalBackground
BreezeQuickMenu{
id: brMenu
x: 490
y: 199
width: 128
height: 256
palette: brPalette
menuFont.pointSize: 16
BreezeQuickMenuItem{
title: "Item 1"
onClicked: mbox.show()
}
BreezeQuickMenuItem{
title: "Item 2"
}
BreezeQuickMenuItem{
title: "Item 3"
}
}
}
BreezeQuickMenu.qml
import QtQuick 2.4
Item {
id: root
property BreezeQuickPalette palette: BreezeQuickPalette
property alias currentIndex: menuList.currentIndex
property font menuFont
property bool menuVisible: false
implicitWidth: 128
implicitHeight: menuList.height
ListView{
id: menuList
anchors.fill: parent
model: root.children
clip: true
delegate: Component {
id: menuItem
Rectangle {
id: menuElement
property bool isCurrentItem: ListView.isCurrentItem
anchors {
left: parent.left
right: parent.right
}
color: palette.normalBackground
height: menuText.font.pixelSize*1.2
Text {
id: menuText
anchors.fill: parent
text: title
color: palette.normalText
font: menuFont
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
menuList.currentIndex = index
menuList.model[index].clicked()
}
}
}
}
}
}
BreezeQuickMenuItem.qml
import QtQuick 2.4
Item {
id: root
property string title: "Menu Element"
signal clicked
}
As you can see I'm trying to implement menu list and menu items with their own signals. I have 2 questions:
how can I properly get rid of using title property of parent element, since I need to read title property of childrens
what is the correct approach of using signals and slots in menu elements to avoid above error?
Please help me to understand. Full project can be pulled here:
git clone git://git.code.sf.net/p/breezequick/code breezequick-code
The problem with the signal is related to its declaration. Signals are always declared as a function would be: with a signature. In other words, a signal without parameters has the form
signal <signal_name>()
That's also why you got the error "is not a function". Apart from that, the usage of signals/signal handlers is correct. Anyhow, reading carefully the documentation wouldn't hurt. This page covers in detail the argument.
Coming to the other problem, you made the wrong assumption: anything that is declared inside a component is part of the children of the component itself. Here you declared a BreezeQuickMenu which has a child ListView. When you use it and add the BreezeQuickMenuItems, you add them to the same set to which the ListView belongs. In the end you have four elements in the children property. Also, by adding the ListView to itself through the model you mess up things to the point that a totally unrelated string is rendered.
There are several ways to handle Items as model members for a view, inclusing VisualItemModel and using object Instanced as models. However, by skimming your code, it is clear that you want to define a component which adds menu items in a declarative fashion. Using children is not sufficient in this case. You also need the default property:
An object definition can have a single default property. A default property is the property to which a value is assigned if an object is declared within another object's definition without declaring it as a value for a particular property.
Hence you can define the default property for your BreezeQuickMenu and exploit it to obtain the desired children for your list. A common approach would be the following (code simplified):
import QtQuick 2.4
Item {
id: root
property BreezeQuickPalette palette: BreezeQuickPalette
property alias currentIndex: menuList.currentIndex
// default declaration (1)
default property alias contents: addItem.children
// Item to which the inner declared meantime will belong (2)
Item {
id: addItem
}
property font menuFont
property bool menuVisible: false
implicitWidth: 128
implicitHeight: menuList.height
ListView{
id: menuList
anchors.fill: parent
model: contents // usage of the default property (3)
clip: true
delegate: Rectangle {
// your current delegate code
}
}
}
The basic idea is to exploit also property alias: basically in (1) we are saying that "all the Items declared inside BreezeQuickMenu are automatically children of addItem which is an inner declared Item (2). In this way the ListView is kept apart whereas all the BreezeQuickMenuItem are gathered together, under addItem children property. At this point, it is sufficient to use the same children property as the model (3) for the ListView and that's it.

Building TabBar in QML - Loader doesn't show all the Rectangles

import QtQuick 2.4
import QtQuick.Window 2.2
Window
{
visible: true
height: 500
width: 500
property VisualItemModel contentToBeShownOnTabClick : visualItemModelDemo
property variant tabLabels : ["Navigation", "Payload", "System Control"]
VisualItemModel
{
id: visualItemModelDemo
Rectangle
{
id: navigationTab
color: "green"
height: 200
width: 200
}
Rectangle
{
id: navigationTab1
color: "darkgreen"
height: 200
width: 200
}
Rectangle
{
id: navigationTab2
color: "lightgreen"
height: 200
width: 200
}
}
MainForm
{
Component
{
id: tabsOnBottomComponent
Repeater
{
model: tabLabels
// The Tabs
Rectangle
{
id: tabsOnBottom
// This anchoring places the tabs on the outer top of the parent rectangle.
anchors.top: parent.bottom
anchors.topMargin: 180
color: "lightsteelblue"
border.color: "steelblue"
border.width: 2
implicitWidth: Math.max ((labelTabsBottom.width + 4), 80)
implicitHeight: 20
radius: 2
// Tabs Text/Label
Text
{
id: labelTabsBottom
anchors.centerIn: parent
color: "white"
rotation: 0
// With reference to mode: tabLabels
text: modelData
font.pointSize: 11
}
MouseArea
{
anchors.fill: parent
onClicked: bottomTabClicked (index);
}
}
}
}
Rectangle
{
// The things which get displayed on clicking of a tab will be shown in this rectangle.
id: areaForTabContents
border.color: "black"
border.width: 10
height: parent.height
width : parent.width
color : "pink"
// These are the tabs displayed in one row - horizontally.
Row
{
id: horizontalTabs
Loader
{
anchors.fill: parent
sourceComponent: tabsOnBottomComponent
}
}
}
anchors.fill: parent
}
}
This gets shown as follows:
whereas I want it to see 3 rectangles there side by side.
Loader is not a transparent type w.r.t. the containing type, Row in this case. I think this is an issue related to creation context and the way Repeater works. From the documentation of the latter:
Items instantiated by the Repeater are inserted, in order, as children of the Repeater's parent. The insertion starts immediately after the Repeater's position in its parent stacking list. This allows a Repeater to be used inside a layout.
The Rectangles are indeed added to the parent which is the Loader, they stack up - Loader does not provide a positioning policy - then they are added to the Row resulting in just one Item (the last one) to be visible.
You can tackle the problem with few different approaches, depending on the properties you want to maintain or not. I would get rid of anchoring in the Component and move it to the containing Row. A too specific anchoring inside a Component could be a pain in the neck when it is instanced and used all over a (not so small) project.
As a first approach you can re-parent the Repeater to the Row, i.e. you can rewrite code as:
Row
{
id: horizontalTabs
Loader
{
sourceComponent: tabsOnBottomComponent
onLoaded: item.parent = horizontalTabs
}
}
However this would result in warnings due to the Component anchoring references not working as expected any more.
If you still want to maintain the anchoring, as defined in the Component, and off-load the creation, you can go for the dynamic way (if the semantics fits in your use case), i.e. you can use createObject. This way you totally avoid the Loader and the related issue. For instance, you can create the Repeater once the Row has completed its creation:
Row
{
id: horizontalTabs
Component.onCompleted: tabsOnBottomComponent.createObject(horizontalTabs)
}
Clearly, the creation code can be move anywhere else, depending on your needs.

Resources