How to design a multi-level fluid layout in QML - qt

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

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

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

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.

Related

How to evenly shrink items in layout to make space for more items

Initially, I have four items with a specific size. Once a 5th item gets added, I would like to shrink the size of all items to make space inside the layout.
Window {
width: 640
height: 480
visible: true
id: root
Item {
id: col
width: 300
height: 200
anchors.centerIn: parent
RowLayout {
id: row
spacing: 10
anchors.centerIn: parent
Rectangle {
Layout.minimumWidth: 30
Layout.preferredWidth: 60
Layout.preferredHeight: 20
Layout.fillWidth: true
color: "red"
}
Rectangle {
Layout.minimumWidth: 30
Layout.preferredWidth: 60
Layout.preferredHeight: 20
color: "blue"
Layout.fillWidth: true
}
Rectangle {
Layout.minimumWidth: 30
Layout.preferredWidth: 60
Layout.preferredHeight: 20
color: "green"
Layout.fillWidth: true
}
Rectangle {
Layout.minimumWidth: 30
Layout.preferredWidth: 60
Layout.preferredHeight: 20
color: "green"
Layout.fillWidth: true
}
Rectangle {
Layout.minimumWidth: 30
Layout.preferredWidth: 60
Layout.preferredHeight: 20
color: "blue"
}
}
}
// for visualization
Rectangle {
width: col.width
height: col.height
x: col.x
y: col.y
color: "black"
opacity: 0.3
}
}
The items should have a size of 60 if there's space to fit all items, or scale down to 30 if there isn't. Any ideas how I would accomplish that?
You were almost there: Set Layout.maximumWidth instead of Layout.preferredWidth and make sure all the elements have Layout.fillWidth set to true. Also, the layout element itself must have the correct size for fillWidth to work. So replace centerIn with fill in this case.
Here is your snippet; corrected and rewritten to make it easier to try out different numbers of elements.
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14
import QtQuick.Window 2.14
Window {
width: 640
height: 480
visible: true
id: root
Item {
id: col
width: 300
height: 200
anchors.centerIn: parent
RowLayout {
id: row
spacing: 10
anchors.fill: parent
Repeater {
model: spinBox.value
delegate: Rectangle {
Layout.minimumWidth: 30
Layout.maximumWidth: 60
Layout.preferredHeight: 20
Layout.fillWidth: true
color: ["red", "blue", "green", "green", "blue"][index % 5]
}
}
}
}
// for visualization
Rectangle {
width: col.width
height: col.height
x: col.x
y: col.y
color: "black"
opacity: 0.3
}
SpinBox {
id: spinBox
from: 1; to: 10
value: 5
}
}

QML: SplitView hides all children except last

