QML Grid remove spacing if column is not visible - qt

I have a Grid that has a layout with 4 rows and 2 columns like this:
import QtQuick 2.9
import QtQuick.Controls 2.2
Item {
id: page
Grid {
property int buttonSize: height * 0.2
property int spacingSize: height * 0.04
id: grid
height: parent.height
spacing: spacingSize
anchors.horizontalCenter: parent.horizontalCenter
topPadding: spacingSize
flow: Grid.TopToBottom
columns: 2
rows: 4
Rectangle {
color: "blue"
height: grid.buttonSize
width: grid.buttonSize
visible: true //this comes from c++, not always true
}
Rectangle {
color: "blue"
height: grid.buttonSize
width: grid.buttonSize
visible: true //this comes from c++, not always true
}
Rectangle {
color: "blue"
height: grid.buttonSize
width: grid.buttonSize
visible: true //this comes from c++, not always true
}
Rectangle {
color: "blue"
height: grid.buttonSize
width: grid.buttonSize
visible: true //this comes from c++, not always true
}
Rectangle {
color: "blue"
height: grid.buttonSize
width: grid.buttonSize
visible: true //this comes from c++, not always true
}
}
}
If I have 5+ visible elements (2 columns are drawn), everything works fine, but if I have 4 or less items visible (1 column is drawn), the grid keeps the column spacing visible and the elements are not centered.
How can I fix this? Thank you

The solution was simple, just remove the number of columns. This will make the Grid create columns only when necessary.

Related

How to change the position when the parent is resized?

How do I properly change the x, y of an object so that it changes its position when the parent is resized? There is, I will introduce that if I drag the rectangle to the middle, then when the window is resized, it should remain in the middle. (middle for example only, rectangle can be moved freely)
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
Window {
visible: true
width: 640
height: 480
onWidthChanged: {
block.x -= block.previousWidth - width
block.previousWidth = width
}
onHeightChanged: {
block.y -= block.previousHeight - height
block.previousHeight = height
}
Rectangle {
id: block
color: "red"
width: 50
height:50
x: 100
y: 50
property int previousWidth: 0
property int previousHeight:0
Component.onCompleted: {
previousWidth = parent.width
previousHeight = parent.height
}
MouseArea {
anchors.fill: parent
drag.target: block
}
}
}
I must admit, at first I was not impressed by the question. However, when I thought about it, it represents a very interesting and valid use case. So I would be happy to provide a solution.
Solution
I would approach the problem like this:
Make the frame a child of the background image.
Instead of manually calculating the coordinates, use Item.scale to scale the image, effectively preserving the relative position of the frame with regard to the image.
Example
Here is an example I have prepared for you to demonstrate how the proposed solution could be implemented:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
visible: true
width: 640
height: 480
Image {
anchors.centerIn: parent
source: "alphabet.png"
scale: parent.width/sourceSize.width
Rectangle {
id: frame
width: parent.width/7
height: parent.height/4
border.color: "black"
color: "transparent"
antialiasing: true
MouseArea {
anchors.fill: parent
drag.target: parent
}
}
}
}
Result
The example produces the following result:
Original window
Resized window
The frame is moved
The window is resized again
As I said in my comment, the best solution is anchoring, for example:
Window {
id: root
width: 600
height: 400
title: qsTr("Parent window")
visible: true
flags: Qt.WindowStaysOnTopHint
Grid {
anchors.fill: parent
Repeater {
model: 16
Rectangle {
width: root.width / 4
height: root.height / 4
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1)
}
}
}
Rectangle {
border {
width: 5
color: "black"
}
color: "transparent"
width: root.width / 4
height: root.height / 4
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.leftMargin: root.width / 4
anchors.bottomMargin: root.height / 4
}
}

Scroll two or more List views in QML

