How to design a multi-level fluid layout in QML
Asked Answered
U

3

24

I have designed a layout in QML to learn more about its features and have some questions on the "Best Practices" in designing such layout. Here it is:

enter image description here

It is essentially a ColumnLayout consisted of three RowLayouts, each one with some Rectangles. The size of each Row and Rectangle should be calculate such as:

  • First row: Height = 40%, Width = 100%
    • Red Rectangle filling the whole area
  • Second row: Height = 20%, Width = 100%
    • Dark-green Rectangle: Height = 100%, Width = 20%,
    • Light-green Rectangle: Height = 100%, Width = 80%
  • Third row: Height = 40%, Width = 100%
    • Dark-blue Rectangle: Height = 100%, Width = 40%,
    • Blue Rectangle: Height = 100%, Width = 20%
    • Light-blue Rectangle: Height = 100%, Width = 40%

The QML I have came up with is working and is in the following. I have some questions about it:

  1. I have set the width and height percentages using Layout.preferredHeight: x*parent.height pattern. Other options caused some issues (e.g. preferredHeight caused binding loop warnings). Is my approach correct and efficient?
  2. As a hack, I set Layout.fillWidth: true for the first element of Row #2 and Row #3, which doesn't make sense to me, but does work. If I set their width as percentage (e.g. Layout.preferredWidth: 0.2*parent.width) their row will collapse to width 0. Is this an expected behavior? Is there any better workaround?
  3. Do you have any recommendation on the layouts? Am I on the right path?

Here is my QML code for the layout:

ApplicationWindow {
    x: 500
    y: 100
    width: 250
    height: 150
    visible: true

    ColumnLayout {
        anchors.fill: parent
        spacing: 0
        RowLayout {
            spacing: 0
            Layout.preferredHeight: 0.4*parent.height
            Layout.fillHeight: false
            Rectangle {
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "red"
            }
        }
        RowLayout {
            spacing: 0
            Layout.preferredHeight: 0.2*parent.height
            Layout.fillHeight: false
            Rectangle {
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "darkGreen"
            }
            Rectangle {
                Layout.fillHeight: true
                Layout.preferredWidth: 0.8*parent.width
                color: "lightGreen"
            }
        }
        RowLayout {
            spacing: 0
            Layout.preferredHeight: 0.4*parent.height
            Layout.fillHeight: false
            Rectangle {
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "darkBlue"
            }
            Rectangle {
                Layout.fillHeight: true
                Layout.preferredWidth: 0.2*parent.width
                color: "blue"
            }
            Rectangle {
                Layout.fillHeight: true
                Layout.preferredWidth: 0.4*parent.width
                color: "lightBlue"
            }
        }
    }
}

Update:

My approach seems to be more hacky than I expected:

  1. Putting Text elements as children in this layout raises binding loop warnings like:

QML QQuickLayoutAttached: Binding loop detected for property "preferredWidth"

If a wrap Text inside a Rectangle the warnings disappear.

  1. The spacing: 0 seems to play an important role. Omitting it will causes binding loop warnings.

While my approach to fluid layout design in QML works, it has some serious issue and might not fall under the "best practices".

Ululate answered 2/6, 2015 at 19:18 Comment(4)
Can you show where in documentation tells about the Layout.preferredXXX ? It doesn't look like a QML property and I couldn't find in the documentation of Qt5.4.Chap
Here is the documentation for the property I am using: doc.qt.io/qt-5/qml-qtquick-layouts-layout.html. BTW, I am still getting Binding loop detected for property "preferredHeight" for more complex layouts which means my approach certainly has some flaws.Ululate
I was getting binging loop warnings on another layout which was built using this approach. Apparently the spacing: 0 plays a role in avoiding this warning. Setting it to zero caused some warnings to disappear.Ululate
Doing something more than a regular layout (without any layouts inside) in QML as far as sanity goes. After that it's all downhill. I'm currently trying to do a similar (but simpler) layout and it just...doesn't...work. Some people go with the typical Rectangle type and set width and height of each to a constant which however is unacceptable if you want to handle layouts of variable size. Working with layouts in QML makes a complex layout structure in Qt (Widgets) Designer seem like a breeze. That's says a lot about QML. The Qt Quick Designer is no better.Telles
L
5

QtQuick.Layout does not provide any real improvements over the classical anchoring system. I would recommand to avoid them. You can have way more control over your layout using anchors.

Here is the exact same design without QtQuick.Layout :