I am new to QML and I am trying to implement a horizontal splitview with 2 children. The problem that I am having is that despite setting maximum and minimum widths for the children, the last child always takes up the entire split view and all the others are hidden and have to be manually opened. I have tried defining minimum and maximum widths using Layout.maximum/minimumwidth (which dont work at all) and have tried using fillwidth on the first child of the splitview. Nothing seems to work. I even copied and pasted the code from the qml doc page from splitview and it did the same thing. Here is my code:
import QtQuick 2.0
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.11
import "../buttons"
import "../customWidgets"
Rectangle {
id: conversationsPage
anchors.fill: parent
height: 455
width: 800
SplitView {
id: splitView
anchors.fill: parent
orientation: Qt.Horizontal
Rectangle {
id: sideBar
Layout.minimumWidth: 200
Layout.preferredWidth: 300
Layout.maximumWidth: 500
Layout.fillWidth: true
color: "#b9b9b9"
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.topMargin: 0
clip: true
Rectangle {
id: sideBarTopBar
y: 0
z: 2
height: 44
color: "#e868ff"
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 0
anchors.rightMargin: 0
SearchBar {
id: conversationSearchBar
anchors.left: parent.left
anchors.right: newConversationBtn.left
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.bottomMargin: 10
anchors.topMargin: 10
anchors.leftMargin: 10
anchors.rightMargin: 10
}
IconBtn {
id: newConversationBtn
width: 35
height: 35
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 10
btnIconSource: "../images/icons/plus.svg"
}
}
ScrollView {
id: conversationsListScroll
anchors.left: parent.left
anchors.right: parent.right
anchors.top: sideBarTopBar.bottom
anchors.bottom: parent.bottom
z: 1
anchors.topMargin: 0
ColumnLayout {
id: conversationListLayout
x: 0
y: 0
width: conversationsListScroll.width
clip: true
ConversationTab {
Layout.fillWidth: true
}
ConversationTab {
Layout.fillWidth: true
}
ConversationTab {
Layout.fillWidth: true
}
ConversationTab {
Layout.fillWidth: true
}
ConversationTab {
Layout.fillWidth: true
}
ConversationTab {
Layout.fillWidth: true
}
ConversationTab {
Layout.fillWidth: true
}
}
}
}
Rectangle {
id: conversationView
Layout.fillWidth: false
Layout.minimumWidth: 300
Layout.maximumWidth: 500
color: "#ff0000"
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.topMargin: 0
anchors.bottomMargin: 0
}
}
Do you guys have any idea why the split view isnt working the way I want it to?
I noticed a couple of things, first, in SplitView from Quick Controls 2, you must use the SplitView attached property instead of the Layout attached property.
Secondarily, I also noticed that you have anchors specified inside the direct children of the SplitView, which have no effect and can be removed. I am not sure, but it seems like the child with SplitView.fillWidth: true should not have a maximum width set, as both children having maximum widths can prevent the SplitView from filling its parent Rectangle fully (you may still have a use case for this, but I removed it for this reason).
Here is the code with these recommendations:
SplitView {
id: splitView
anchors.fill: parent
orientation: Qt.Horizontal
Rectangle {
id: sideBar
SplitView.minimumWidth: 200
SplitView.preferredWidth: 300
SplitView.fillWidth: true
color: "#b9b9b9"
clip: true
// children here...
}
Rectangle {
id: conversationView
SplitView.fillWidth: false
SplitView.minimumWidth: 300
SplitView.maximumWidth: 500
color: "#ff0000"
}
}

QtQuick: Resize two textfields with Elide on both

I've got two Labels A and B in a row which need to be anchored together such that there is no spacing between them. A is not allowed to be larger than it's content, because B is like a detail label. It would be weird to see Google Chrome_____(the current browser). You'll want Google Chrome (the current browser)____ in this case.
Both have a common parent that they can fill up. Content-wise both A and B can be the larger of the two. The whole thing should be left aligned such that A is anchored to the parent's left and B.left == A.right.
When there is not enough space, both items should shrink and elide until they fit the parent, eg Google Chr..(The current br...
This last part is what I could not figure out how to do.
I'm using 2 labels instead of 1 because they need their own elision and their own styling.
My current solution will simply elide B, not shrink A at all.
RowLayout
{
spacing: 0
anchors.left: parent.left
anchors.right: customisedSettings.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
Label
{
id: textLabelA
text: qualityName()
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
Layout.margins: 0
height: contentHeight
verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
elide: Text.ElideRight
function qualityName() {
[...]
}
}
Label
{
id: textLabelDetail
text: activeQualityDetailText()
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_detail")
anchors.verticalCenter: textLabelA.verticalCenter
Layout.margins: 0
Layout.fillWidth: true
height: contentHeight
verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
elide: Text.ElideRight
function activeQualityDetailText()
{
[..]
}
}
}
You can do that by putting them in a RowLayout and setting Layout.fillWidth: true on both of them so they both shrink.
To ensure that the first Label isn't larger than needed, set Layout.maximumWidth: implicitWidth.
This gives us :
import QtQuick 2.8
import QtQuick.Controls 2.1
import QtQuick.Window 2.3
import QtQuick.Layouts 1.2
Window {
visible: true
width: 600
height: 400
RowLayout {
anchors.fill: parent
Label {
text: "Google Chrome"
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth
elide: Text.ElideRight
}
Label {
text: "(the current browser)"
color: "darkgray"
Layout.fillWidth: true
elide: Text.ElideRight
}
}
}

Margin isn't working in qml

I am making UI for a game. When I tried to put margin for the image tab.png
It doesn't reflect any changes to it. It stays where it was before. I also tried to solve this problem by adding the margins through the Layout and by adding it outside the rectangle and row layout but nothing happened.
Also when I am adding margin to the bottom to the user.png to shift it a bit upward, it isn't shifting. So please help me out to solve this. I want to position the tab.png as this layout
The second circle is where I want to place the tab.png. The output of the code
Window {
visible: true
width: 800
height: 600
title: qsTr("Main screen")
ColumnLayout{
spacing: 0
anchors.fill: parent
Item {
id: titlebar
Layout.preferredHeight: 60
Layout.fillWidth: true
RowLayout {
anchors.fill: parent
spacing: 0
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "black"
Image {
source: "qrc:/img/tab.png"
anchors.leftMargin: undefined
Layout.leftMargin: 20
}
}
Rectangle {
Layout.preferredWidth: 100
Layout.fillHeight: true
color: "#f46b42"
/*Text {
anchors.centerIn: parent
text: "Actions"
}*/
Image{
id:image_user
source: "qrc:/img/user.png"
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset:
anchors.left=parent.left
anchors.leftMargin: 10
clip: true
}
Item{
id:text_content
anchors.centerIn: parent
anchors.bottomMargin: 20
Text{
id:text_user
text: "User"
anchors.bottom:text_value.top
anchors.bottomMargin: 4
}
Text{
id:text_value
text:"$ 2000"
color:"yellow"
}}
}
}
}
Rectangle {
id: content
Layout.fillHeight: true
Layout.fillWidth: true
color: "lightyellow"
Text {
anchors.centerIn: parent
}
Column{
spacing: 1;
Repeater{
id:mmm
model: 5
Rectangle{
id:imgl
width: 100
height: 100
color: "#4286f4"
property string src: ""
MouseArea{
anchors.fill:parent
onClicked: {
parent.color="";
}
}
Image {
id: imgx
source: parent.src;
anchors.verticalCenter: parent.verticalCenter
}
onParentChanged: {
mmm.itemAt(0).src="qrc:/img/5by90.png";
mmm.itemAt(1).src="qrc:/img/6by42.png";
mmm.itemAt(2).src="qrc:/img/12by24.png";
mmm.itemAt(3).src="qrc:/img/fortune.png";
mmm.itemAt(4).src="qrc:/img/mini-roulette.png";
}
}
}
}
}
}
}
Layouts only affect your direct children, not the children of the children., so Layout.leftMargin: 20 will not affect Image as you see in this case.
The solution is really simple, it establishes the property x: 20 since the position of item is with respect to the parent's topleft position
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "black"
Image {
x:20
source: "qrc:/img/tab.png"
}
}
Inside Rectangle, for child elements, you need to use anchors.margin, whereas for Layouts child element can use Layout.margin.You need to use anchors.leftMargin: as Parent is Rectangle, Layout.margin will not have any effect.
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "black"
Image {
source: "qrc:/img/tab.png"
anchors.leftMargin: 20
}
}