I need to scroll two or more list view at once using a single scrollBar. Initially, i used Column inside a Flickable but scroll was not happening as expected. Later, I used ListView and even that was not scrolling correctly.
So how to scroll a listview/layout content item with a scroll bar? Should I use ScrollView or Flickable or something else?
The stock scrollbar will only hook to a single scrollable item. However, it is trivial to make a custom scroller and hook multiple views to it:
Row {
Flickable {
width: 50
height: main.height
contentHeight: contentItem.childrenRect.height
interactive: false
contentY: (contentHeight - height) * scroller.position
Column {
spacing: 5
Repeater {
model: 20
delegate: Rectangle {
width: 50
height: 50
color: "red"
Text {
anchors.centerIn: parent
text: index
}
}
}
}
}
Flickable {
width: 50
height: main.height
contentHeight: contentItem.childrenRect.height
interactive: false
contentY: (contentHeight - height) * scroller.position
Column {
spacing: 5
Repeater {
model: 30
delegate: Rectangle {
width: 50
height: 50
color: "cyan"
Text {
anchors.centerIn: parent
text: index
}
}
}
}
}
Rectangle {
id: scroller
width: 50
height: 50
color: "grey"
property real position: y / (main.height - 50)
MouseArea {
anchors.fill: parent
drag.target: parent
drag.minimumY: 0
drag.maximumY: main.height - 50
drag.axis: Drag.YAxis
}
}
}
Note that it will work adequately even if the the views are of different content height, scrolling each view relative to the scroller position:
Realizing the question was not put that well, just in case someone wants to actually scroll multiple views at the same time comes around, I will nonetheless share another interesting approach similar to a jog wheel, something that can go indefinitely in every direction rather than having a limited range like a scrollbar. This solution will scroll the two views in sync until they hit the extent of their ranges. Unlike GrecKo's answer, this never leaves you with an "empty view" when the view size is different:
Row {
Flickable {
id: f1
width: 50
height: main.height
contentHeight: contentItem.childrenRect.height
interactive: false
Connections {
target: jogger
onScroll: f1.contentY = Math.max(0, Math.min(f1.contentHeight - f1.height, f1.contentY + p))
}
Column {
spacing: 5
Repeater {
model: 20
delegate: Rectangle {
width: 50
height: 50
color: "red"
Text {
anchors.centerIn: parent
text: index
}
}
}
}
}
Flickable {
id: f2
width: 50
height: main.height
contentHeight: contentItem.childrenRect.height
interactive: false
Connections {
target: jogger
onScroll: f2.contentY = Math.max(0, Math.min(f2.contentHeight - f2.height, f2.contentY + p))
}
Column {
spacing: 5
Repeater {
model: 30
delegate: Rectangle {
width: 50
height: 50
color: "cyan"
Text {
anchors.centerIn: parent
text: index
}
}
}
}
}
MouseArea {
id: jogger
width: 50
height: main.height
drag.target: knob
drag.minimumY: 0
drag.maximumY: main.height - 50
drag.axis: Drag.YAxis
signal scroll(real p)
property real dy: 0
onPressed: dy = mouseY
onPositionChanged: {
scroll(dy - mouseY)
dy = mouseY
}
onScroll: console.log(p)
Rectangle {
anchors.fill: parent
color: "lightgrey"
}
Rectangle {
id: knob
visible: parent.pressed
width: 50
height: 50
color: "grey"
y: Math.max(0, Math.min(parent.mouseY - 25, parent.height - height))
}
}
}
Another advantage the "jog" approach has it is it not relative but absolute. That means if your view is huge, if you use a scroller even a single pixel may result in a big shift in content, whereas the jog, working in absolute mode, will always scroll the same amount of pixels regardless the content size, which is handy where precision is required.
You could just use a Flickable with your Columns.
I don't know how your Columns are laid out horizontally but if they are inside a Row it's pretty straightforward:
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Multi Column")
Flickable {
anchors.fill: parent
contentWidth: row.implicitWidth
contentHeight: row.implicitHeight
Row {
id: row
Column {
spacing: 5
Repeater {
model: 20
delegate: Rectangle {
width: 50
height: 50
color: "red"
Text {
anchors.centerIn: parent
text: index
}
}
}
}
Column {
spacing: 5
Repeater {
model: 30
delegate: Rectangle {
width: 50
height: 50
color: "cyan"
Text {
anchors.centerIn: parent
text: index
}
}
}
}
}
ScrollBar.vertical: ScrollBar { }
}
}
Even if they are not in a Row you could do :
contentHeight: Math.max(column1.height, column2.height, ...)
Demonstration :

QML GridLayout does not obey my specified cell arrangements

