QML grid specify position manually - qt

Can I set the column and row of QML Grid children elements manually? In this case, I'd like the Button to be in row 11 column 1.
Grid {
rows: 11
columns: 4
Button {
height: 128
width: 128
text: qsTr("Sahtel lahti")
}
}

As already mentioned in the comments you can use GridLayout and Layout.row and Layout.column.
import QtQuick 2.13
import QtQuick.Layouts 1.13
GridLayout {
columns: 3
Text { text: "4"; Layout.row: 1; Layout.column: 0}
Text { text: "3"; Layout.row: 0; Layout.column: 2}
Text { text: "2"; Layout.row: 0; Layout.column: 1}
Text { text: "1"; Layout.row: 0; Layout.column: 0}
}

Related

How to have a sticky header for a GridLayout in a Flickable

I have a GridLayout populated by a Repeater (a TableView doesn't fit my needs), inside a Flickable so I can scroll the content.
I want my GridLayout to have a header, which I can easily by adding Texts before my Repeater like this:
import QtQuick 2.0
import QtQuick.Layouts 1.12
ColumnLayout {
width: 200
height: 200
Flickable {
Layout.fillWidth: true
Layout.preferredHeight: 200
contentWidth: width
contentHeight: grid.height
clip: true
GridLayout {
id: grid
columns: 3
columnSpacing: 10
function reparentChildren(source, target) {
while (source.children.length) {
source.children[0].parent = target;
}
}
// Header
Text {
text: "Header 0"
}
Text {
text: "Header 1"
}
Text {
text: "Header 2"
}
// Data
Repeater {
model: 50
Item {
Component.onCompleted: grid.reparentChildren(this, grid)
Text {
text: "Column 0, %1".arg(modelData)
}
Text {
text: "Column 1, %1".arg(modelData)
}
Text {
text: "Column 2, %1".arg(modelData)
}
}
}
}
}
}
However I would like my header to be "sticky" / "frozen", i.e. remain visible when scrolling the Flickable.
I could create my header outside the Flickable, however the GridLayout doesn't give me the final row sizes, so I cannot position my header texts.
It's a bit hacky, but I found a solution.
First, create "dummy" headers which are Items. You can make set their Layout.minimalWidth to be the width of the header text if you need to.
Then, in a Item before the Flickable, create your header, make sure it is horizontally aligned with the grid, and position the elements using the x values of the header.
Finally, set a negative margin on the Flickable to remove the extraneous row added by the dummy headers.
I also tried using fillWidth: true on the dummies and then setting the width of each header items, but the downside is that it modifies the table column width.
import QtQuick 2.0
import QtQuick.Layouts 1.12
ColumnLayout {
width: 200
height: 200
// Header
Item {
Layout.minimumHeight: childrenRect.height
Layout.fillWidth: true
Text {
id: header0
x: headerDummy0.x
anchors.top: parent.top
text: "Header 0"
}
Text {
id: header1
x: headerDummy1.x
anchors.top: parent.top
text: "Header 1"
}
Text {
id: header2
x: headerDummy2.x
anchors.top: parent.top
text: "Header 2"
}
}
Flickable {
Layout.fillWidth: true
Layout.preferredHeight: 200
contentWidth: width
contentHeight: grid.height
clip: true
// Eliminate the first row, which are the dummies
topMargin: - grid.rowSpacing
GridLayout {
id: grid
columns: 3
rowSpacing: 50
columnSpacing: 10
function reparentChildren(source, target) {
while (source.children.length) {
source.children[0].parent = target;
}
}
// Header dummies
Item {
id: headerDummy0
Layout.minimumWidth: header0.implicitWidth
}
Item {
id: headerDummy1
Layout.minimumWidth: header1.implicitWidth
}
Item {
id: headerDummy2
Layout.minimumWidth: header2.implicitWidth
}
// Data
Repeater {
model: 50
Item {
Component.onCompleted: grid.reparentChildren(this, grid)
Text {
text: "Column 0, %1".arg(modelData)
}
Text {
text: "Column 1, %1".arg(modelData)
}
Text {
text: "Column 2, %1".arg(modelData)
}
}
}
}
}
}

QML Repeater and QML Grid Layout with variable number of columns

I have an model that consist of objects with some properties, e.g.:
ListModel {
id: fruitModel
ListElement {
name: "Apple"
color: "green"
cost: 2.45
}
ListElement {
name: "Orange"
color: "orange"
cost: 3.25
}
ListElement {
name: "Banana"
color: "yellow"
cost: 1.95
}
}
And now I want to display this model using GridLayout. For each property I want to use one element inside GridLayout, e.g:
GridLayout {
columns: 3
Text { text: "Apple" }
Rectangle { color: "green" }
SpinBox { value: 2.45 }
Text { text: "Orange" }
Rectangle { color: "orange" }
SpinBox { value: 3.25 }
Text { text: "Banana" }
Rectangle { color: "yellow" }
SpinBox { value: 1.95 }
}
The point is that I can easily change the columns property of GridLayout and make my layout narrower (e.g. to fit small screens). I can use Repeater to fill GridLayout. However such approach will fill my GridLayout in wrong order:
GridLayout {
columns: 3
Repeater {
model: fruitModel
Text { text: name }
}
Repeater {
model: fruitModel
Rectangle { color: color }
}
Repeater {
model: fruitModel
SpinBox { value: value }
}
}
And it is waste to use Layout.column and Layout.row attached properties since I want to easily change number of columns in GridLayout.
Is there any way to fill GridLayout with data from model row by row?
UPD1:
Behaviour that I want to get:
GridLayout {
columns: parent.width > 235 ? 3 : 1
Text { text: "Apple" }
Rectangle { color: "green"; width: 40; height: 40 }
SpinBox { value: 2 }
Text { text: "Orange" }
Rectangle { color: "orange"; width: 40; height: 40 }
SpinBox { value: 3 }
Text { text: "Banana" }
Rectangle { color: "yellow"; width: 40; height: 40 }
SpinBox { value: 1 }
}
UPD2:
Modified variant from #m7913d:
GridLayout {
id: layout
property int maxColumns: 3
columns: parent.width > 235 ? maxColumns : 1
Repeater {
model: fruitModel
Text {
text: name
Layout.row: layout.columns == maxColumns ? index : (maxColumns * index)
Layout.column: 0
}
}
Repeater {
model: fruitModel
Rectangle {
Layout.preferredWidth: 30
Layout.preferredHeight: 30
color: col
Layout.row: layout.columns == maxColumns ? index : (maxColumns * index + 1)
Layout.column: layout.columns == maxColumns ? 1 : 0
}
}
Repeater {
model: fruitModel
SpinBox {
value: val
Layout.row: layout.columns == maxColumns ? index : (maxColumns * index + 2)
Layout.column: layout.columns == maxColumns ? 2 : 0
}
}
}
It's working but not easy-to-modify solution plus sometimes during layout resize there is messages QGridLayoutEngine::addItem: Cell (1, 0) already taken.
I would use a Column filled by either Rows or Columns (or any other arrangement) as delegates.
Column {
id: rootCol
anchors.fill: parent
Repeater {
model: fruitModel
delegate: rootCol.width > 300 ? rowDel : colDel
}
Component {
id: rowDel
Row {
Text { width: 100; height: 50; text: model.name }
Rectangle { width: 50; height: 50; color: model.color }
SpinBox { width: 150; height: 50; value: model.cost }
}
}
Component {
id: colDel
Column {
Text { width: 100; height: 50; text: model.name }
Rectangle { width: 50; height: 50; color: model.color }
SpinBox { width: 150; height: 50; value: model.cost}
}
}
}
Or maybe:
Column {
id: rootCol
anchors.fill: parent
Repeater {
model: fruitModel
delegate: Flow {
anchors {
left: parent.left
right: parent.right
}
Text { width: 100; height: 50; text: model.name }
Rectangle { width: 50; height: 50; color: model.color }
SpinBox { width: 150; height: 50; value: model.value }
}
}
}
Here is yet another solution:
The Idea: add the children in the right order
For that we will need to make sure that all Items for a model entry.
GridLayout {
id: gl
columns: parent.width > 235 ? 3 : 1
}
Instantiator {
model: fruitModel
delegate: QtObject {
property Item text: Text { parent: gl; text: model.name }
property Item rect: Rectangle { parent: gl; color: model.color; width: 50; height: 50; }
property Item spin: SpinBox { parent: gl; value: model.cost; }
}
}
Note: This will fail when entries in the model will be inserted or reordered, as in this solution the entries are always appended.
Other than that, it will automatically support column changes
A possible approach is to extend this method with condition column/row indexes:
GridLayout {
id:gridLayout
columns: parent.width > 235 ? 3 : 1
Repeater {
model: fruitModel
implicitHeight: 50
Text {
text: name
Layout.row: gridLayout.columns == 3 ? index : 3 * index
Layout.column: 0
}
}
Repeater {
model: fruitModel
Rectangle {
color: model.color
implicitWidth: 50
implicitHeight: 50
Layout.row: gridLayout.columns == 3 ? index : 3 * index + 1
Layout.column: gridLayout.columns == 3 ? 1 : 0
}
}
Repeater {
model: fruitModel
SpinBox {
value: value
Layout.row: gridLayout.columns == 3 ? index : 3 * index + 2
Layout.column: gridLayout.columns == 3 ? 2 : 0
}
}
}

QML slider tickmark with text at start and end

How to achieve something like this.
Should the text thin and thick must be outside slider as labels or can they be part of tickmarks?
That can be easily done with styles. I advice you to look at QML controls/styles source in $QTHOME/qml/QtQuick/Controls[/Styles/Base] to have an understanding of default styles of QML controls.
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.4
Window {
id: rootWindow
visible: true
height: 800
width: 800
Rectangle {
width: 350
height: 100
color: "#555"
anchors.centerIn: parent
Slider {
anchors.centerIn: parent
minimumValue: 1
maximumValue: 5
stepSize: 1
tickmarksEnabled: true
width: 300
style: SliderStyle {
handle: Rectangle {
width: 18
height: 30
border.width: 2
border.color: "#555"
color: "#CCC"
radius: 5
}
groove: Rectangle {
height: 15
width: parent.width
anchors.verticalCenter: parent.verticalCenter
color: "#CCC"
}
tickmarks: Repeater {
id: repeater
model: control.stepSize > 0 ? 1 + (control.maximumValue - control.minimumValue) / control.stepSize : 0
Item {
Text {
y: repeater.height + 5
width : repeater.width / repeater.count
x: width * index
height: 30
color: "#CCC"
font.pixelSize: 20
text: getText()
horizontalAlignment: getAlign()
function getText() {
if(index === 0) return "THIN"
else if(index === repeater.count - 1) return "THICK"
else return "";
}
function getAlign() {
if(index === "0") return Text.AlignLeft
else if(index === repeater.count - 1) return Text.AlignRight
else return Text.AlignHCenter;
}
}
Rectangle {
color: "#CCC"
width: 2 ; height: 5
y: repeater.height
x: styleData.handleWidth / 2 + index * ((repeater.width - styleData.handleWidth) / (repeater.count-1))
}
}
}
}
}
}
}
The exampe is full of excessive and worthless code but that's good for understanding.
It doesn't seem like they can be a part of the tick marks, but you can easily achieve this with separate text labels:
Slider {
id: slide
width: 200
}
Text {
text: "THIN"
anchors.top: slide.bottom
anchors.left: slide.left
}
Text {
text: "THICK"
anchors.top: slide.bottom
anchors.right: slide.right
}

How to move a cell in GridLayout - QML?

Considering the documentation of GridLayout, here is what I have tried:
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
Window
{
visible: true
MainForm
{
GridLayout {
id: gridLayout
columns: 3
height: 100
width: 100
property int oneRow: 0
property int oneCol: 0
Rectangle { id: one; Layout.row :gridLayout.oneRow; Layout.column: gridLayout.oneCol;
height: 50; width: 50; color: "brown"}
Rectangle { height: 50; width: 50; color: "red" }
Rectangle { height: 50; width: 50; color: "blue"}
Rectangle { height: 50; width: 50; color: "green"}
Rectangle { height: 50; width: 50; color: "yellow"}
}
Component.onCompleted:
{
gridLayout.oneRow = 2
gridLayout.oneCol = 2
}
}
}
If I comment out this code from Component.onCompleted,
gridLayout.oneRow = 2
gridLayout.oneCol = 2
I get:
Whereas I want that brown square to "move to" the second row's last column.
So, I wrote:
gridLayout.oneRow = 1
gridLayout.oneCol = 2
in Component.onCompleted.
but then, I got the following:
which is NOT what I wanted.
Please help.
If you wish to change the cell number of some item in the GridLayout, then you need to assign the initial row number and column number to all the elements _yourself_, and then change the position of the desired item dynamically as shown below:
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
Window
{
visible: true
MainForm
{
GridLayout {
id: gridLayout
height: 100
width: 100
property int oneRow: 0
property int oneCol: 0
Rectangle { id: one;
Layout.row :gridLayout.oneRow; Layout.column: gridLayout.oneCol;
height: 50; width: 50; color: "brown"}
property int twoRow: 0
property int twoCol: 1
Rectangle { id: two;
Layout.row :gridLayout.twoRow; Layout.column: gridLayout.twoCol;
height: 50; width: 50; color: "red" }
property int threeRow: 0
property int threeCol: 2
Rectangle { id: three;
Layout.row :gridLayout.threeRow; Layout.column: gridLayout.threeCol;
height: 50; width: 50; color: "blue"}
property int fourRow: 1
property int fourCol: 0
Rectangle { id: four;
Layout.row :gridLayout.fourRow; Layout.column: gridLayout.fourCol;
height: 50; width: 50; color: "green"}
property int fiveRow: 1
property int fiveCol: 1
Rectangle { id: five;
Layout.row :gridLayout.fiveRow; Layout.column: gridLayout.fiveCol;
height: 50; width: 50; color: "yellow"}
}
Component.onCompleted:
{
gridLayout.oneRow = 1
gridLayout.oneCol = 2
}
}
}
Now, as you can see the following code in Component.onCompleted moves the brown rectangle to the 2nd row's 3rd column.
Component.onCompleted:
{
gridLayout.oneRow = 1
gridLayout.oneCol = 2
}

QML Repeater for multiple items without a wrapping Item

I want to make a view that looks something like (I left off a column for brevity)
__________
| text |
|__________|
| |headr|
|____|_____|
|text|item1|
| |item2|
| | |
|text|item3|
| |item4|
| |item5|
| |item6|
|____|_____|
and have been trying to do this with a GridLayout. The problem is that I could have potentially many rows. It just depends on my model. So, I want to be able to have a repeater that will repeat the same n elements. However, it seems to only take one component. I would love to repeat one element but that is not how the GridLayout figures out spacing. So, it seems as if this is impossible except using dynamic object creation.
My code for the actual item trying to be repeated is this
Text {
Layout.alignment: Qt.AlignHCenter
text: abbr
color: "#545454"
}
VerticalRule {
Layout.fillHeight: true
}
ColumnLayout {
Repeater {
model: getModel()
Image {}
}
}
VerticalRule {
Layout.fillHeight: true
}
ColumnLayout {
Repeater {
model: getModel()
Image {}
}
}
So, is there any way to do this easily in qml or am I kind of on my own when it comes to this kind of super specific table ish format.
You can do it with the GridLayout; use multiple Repeaters, one for each column in the Gridlayout. The following snippet reproduces your text layout:
GridLayout {
columns: 2
Text {
Layout.columnSpan: 2
Layout.alignment: Qt.AlignLeft
text: 'text'
}
Rectangle {
height: 2
color: "black"
Layout.row: 1
Layout.columnSpan: 2
Layout.fillWidth: true
}
Text {
Layout.column: 1
Layout.row: 2
text: 'header'
}
Rectangle {
height: 2
color: "black"
Layout.row: 3
Layout.columnSpan: 2
Layout.fillWidth: true
}
Repeater {
model: list
Text {
Layout.column: 0
Layout.row: index + 4
Layout.alignment: Qt.AlignTop
text: label
}
}
Repeater {
model: list
Column {
Layout.column: 1
Layout.row: index + 4
Repeater {
model: items
Text { text: item }
}
}
}
ListModel {
id: list
ListElement {
label: 'text1'
items: [
ListElement { item: 'item1' },
ListElement { item: 'item2' }
]
}
ListElement {
label: 'text2'
items: [
ListElement { item: 'item3' },
ListElement { item: 'item4' },
ListElement { item: 'item5' },
ListElement { item: 'item6' },
ListElement { item: 'item7' }
]
}
ListElement {
label: 'text3'
items: [
ListElement { item: 'item8' },
ListElement { item: 'item9' }
]
}
}
}
Unless I find a way, You cant. You can use a ListView to make rows that they themselves have GridViews .... I think.
Add multiple Items as children to a delegate Item and use JavaScript to reparent them to the GridLayout when the delegate Item is completed.
The OP requests a solution that avoids using a wrapping Item, but a wrapping Item affords the cleanest solution to this problem and supports an arbitrary number of columns. Also, I have never seen a solution using a "wrapping Item" -- this is the page that comes up whenever I search for one.
GridLayout {
id: grid
anchors.centerIn: parent
columns: 2
Repeater {
model: [
{ column1: "text1", column2: ["item1","item2"] },
{ column1: "text2", column2: ["item3","item4","item5","item6","item7"] },
{ column1: "text3", column2: ["item8","item9"] },
]
delegate: Item {
objectName: "wrapping_item"
property int rowOffset: 4
Component.onCompleted: {
// reparent all child elements into the grid
while (children.length)
children[0].parent = grid;
}
Text {
objectName: "first_child"
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.column: 0
Layout.row: index + rowOffset
text: modelData.column1
}
Column {
objectName: "second_child_containing_more_children"
Layout.alignment: Qt.AlignLeft
Layout.column: 1
Layout.row: index + rowOffset
Repeater {
model: modelData.column2
delegate: Text { text: modelData }
}
}
}
}
// same header code as in other answer, just moved out of the way
Text {
text: "text"
Layout.columnSpan: 2
}
Rectangle {
height: 2
color: "blue"
Layout.row: 1
Layout.columnSpan: 2
Layout.fillWidth: true
}
Text {
text: "header"
Layout.row: 2
Layout.column: 1
}
Rectangle {
height: 2
color: "blue"
Layout.row: 3
Layout.columnSpan: 2
Layout.fillWidth: true
}
}

Resources