Nested ScrollView in QML doesn't respond to mousewheel

I have a nested ScrollView, similar to the following QML:
import QtQuick 2.0
import QtQuick.Controls 1.1
Rectangle {
width: 200
height: 600
ScrollView {
id: sView
anchors.fill: parent
ListView {
id: list
boundsBehavior: Flickable.StopAtBounds
clip: true
focus: true
interactive: true
model: 5
delegate: Component {
MouseArea {
id: hoverArea
width: 100
height: 200
onClicked: list.currentIndex = index;
Rectangle {
id: fauxParent
anchors.fill: parent
border.width: 1
border.color: "black"
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
height: parent.height
width: parent.width / 2
border.width: 1
border.color: "purple"
color: "green"
Text {
anchors.centerIn: parent
text: "stuff"
}
}
ScrollView {
//parent: sView
anchors.top: fauxParent.top
anchors.right: fauxParent.right
height: fauxParent.height
width: fauxParent.width / 2
ListView {
model: 3
delegate: Component {
Rectangle {
radius: 10
height: 100
width: 100
color: "blue"
}
}
}
}
}
}
}
}
}
}
It seems to run correctly, except that the inner ScrollView won't respond to the mousewheel: the outer ScrollView intercepts that event. The only fix I've found in research for this, is to set the inner scrollview's parent directly to the outer scrollview (uncomment the parent: sView line). Unfortunately, this re-positions all five scrollview delegates onto the top right corner of the outer scrollview. It seems that ScrollView positions itself based on its parent?
For the record, my actual application is wrapping a large section of the page in a scrollview so as to allow the user to access sections of it that may be out of bounds for the current window size. The content of this section, though, has a variety of different controls for a variety of different purposes, including some scrollviews. So I'd also accept an alternate way of moving around a set of generic content that's too large for the window.
This is a Windows desktop app, so I don't need to consider mobile-specific issues.
You nested four elements that handle scroll Events.
Why do you put a ScrollView arround a ListView?
If you remove the ScrollViews the Mousewheel work fine.
Rectangle {
width: 200
height: 600
ListView {
anchors.fill: parent
id: list
boundsBehavior: Flickable.StopAtBounds
clip: true
focus: true
interactive: true
model: 5
delegate: Component {
MouseArea {
id: hoverArea
width: 100
height: 200
onClicked: list.currentIndex = index;
Rectangle {
id: fauxParent
anchors.fill: parent
border.width: 1
border.color: "black"
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
height: parent.height
width: parent.width / 2
border.width: 1
border.color: "purple"
color: "green"
Text {
anchors.centerIn: parent
text: "stuff"
}
}
ListView {
anchors.top: fauxParent.top
anchors.right: fauxParent.right
height: fauxParent.height
width: fauxParent.width / 2
model: 3
delegate: Component {
Rectangle {
radius: 10
height: 100
width: 100
color: "blue"
}
}
}
}
}
}
}
}
If you miss the Scrollbar look at this:
How to create scrollbar in QtQuick 2.0?

Resources