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 Czech language and no, I don't normally use light mode :).

    0_1568054229567_settings.PNG

    0_1568054233095_regset.PNG

    It looks real trash but in future I will do proper styling. Also, I want no search bar. 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 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 GridView anchors.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


  • Qt Champions 2018

    @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 be initialItem: 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 tried anchors.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.


 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.