ApplicationWindow {
    x: 500
    y: 100
    width: 250
    height: 150
    visible: true

    Column {
        anchors.fill: parent

        Row {
            anchors.left: parent.left
            anchors.right: parent.right
            height: 0.4 * parent.height

            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: parent.width
                color: "red"
            }
        }

        Row {
            anchors.left: parent.left
            anchors.right: parent.right
            height: 0.2 * parent.height

            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.2 * parent.width
                color: "darkGreen"
            }

            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.8 * parent.width
                color: "lightGreen"
            }
        }

        Row {
            anchors.left: parent.left
            anchors.right: parent.right
            height: 0.4 * parent.height

            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.4 * parent.width
                color: "darkBlue"
            }
            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.2 * parent.width
                color: "blue"
            }
            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.4 * parent.width
                color: "lightBlue"
            }
        }
    }
}

So far I never met any design that was impossible to do without QtQuick.Layout.

Lashoh answered 20/10, 2016 at 10:15 Comment(5)
Nice approach! Do you have any resources that use this approach for more examples? BTW, do you see any difference between width: parent.width vs. anchors.left: parent.left; anchors.right: parent.right?Ululate
@isaac : you can look at qml example provided with qt. using anchors is also setting x and y at the same time, it also provide margins which is more intuitive than making width and height calculationLashoh
Sadly, for me this kind of solution works way more reliably than Layouts, especially in nested cases...Banter
I wouldn't agree that Layouts offer no improvement over anchoring. In my experience they require much less code to achieve the same result, result in much more readable code, and react better for resizable windows/views.Warfield
@iLikeDirt: My answer is ~4 years old, so it might be that Layouts has improved/changed since then. I don't use Qt/Qml as much nowadays, so I don't know.Lashoh
G
17

While both other answers show valid solutions, I believe both the question being asked and the two solutions somehow miss the point of using Layouts.

Basically, Layouts are made to bring together Items that have an implicit size (implicitHeight/implicitWidth). Layout.preferredWidth/Layout.preferredHeight are used to override these things in some rare situations, see below. The Qt Quick Layouts - Basic Example coming with Qt does not use Layout.preferredWidth/Layout.preferredHeight at all (!) and makes a really nice look, without contaminating the whole qml file with either anchors or Layout properties. It takes some learning to be able to do this oneself, but once you got used to it, Layouts are a way to define user interfaces more directly with less code.

What confused me the most at the beginning were the following things:

  • RowLayout/ColumnLayout/GridLayout come with Layout.fillWidth/Layout.fillHeight set to true, so when putting these near an Item/Rectangle then the Items/Rectangles suddenly disappear, because they don't have set these values (i.e. they have Layout.fillWidth/Layout.fillHeight set to false).
  • Items/Rectangles come with an implicitHeight/implicitWidth of 0, meaning they don't really play nice side-by-side with Layouts. The best thing to do is to derive implicitWidth/implicitHeight from contained subitems, like a RowLayout/ColumnLayout itself does by default for its subitems.
  • Layout.preferredWidth/Layout.preferredHeight can be used to overcome implicit sizes where they are already defined and cannot be set. One such place is directly in a layout item, another is e.g. a Text item which also doesn't let you override implicit sizes.

Considering these points, I would write the example in the following way. I removed unnecessary items to better illustrate when Layout.fillWidth/Layout.fillHeight are needed, and when it is better to use implicitWidth in my opinion.

import QtQuick 2.9
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3

ApplicationWindow {
    width: 250
    height: 150
    visible: true

    ColumnLayout {
        spacing: 0
        anchors.fill: parent
        Rectangle {
            implicitHeight: 40
            Layout.fillHeight: true
            Layout.fillWidth: true
            color: "red"
        }
        RowLayout {
            spacing: 0
            Layout.preferredHeight: 20
            Rectangle {
                implicitWidth: 20
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "darkGreen"
            }
            Rectangle {
                implicitWidth: 80
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "lightGreen"
            }
        }
        RowLayout {
            spacing: 0
            Layout.preferredHeight: 40
            Rectangle {
                implicitWidth: 40
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "darkBlue"
            }
            Rectangle {
                implicitWidth: 20
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "blue"
            }
            Rectangle {
                implicitWidth: 40
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "lightBlue"
            }
        }
    }
}
Getter answered 11/10, 2018 at 7:22 Comment(2)
Hi. How does QML make this resizable? I thought since you are using absolute values resizing the component should lose its proportions but it doesn't. Is it because all components having the fillSize property the QML engine gives them space in proportion to their original size?Hedvige
QML uses implicitHeight/implicitWidth/preferredWidth/preferredHeight as reference for resizing or calculating the relevant sizes inside layouts. And because of that, it is no problem when height/width change on a resize: The relevant information for calculating the sizes in implicit/preferred properties remains unchanged.Getter
G
10

It is forbidden (and unnecessary) to try and reference width and height of the parent from Items inside the Layout.

