How to put attached properties to child item - qt

Let's assume I have a component like this
RowLayout {
MyItem {
Layout.fillWidth: true
Layout.fillHeight: true
... // other properties
}
MyItem {
Layout.fillWidth: true
Layout.fillHeight: true
... // other properties
}
}
in which MyItem.qml is defined like this
Rectangle {
... // other properties
// Layout.fillWidth: true
// Layout.fillHeight: true
}
Can I put Layout.fillWidth to MyItem, so that I don't need to repeat it in RowLayout ?

Can I put Layout.fillWidth to MyItem, so I don't need to repeat it in RowLayout ?
I think the question has the answer in it: if you don't want to repeat, just use the Repeater type. The documentation states that
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 example which follows in the documentation uses Row but the very same approach can be applied to other layouts, e.g. RowLayout. Actually, it works for any type with attached properties as per the Repeater nature ("insert items inside parent").
Here is an example. Assume we have defined an Example type.
import QtQuick 2.5
Rectangle {
property alias text: inner.text
color: "steelblue"
Text {
id: inner
anchors.centerIn: parent
font.pixelSize: 30
}
}
We can add the layout properties to our Example type inside the Repeater, for instance like this:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
Window {
id: window
width: 600
height: 400
visible: true
RowLayout {
id: row
anchors.fill: parent
Repeater {
model: 6
delegate : Example {
text: index
Layout.fillWidth: true // layout options added in the delegate
Layout.fillHeight: true
Layout.alignment: Qt.AlignCenter
Layout.maximumWidth: parent.width / model.length
}
}
}
}
The model property of the Repeater can be either an array of strings or another model, as usual.
This approach is flexible enough to combine several Repeaters to create more complex structures. See for instance the following example in which Text is used to fill the screen inside a GridLayout:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
Window {
id: window
width: 600
height: 400
visible: true
GridLayout {
id: grid
anchors.fill: parent
rows: 2
columns: 6
Repeater {
model: grid.columns
Text {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.row: 0
Layout.column: index
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: index + 1 // index of the repeater as text
}
}
Repeater {
model: grid.columns
Text {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.row: 1
Layout.column: index
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: index + 7
}
}
}
}

Yes, you can do that, but it will end in an error whenever you decide to use that component in a context where it has no attached property named Layout.fillWidth or, more in general, whenever you decide not to use it as a top element within a layout.

Related

Qt QML binding child property to parent property