In this code, some items are invisible at first. I want them to be visible on the click of a button at the place where they have been placed by me.
In order to leave space for them, I have placed the other item where it will be shown when hidden options will be visible.
My problem is that GridLayout does not obey the following cell positions set in code when other items are invisible.
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
Window {
visible: true
height: 500; width: 500
GridLayout {
id: gridLayout
property bool secondScreenOptionsVisible: false
property int hmmiButtonRow: 0
property int hmmiButtonCol: 0
Rectangle {
id: hmmi; visible: gridLayout.secondScreenOptionsVisible
Layout.row: gridLayout.hmmiButtonRow; Layout.column: gridLayout.hmmiButtonCol;
height: 50; width: 50; color: "pink";
Layout.alignment: Qt.AlignTop
Text { text: "HMMI"; anchors.centerIn: parent }
}
property int optionsButtonRow: 1
property int optionsButtonCol: 0
Rectangle {
id: optionsButton; visible: gridLayout.secondScreenOptionsVisible
Layout.row: gridLayout.optionsButtonRow; Layout.column: gridLayout.optionsButtonCol;
height: 50; width: 50; color: "red"
Layout.alignment: Qt.AlignTop
Text { text: "Options..."; anchors.centerIn: parent }
}
property int flipperControlRow: 3
property int flipperControlCol: 0
Rectangle {
id: flipperControl;
Layout.row :gridLayout.flipperControlRow; Layout.column: gridLayout.flipperControlCol;
height: 200; width: 50;
color: "brown";
Layout.rowSpan: 4
Layout.alignment: Qt.AlignTop
Text { text: "Flipper"; anchors.centerIn: parent }
}
}
}
Output:
When all the items are visible:
When other two items are hidden, the GridLayout does not obey the rules.
I want GridLayout to obey the cell positions set by me, irrespective of whether other items are visible or not.
Please help.
The doc says for GridLayout that:
[...] It is similar to the widget-based QGridLayout. All visible children of the GridLayout element will belong to the layout. [...].
So what you are seeing is a direct consequence of the implementation approach followed by developers. Indeed a change in visibility triggers Items repositioning, as can be seen in this code path.
Instead of considering visible property you can use opacity property: the non-opaque Items are taken in account by the layout, resulting in the expected visible behaviour. See for instance this simple example:
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
Window {
visible: true
height: 400; width: 400
GridLayout {
anchors.fill: parent
id: gridLayout
rows: 3
columns: 3
Repeater {
id: rep
model: 9
Rectangle {
color: "black"
Layout.preferredWidth: 100
Layout.preferredHeight: 100
Layout.alignment: Qt.AlignCenter
opacity: index === rep.count - 1
}
}
}
}
Mind that non-opaque Items are still rendered, differently from invisible ones, with a varying degree of impact over performances, depending on your actual use case.

What is the difference between Row and RowLayout?

This works as intended with Row, but not with RowLayout. Why? What is the difference between the two?
ApplicationWindow {
title: "Testing"
width: 640
height: 480
//RowLayout {
Row {
anchors.fill: parent
Rectangle {
id: rect1
width: parent.width * 0.3
height: parent.height
color: "blue"
}
Rectangle {
height: parent.height
width: parent.width * 0.7
color: "red"
}
}
}
Row is a Item Positioner. Positioner items are container items that manage the positions of items in a declarative user interface.
RowLayout is part of Qt Quick Layouts. They manage both the positions and the sizes of items on a declarative user interface, and are well suited for resizable user interfaces.
Your code with RowLayout should look like this:
RowLayout{
anchors.fill: parent
spacing: 0
Rectangle{
Layout.fillHeight: true
Layout.preferredWidth: parent.width * 0.3
color: "blue"
}
Rectangle{
Layout.fillHeight: true
Layout.fillWidth: true
color: "red"
}
}
I think
Row is a container which grows the size based on the children of Row, making them line up as a row, so the children of it shall have size.
RowLayout is the wrapper of its children, giving them the ability of row positioning and resizing based on the size of RowLayout, so RowLayout shall have its own size to help the children to position and resize.

ListView isn't positioned in layout as expected

I have a ColumnLayout with a RowLayout in it. The RowLayout is positioned as expected. This is also true if the windows is being resized. Even if the windows is smaller than the entire ColumnLayout (see second image)
But if I replace the RowLayout by a (horizontal) ListView, this ListView is not positioned as I would expect. I would expect this behaves like the example with the RowLayout but the ListView is positioned higher:
And if the window gets 'to small' the blue rectangle 'moves into' the ListView unlike the first example:
How can I achieve the behaviour of the first example with a ListView?
Source
import QtQuick 2.0
import QtQuick.Layouts 1.1
Rectangle {
width: 360
height: 360
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
Item {
Layout.fillHeight: true
}
/*
ListView {
Layout.fillHeight: true
Layout.fillWidth: true
orientation: ListView.Horizontal
spacing: 5
model: 3
delegate: Rectangle {
width: 50
height: 50
color: "red"
}
}
*/
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true
Rectangle {
width: 50
height: 50
color: "red"
}
Rectangle {
width: 50
height: 50
color: "red"
}
Rectangle {
width: 50
height: 50
color: "red"
}
}
Item {
Layout.fillHeight: true
}
Rectangle {
width: 50
height: 50
color: "blue"
}
}
}
You just need to define width and height for your ListView. In that way your column layout will consider its size and keep it as a fixed size.
Here your code updated:
import QtQuick 2.0
import QtQuick.Layouts 1.1
Rectangle {
width: 360
height: 360
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
Item {
Layout.fillHeight: true
}
ListView {
//take as much width as possible with a binding
width: parent.width
//keep enought height to display each delegate instance
height: 50
orientation: ListView.Horizontal
spacing: 5
model: 3
delegate: Rectangle {
width: 50
height: 50
color: "red"
}
}
Item {
Layout.fillHeight: true
}
Rectangle {
width: 50
height: 50
color: "blue"
}
}
}

Resources