When fillWidth (or fillHeight) is set to true, then Items are allocated space in proportion to their specified preferredWidth (or preferredHeight).

Therefore the correct way to create your Layout is as follows. I have modified the appearance only to show that spacing and Text can also be set freely as desired. No binding loops.

enter image description here enter image description here

ApplicationWindow {
    x: 500
    y: 100
    width: 250
    height: 150
    visible: true

    ColumnLayout {
        anchors.fill: parent
        spacing: 5
        RowLayout {
            spacing: 5
            Layout.preferredHeight: 40
            Layout.fillHeight: true
            Rectangle {
                Layout.fillHeight: true
                Layout.fillWidth: true
                color: "red"
            }
        }
        RowLayout {
            spacing: 5
            Layout.preferredHeight: 20
            Layout.fillHeight: true
            Rectangle {
                Layout.fillHeight: true
                Layout.preferredWidth: 20
                Layout.fillWidth: true
                color: "darkGreen"
            }
            Rectangle {
                Layout.fillHeight: true
                Layout.preferredWidth: 80
                Layout.fillWidth: true
                color: "lightGreen"
            }
        }
        RowLayout {
            spacing: 5
            Layout.preferredHeight: 40
            Layout.fillHeight: true
            Text {
                Layout.fillHeight: true
                Layout.preferredWidth: 40
                Layout.fillWidth: true
                color: "darkBlue"
                text: "hello world!"
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
            }
            Rectangle {
                Layout.fillHeight: true
                Layout.preferredWidth: 20
                Layout.fillWidth: true
                color: "blue"
            }
            Rectangle {
                Layout.fillHeight: true
                Layout.preferredWidth: 40
                Layout.fillWidth: true
                color: "lightBlue"
            }
        }
    }
}
Groundling answered 25/9, 2017 at 8:48 Comment(3)
"It is forbidden (and unnecessary) to try and reference width and height of the parent from Items inside the Layout". What you have created is not re-sizable though. How would you make your window resizable without referencing the size of the parent from items inside the layout?Hedvige
@Hedvige it is perfectly resizable, fillHeight and fillWidth make sure everything stays in proportion. confirmed just this moment with Qt 5.9.4.Groundling
Could you refer me to the documentation that says it is bad practice to reference parent's size from inside a layout component? I was surprised that your solution stays proportionate. Reading the documentation it seems to me that the only guarantee is that the fillHeight and fillWidth properties make the component as large as possible within the specified constraints. And since there are no minimum/maximum sizes in your solution I am not sure if the engine "has to" keep things in proportion or not. (at least it doesn't seem to be the case to me according to the documentation)Hedvige
L
5

QtQuick.Layout does not provide any real improvements over the classical anchoring system. I would recommand to avoid them. You can have way more control over your layout using anchors.

Here is the exact same design without QtQuick.Layout :

ApplicationWindow {
    x: 500
    y: 100
    width: 250
    height: 150
    visible: true

    Column {
        anchors.fill: parent

        Row {
            anchors.left: parent.left
            anchors.right: parent.right
            height: 0.4 * parent.height

            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: parent.width
                color: "red"
            }
        }

        Row {
            anchors.left: parent.left
            anchors.right: parent.right
            height: 0.2 * parent.height

            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.2 * parent.width
                color: "darkGreen"
            }

            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.8 * parent.width
                color: "lightGreen"
            }
        }

        Row {
            anchors.left: parent.left
            anchors.right: parent.right
            height: 0.4 * parent.height

            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.4 * parent.width
                color: "darkBlue"
            }
            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.2 * parent.width
                color: "blue"
            }
            Rectangle {
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                width: 0.4 * parent.width
                color: "lightBlue"
            }
        }
    }
}

So far I never met any design that was impossible to do without QtQuick.Layout.

Lashoh answered 20/10, 2016 at 10:15 Comment(5)
Nice approach! Do you have any resources that use this approach for more examples? BTW, do you see any difference between width: parent.width vs. anchors.left: parent.left; anchors.right: parent.right?Ululate
@isaac : you can look at qml example provided with qt. using anchors is also setting x and y at the same time, it also provide margins which is more intuitive than making width and height calculationLashoh
Sadly, for me this kind of solution works way more reliably than Layouts, especially in nested cases...Banter
I wouldn't agree that Layouts offer no improvement over anchoring. In my experience they require much less code to achieve the same result, result in much more readable code, and react better for resizable windows/views.Warfield
@iLikeDirt: My answer is ~4 years old, so it might be that Layouts has improved/changed since then. I don't use Qt/Qml as much nowadays, so I don't know.Lashoh

© 2022 - 2024 — McMap. All rights reserved.