Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to design a multi-level fluid layout in QML

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".

like image 996
Isaac Avatar asked Jun 02 '15 19:06

Isaac


3 Answers

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"
            }
        }
    }
}
like image 182
FourtyTwo Avatar answered Nov 12 '22 13:11

FourtyTwo


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"
            }
        }
    }
}
like image 21
Mark Ch Avatar answered Nov 12 '22 15:11

Mark Ch


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.

like image 5
BlueMagma Avatar answered Nov 12 '22 14:11

BlueMagma