Solved Centering GridView inside of a window
-
Hello,
I am writing an application, that tries to imitate Windows metro (is it still called like that?). Anyways, now I want the home screen of the app to look like the one in Windows 10 settings (the modern one). Yes, I use my PC in the Czech language and no, I don't normally use light mode :).It doesn't look particularly nice but in the future, I will do proper styling. Also, I don't want a search bar, to list the differences. The thing I have a problem with, is the layout of the buttons.
Here is the source code, which I describe below.
import QtQuick 2.2 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.0 import QtQuick.Controls.Universal 2.3 ApplicationWindow { visible: true width: 800 height: 600 title: qsTr("Windows Configurator") Universal.theme: Universal.System Universal.accent: Universal.Cobalt StackView { id: stack anchors.fill: parent initialItem: homeGrid Pane { id: home anchors.fill: parent property var cellHeight: 100 property var cellWidth: 240 property var maxPerLine: homeGrid.model.count property var minWidthToSwitch: 0 onWidthChanged: { var margin = 0; if(home.width < cellWidth) { margin = 0; } else if(home.width / cellWidth < maxPerLine) { margin = home.width % cellWidth; } else { margin = home.width - cellWidth * maxPerLine; } homeGrid.anchors.leftMargin = margin / 2; homeGrid.anchors.rightMargin = margin / 2; } GridView { id: homeGrid anchors.fill: parent cellHeight: home.cellHeight cellWidth: home.cellWidth boundsBehavior: Flickable.StopAtBounds delegate: Item { height: GridView.view.cellHeight width: GridView.view.cellWidth Button { height: parent.height * 0.8 width: parent.width * 0.8 anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter text: model.title onClicked: { stack.push(Qt.resolvedUrl(page)); } } } model: ListModel { ListElement { title: "Registry" page: "qrc:/Registry.qml" } ListElement { title: "Programs" page: "qrc:/Programs.qml" } ListElement { title: "About" page: "qrc:/About.qml" } } } } } }
To layout the buttons, I use GridView to which I give a list of all buttons to show. The problem comes, when I want to center the buttons inside the window. For this, I made a Pane (id:pane) which contains only the GridView and every time its width changes, it changes margins of the GridView to center it. I don't like this solution and think it is not the best either but it is the only thing I was able to get working.
The problem, however, comes when I push new content to the StackView. Everything seems to work fine but I get a message in the console saying:
qrc:/Main.qml:46:13: QML GridView: StackView has detected conflicting anchors. Transitions may not execute properly.
It is pointing on the line opening the GridView. When I delete the line in GridViewanchors.fill: parent
, the message doesn't pop up anymore but the buttons are not centered anymore.When I try to add padding inside the pane (I just replace
homeGrid.anchors.leftMargin = margin / 2; homeGrid.anchors.rightMargin = margin / 2;
with the respective paddings), I get yet another error message
qrc:/Main.qml:20:9: QML Pane: Binding loop detected for property "implicitWidth"
this time pointing on the line opening the Pane (id: pane) and the items are not centered again (probably because the padding was not applied because of the error?).The questions I have are:
Is there a better way to implement the layout I want?
What is the error with conflicting anchors caused by?Thank you for any advice
-
@tom_h Thank you very much.
I triedanchors.centerIn: parent
before and it did not work probably because I set the wrong initial item 🤦. It probably also was the cause of the anchor conflict I mentioned in the first post.The code below is the final outcome and the solution is definitely much better than using anchors+margins. I still ended up wrapping the GridView in a Pane, as I will need the onWidthChanged signal in the future and want it to be separate from the StackView. Also, the only way I found that allowed me to actually center the grid was to change its width manually to nearest lower multiple of cellWidth because otherwise it just filled all the space available and started from left.
Pane { id: home anchors.fill: parent property var cellHeight: 100 property var cellWidth: 240 property var maxPerLine: homeGrid.model.count property var minWidthToSwitch: 0 onWidthChanged: { if(width < cellWidth) { homeGrid.width = width; } else if(width / cellWidth < maxPerLine) { homeGrid.width = Math.floor(width / cellWidth) * cellWidth; } else { homeGrid.width = cellWidth * maxPerLine; } } GridView { id: homeGrid anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.bottom: parent.bottom cellHeight: home.cellHeight cellWidth: home.cellWidth boundsBehavior: Flickable.StopAtBounds delegate: Item { height: GridView.view.cellHeight width: GridView.view.cellWidth Button { height: parent.height * 0.8 width: parent.width * 0.8 anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter text: model.title onClicked: { stack.push(Qt.resolvedUrl(page)); } } } model: ListModel { ListElement { title: "Registry" page: "qrc:/Registry.qml" } ListElement { title: "Programs" page: "qrc:/Programs.qml" } ListElement { title: "About" page: "qrc:/About.qml" } } } }
EDIT: I got the conflicting anchors thing again but removing
anchors.fill: parent
from the pane did not break anything and solved the problem. -
@cubicap Instead of doing it manually you should use layouts: https://doc.qt.io/qt-5/qtquicklayouts-index.html
-
Thank you.
Unfortunately, I am not able to make it work either.This is the new code:
ColumnLayout { id: home anchors.fill: parent property var cellHeight: 100 property var cellWidth: 240 property var maxPerLine: homeGrid.model.count GridView { id: homeGrid Layout.alignment: Qt.AlignHCenter cellHeight: home.cellHeight cellWidth: home.cellWidth boundsBehavior: Flickable.StopAtBounds onWidthChanged: { console.log(width); } delegate: Item { height: GridView.view.cellHeight width: GridView.view.cellWidth Button { height: parent.height * 0.8 width: parent.width * 0.8 anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter text: model.title onClicked: { stack.push(Qt.resolvedUrl(page)); } } } model: ListModel { ListElement { title: "Registry" page: "qrc:/Registry.qml" } ListElement { title: "Programs" page: "qrc:/Programs.qml" } ListElement { title: "About" page: "qrc:/About.qml" } } } }
I replaced the Pane by a ColumnLayout, hoping, I could use
Layout.alignment: Qt.AlignHCenter
to center the grid inside of a window. I don't know why but the grid keeps resizing with the layout and so I can not align it as it already fills the whole space. I am not even able to set the width of the grid to be constant, it just sets, triggers event on the new width and imediatelly resets back to the width of the window.
I see that when I put just a few buttons into the layout, they are centered properly but in this case, it does not.I also tried to put the buttons inside of a GridLayout but it did not line wrap and it also stretched to fill the whole window which is not a thing I expect. I want the buttons to have constant spacing, constant dimensions and wrap to the next line, if necessary. And indeed to be centered.
-
@cubicap In your first example, you have
initialItem: homeGrid
. It should beinitialItem: home
. But you don't need the Pane, you can just put the GridView in the StackView directly. Likewise, in the second example, you don't need the ColumnLayout.You can also center the GridView with
anchors.centerIn: parent
. -
@tom_h Thank you very much.
I triedanchors.centerIn: parent
before and it did not work probably because I set the wrong initial item 🤦. It probably also was the cause of the anchor conflict I mentioned in the first post.The code below is the final outcome and the solution is definitely much better than using anchors+margins. I still ended up wrapping the GridView in a Pane, as I will need the onWidthChanged signal in the future and want it to be separate from the StackView. Also, the only way I found that allowed me to actually center the grid was to change its width manually to nearest lower multiple of cellWidth because otherwise it just filled all the space available and started from left.
Pane { id: home anchors.fill: parent property var cellHeight: 100 property var cellWidth: 240 property var maxPerLine: homeGrid.model.count property var minWidthToSwitch: 0 onWidthChanged: { if(width < cellWidth) { homeGrid.width = width; } else if(width / cellWidth < maxPerLine) { homeGrid.width = Math.floor(width / cellWidth) * cellWidth; } else { homeGrid.width = cellWidth * maxPerLine; } } GridView { id: homeGrid anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.bottom: parent.bottom cellHeight: home.cellHeight cellWidth: home.cellWidth boundsBehavior: Flickable.StopAtBounds delegate: Item { height: GridView.view.cellHeight width: GridView.view.cellWidth Button { height: parent.height * 0.8 width: parent.width * 0.8 anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter text: model.title onClicked: { stack.push(Qt.resolvedUrl(page)); } } } model: ListModel { ListElement { title: "Registry" page: "qrc:/Registry.qml" } ListElement { title: "Programs" page: "qrc:/Programs.qml" } ListElement { title: "About" page: "qrc:/About.qml" } } } }
EDIT: I got the conflicting anchors thing again but removing
anchors.fill: parent
from the pane did not break anything and solved the problem.