I want to set the ApplicationWindow to minimum width and height by the minimum width and height of the child element "mainLayout". I am having trouble to use the property of "mainLayout" in the parent QML ApplicationWindow. I tried to make the property viewable by making an alias. Not sure if it is the right solution. It does not work. But there is also no Error when I run.
My code looks like this:
main.qml
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.3
import QtQuick.Window 2.2
ApplicationWindow {
visible: true
width: 1500
height: 1200
property int margin: 11
minimumWidth: serial.mainLayout.minimumWidth + 2 * margin //this one is not working
minimumHeight: serial.mainLayout.minimumHeight + 2 * margin //this one is not working
Serial {
id: serial
anchors.fill: parent
}
}
Serial.qml
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import io.qt.serialComm 1.0
Item {
anchors.fill: parent
id: item
property alias mainLayout: mainLayout
ColumnLayout {
id: wrapper
width: parent.width/2
height: parent.height/2
ColumnLayout {
id: mainLayout
anchors.fill: parent
anchors.margins: margin
GroupBox {
id: rowBox
title: "Row layout"
Layout.fillWidth: true
RowLayout {
id: rowLayout
anchors.fill: parent
TextField {
placeholderText: "This wants to grow horizontally"
Layout.fillWidth: true
}
Button {
text: "Button"
}
}
}
GroupBox {
id: gridBox
title: "Grid layout"
Layout.fillWidth: true
GridLayout {
id: gridLayout
rows: 3
flow: GridLayout.TopToBottom
anchors.fill: parent
Label { text: "Line 1" }
Label { text: "Line 2" }
Label { text: "Line 3" }
TextField { }
TextField { }
TextField { }
TextArea {
text: "This widget spans over three rows in the GridLayout.\n"
+ "All items in the GridLayout are implicitly positioned from top to bottom."
Layout.rowSpan: 3
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
TextArea {
id: t3
text: "This fills the whole cell"
Layout.minimumHeight: 30
Layout.fillHeight: true
Layout.fillWidth: true
}
GroupBox {
id: stackBox
title: "Stack layout"
implicitWidth: 200
implicitHeight: 60
Layout.fillWidth: true
Layout.fillHeight: true
StackLayout {
id: stackLayout
anchors.fill: parent
function advance() { currentIndex = (currentIndex + 1) % count }
Repeater {
id: stackRepeater
model: 5
Rectangle {
color: Qt.hsla((0.5 + index)/stackRepeater.count, 0.3, 0.7, 1)
Button { anchors.centerIn: parent; text: "Page " + (index + 1); onClicked: { stackLayout.advance() } }
}
}
}
}
}
}
}
When I put the code in one file, it works and the ApplicationWindow does not get smaller than the minimum height and width of the child element "mainLayout". But splitting into 2 files does not work..
The reason why you are not able to use the property minimumWidth of your QML element with the id mainLayout like serial.mainLayout.minimumWidth is that it doesn't have one.
However, the QML element in question does have an attached property Layout.minimumWidth because it's an item in a ColumnLayout with the id wrapper. You already found out that you could access it through serial.mainLayout.Layout.minimumWidth.
Attached property mechanism that enables the minimumWidth for mainLayout is not the easiest one to understand. In short, it enables objects to be annotated with extra properties that are otherwise unavailable to the object but are relevant in certain circumstances. In this case minimumWidth is considered relevant for child items of ColumnLayout. Items in a ColumnLayout support these attached properties.

Spacer Item in QML Layouts

I want to create a layout in QML and I'd like to add a spacer item (the bottom selected item from the image below) just as you do using widgets like so:
But I couldn't find anything to suit this on the QtQuick side of things...is it possible to have this kind of layout in QML w/o using the anchoring system?
I'd prefer the layouts approach...
You can simply use an Item with Layout.fillHeight: true :
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
ColumnLayout {
anchors.fill: parent
Button {
Layout.fillWidth: true
text: "PushButton"
}
Button {
Layout.fillWidth: true
text: "PushButton"
}
Label {
Layout.fillWidth: true
text: "TextLabel"
}
Item {
// spacer item
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle { anchors.fill: parent; color: "#ffaaaa" } // to visualize the spacer
}
}
}
EDIT: Alternatively here, you could have used a Column with no spacer item since a Column just positions its children from top to bottom and don't spread them to take all the available space.
For those coming from Qt widgets and for comparison: the intended solution in QML for this situation is the anchoring system that the question mentions. In this case, it would look as follows, and I think it's not so bad :)
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
ColumnLayout {
// anchors.fill sets all four directional anchors.
// Loosening one yields the space at the bottom.
anchors.fill: parent
anchors.bottom: undefined
// Alternative approach: only set the three anchors we want.
// anchors.top: parent.top
// anchors.left: parent.left
// anchors.right: parent.right
Button {
Layout.fillWidth: true
text: "PushButton"
}
Button {
Layout.fillWidth: true
text: "PushButton"
}
Label {
Layout.fillWidth: true
text: "TextLabel"
}
}
}

Qt QML How to change size of a component and get the whole page change

I am working on an android application and I am facing a problem. In a page of the application I have some input fields, one of them is for date and I wanted to add a Calendar that open on demand for selecting the date or just enter the date manually, for this, I created a custom component which is composed of a TextInput and a button which when clicked will create the calendar item with a loader and set the size of the loader to 80 (it was 0 initially) all this components are included in a columnlayout. When the button get clicked the calendar is drawn below the other input fields.
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
FocusScope {
id: root
Layout.preferredHeight: 20
property alias text: input.text
property alias border: background.border
property alias backgroundColor: background.color
property alias textColor: input.color
ColumnLayout{
anchors.fill: parent
spacing: 1
RowLayout{
Layout.fillHeight: true
Layout.fillWidth: true
Rectangle {
id: background
Layout.fillHeight: true
Layout.fillWidth: true
color: "darkgrey"
TextInput {
id: input
anchors.fill: parent
anchors.margins: 3
verticalAlignment: TextInput.AlignVCenter
focus: true
text: dateInput.selectedDate
}
}
CustomButton {
id: calandar
Layout.fillHeight: true
Layout.preferredWidth: 40
image: "icons/CalandarButton.svg"
onClicked: {
console.log("clicked calandar")
if(calendarLoader.status === Loader.Null){
calendarLoader.height = 80
calendarLoader.sourceComponent = Qt.createQmlObject("import QtQuick 2.5; import QtQuick.Controls 1.4; Calendar {}",
calendarLoader,
"calandarpp")
}
else{
calendarLoader.height = 0
calendarLoader.sourceComponent = undefined
}
}
}
}
Loader {
id: calendarLoader
Layout.fillWidth: true
height: 0
}
}
}
If something is below, then try changing its z coordinate.
There is no need to do Qt.createQmlObject() ever. It's enough to toggle Loader.active or Item.visible.
Example is not reproducible, make sure that it runs by itself with qmlscene.
This works for me:
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
FocusScope {
id: root
Layout.preferredHeight: 20
property alias text: input.text
property alias border: background.border
property alias backgroundColor: background.color
property alias textColor: input.color
z: 1
Loader {
id: calendarLoader
active: false
sourceComponent: Calendar {}
z: 1
}
ColumnLayout {
anchors.fill: parent
spacing: 1
RowLayout{
Layout.fillHeight: true
Layout.fillWidth: true
Rectangle {
id: background
Layout.fillHeight: true
Layout.fillWidth: true
color: "darkgrey"
TextInput {
id: input
anchors.fill: parent
anchors.margins: 3
verticalAlignment: TextInput.AlignVCenter
focus: true
}
}
Button {
id: calandar
Layout.fillHeight: true
Layout.preferredWidth: 40
onClicked: {
console.log("clicked calandar")
calendarLoader.active = !calendarLoader.active
}
}
}
}
}

QML ListView with sections representing first letter of model's name role - sections are not visible

I have QML ListView, which shows customers from database via UeCustomerModel. Customers are sorted in model via MySQL statement. All works fine, but now I want to add sections into ListView, so the customers are sorted in sections by first letter criteria. Here is my ListView:
ListView
{
id: ueCustomersListView
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter|Qt.AlignBottom
Layout.margins: 8
clip: true
spacing: 8
delegate: UeCustomerSelectorDelegate
{
ueParamWidth: ueCustomersListView.width
ueParamHeight: 128
ueParamImage: "image://ueCustomerModel/"+model.ueRoleImage
ueParamName: model.ueRoleCustomerName
ueParamTaxId: model.ueRoleCustomerTaxId
ueParamCurrentDebt: model.ueRoleCustomerCurrDebt
ueParamMaxDebt: model.ueRoleCustomerMaxDebt
} // delegate
section.property: model.ueRoleCustomerName // HERE RUNTIME ERROR OCCURS
section.criteria: ViewSection.FirstCharacter
section.delegate: UeCustomerSelectorSectionDelegate
{
ueParamWidth: ueCustomersListView.width
ueParamHeight: 128
ueParamName: section
} // section.delegate
Component.onCompleted:
{
model=ueCustomerModel;
} // Component.onCompleted
} // ListView
and here is section delegate:
import QtQuick 2.5
import QtQuick.Layouts 1.1
Rectangle
{
property int ueParamWidth
property int ueParamHeight
property string ueParamName
width: ueParamWidth
height: ueParamHeight
color: "#4682b4"
antialiasing: true
smooth: true
RowLayout
{
anchors.fill: parent
anchors.margins: 8
spacing: 8
Text
{
color: "#ffffff"
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter|Qt.AlignVCenter
antialiasing: true
text: ueParamName
font.family: "Courier"
font.bold: true
font.pointSize: 12
clip: true
textFormat: Text.RichText
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
} // Text
} // RowLayout
} // Rectangle
Now, when ListView is shown, there are no sections visible. What did I miss, i.e., how do I correct section.property: model.ueRoleCustomerName statement to get alphabetical letters from customers (A, B, C, ...). I want ListView to section delegates according to Customer Name (delivered from database via model) first letter. Also, in mentioned line of code, I get QML runtime error Unable to assign [undefined] to QString. Why?
P.S.: model works perfectly (is fast, can search accross customers using TextField), I've tested it 5 times.
According to user sk2212's hint, I've upgraded the code with:
section.property: "ueParamName"
section.criteria: ViewSection.FirstCharacter
section.delegate: UeCustomerSelectorSectionDelegate
{
ueParamWidth: ueCustomersListView.width/2
ueParamHeight: ueCustomersListView.height/4
ueParamName: model.ueRoleCustomerName.subString(0,1)
} // section.delegate
and here is Section delegate:
import QtQuick 2.5
import QtQuick.Layouts 1.1
Rectangle
{
property int ueParamWidth
property int ueParamHeight
property string ueParamName
width: ueParamWidth
height: ueParamHeight
color: "#4682b4"
antialiasing: true
smooth: true
RowLayout
{
anchors.fill: parent
anchors.margins: 8
spacing: 8
Text
{
color: "#ffffff"
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter|Qt.AlignVCenter
antialiasing: true
text: ueParamName
font.family: "Courier"
font.bold: true
font.pointSize: 12
clip: true
textFormat: Text.RichText
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
} // Text
} // RowLayout
} // Rectangle
Still does not work.
You have to use the section.property as a model data element:
delegate: UeCustomerSelectorDelegate
{
ueParamWidth: ueCustomersListView.width
ueParamHeight: 128
ueParamImage: "image://ueCustomerModel/"+model.ueRoleImage
ueParamName: model.ueRoleCustomerName
ueParamTaxId: model.ueRoleCustomerTaxId
ueParamCurrentDebt: model.ueRoleCustomerCurrDebt
ueParamMaxDebt: model.ueRoleCustomerMaxDebt
alphabet: model.ueRoleCustomerName.substring(0,1)
} // delegate
section.property: "alphabet"

QML ListView current item not changing with keystrokes or mouse

I have a very simple ListView.
ListView {
id: logListView
anchors.fill: parent
model: LogEntryListModel
delegate:
Text {
text: "Log Item: " + timestamp + ", " + verb
}
highlight: Rectangle { color: "lightsteelblue"; radius: 5 }
focus: true
clip: true
}
It shows the model fine and highlights the first item. It does not move the highlight when I click on another item nor when I use the arrow keys. I know how to control the highlighted item manually by adding event handlers but I see references in the docs to automatic handling of the selectedItem. I was wondering:
Does QML provide an automatic changing of the selected item highlighting? What do I need to add to turn it on?
The keyboard handling is done automatically:
import QtQuick 2.0
import QtQuick.Controls 1.1
Rectangle {
width: 400
height: 400
ListView {
id: logListView
anchors.fill: parent
model: 10
delegate: Text {
text: "Log Item: " + modelData
}
highlight: Rectangle {
color: "lightsteelblue";
radius: 5
}
focus: true
clip: true
}
}
If using the up and down arrow keys does not change the selected item for you, using the code above, then it's a bug.
Using a mouse to select items is not handled by default, however; only flicking/dragging of the list is. It's easy to add, though:
import QtQuick 2.0
import QtQuick.Controls 1.1
Rectangle {
width: 400
height: 400
ListView {
id: logListView
anchors.fill: parent
model: 10
delegate: Text {
text: "Log Item: " + modelData
MouseArea {
anchors.fill: parent
onClicked: logListView.currentIndex = index
}
}
highlight: Rectangle {
color: "lightsteelblue";
radius: 5
}
focus: true
clip: true
}
}

Resources