Home > OS >  How can I use QML Layouts to Size Nested Layouts in ScrollView?
How can I use QML Layouts to Size Nested Layouts in ScrollView?

Time:12-12

I am trying to use QML layouts to size sections of a UI within a ScrollView and having trouble understanding why one of the nested GridLayouts is being clipped. To understand it better, please see the picture below and look specifically at the bottom of "Section 1":

Layout Confusion

I think, basically, the issue is that "Section 2" implicit height(the smaller of the two side-by-side sections) is driving the sizing for that row in the magenta GridLayout instead of the other way around, as I would hope. I'm trying to get the code down to something allowed in the length of the question and will edit as soon as I do.

CodePudding user response:

Instead of using ScrollView. I highly recommend you look at components that have a ScrollBar property first such as Flickable and ListView.

To demonstrate, I mocked up the following example demonstrating how you can use ListView with the ScrollBar.vertical property set to recreate the UI/UX shown in your question.

For the delegate I use a DelegateChooser so each row can have a different delegate. For the model I use a ListModel which is used to populate the different input types.

For each delegate, I add the following code to ensure that when that control is being edited, its focus is forced to be visible. i.e. it avoids the control being clipped.

    onActiveFocusChanged: listView.currentIndex = index

Here's a full working example:

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
    background: Rectangle { color: "black" }
    Frame {
        anchors.fill: parent
        anchors.margins: 10
        background: Rectangle {
            color: "black"
            border.color: "#f0f"
            border.width: 2
        }
        ColumnLayout {
            anchors.fill: parent
            RowLayout {
                Layout.fillWidth: true
                Layout.fillHeight: true
                SectionInput {
                    title: "SECTION 1"
                    model: Data1 { }
                }
                SectionInput {
                    title: "SECTION 2"
                    model: Data2 { }
                }
            }
            SectionInput {
                title: "SECTION 3"
                model: Data3 { }
            }
        }
    }
}

// SectionInput.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.qmlmodels

Frame {
    id: sectionInput
    property string title
    property ListModel model
    Layout.fillWidth: true
    Layout.fillHeight: true
    background: Rectangle {
        color: "black"
        border.color: "yellow"
        border.width: 2
    }
    ColumnLayout {
        anchors.fill: parent
        
        Text {
            text: title
            color: "cyan"
        }
        
        ListView {
            Layout.fillWidth: true
            Layout.fillHeight: true
            model: sectionInput.model
            clip: true
            spacing: 10
            snapMode: ListView.SnapOneItem
            ScrollBar.vertical: ScrollBar {
                width: 20
                policy: ScrollBar.AlwaysOn
            }
            delegate: DelegateChooser {
                id: chooser
                role: "typ"
                DelegateChoice { roleValue: "combo"; MyCombo { } }
                DelegateChoice { roleValue: "text"; MyText { } }
                DelegateChoice { roleValue: "switch"; MySwitch { } }
            }
        }
    }
}

// MyCombo.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

RowLayout {
    property ListView listView: ListView.view
    width: listView.width - 20
    Text {
        Layout.fillWidth: true
        Layout.preferredWidth: 100
        text: lab
        color: "white"
        horizontalAlignment: Qt.AlignRight
    }
    ComboBox {
        Layout.fillWidth: true
        Layout.preferredWidth: 100
        model: dat.split(",")
        onAccepted: val = currentText
        onActiveFocusChanged: listView.currentIndex = index
        Component.onCompleted: currentIndex = find(val)
    }
}

// MyText.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

RowLayout {
    property ListView listView: ListView.view
    width: listView.width - 20
    Text {
        Layout.fillWidth: true
        Layout.preferredWidth: 100
        text: lab
        color: "white"
        horizontalAlignment: Qt.AlignRight
    }
    TextField {
        Layout.fillWidth: true
        Layout.preferredWidth: 100
        background: Rectangle { color: "#ddd" }
        text: val
        placeholderText: pla
        onAccepted: val = text
        onActiveFocusChanged: listView.currentIndex = index
    }
}

// MySwitch.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

RowLayout {
    property ListView listView: ListView.view
    width: listView.width - 20
    Text {
        Layout.fillWidth: true
        Layout.preferredWidth: 100
        text: lab
        color: "white"
        horizontalAlignment: Qt.AlignRight
    }
    Frame {
        Layout.fillWidth: true
        Layout.preferredWidth: 100
        background: Item { }
        Switch {
            checked: val === "true"
            onToggled: val = JSON.stringify(checked)
            onActiveFocusChanged: listView.currentIndex = index
        }
    }
}

// Data1.qml
import QtQuick
import QtQuick.Controls

ListModel {
    ListElement { lab: "Field 1"; typ: "combo"; val: "F1"; dat: "F0,F1,F2,F3" }
    ListElement { lab: "Field 2"; typ: "text"; pla: "xxx.xxx.xxx.xxx" }
    ListElement { lab: "Field 3"; typ: "text"; pla: "xx,xxx" }
    ListElement { lab: "Field 4"; typ: "text"; pla: "x,xxx" }
    ListElement { lab: "Field 5"; typ: "text"; pla: "xxx"  }
    ListElement { lab: "Field 6"; typ: "text"; pla: "xx" }
    ListElement { lab: "Field 7"; typ: "text" }
}

// Data2.qml
import QtQuick
import QtQuick.Controls

ListModel {
    ListElement { lab: "Field 1"; typ: "combo"; val: "TTL"; dat: "TTL,XYZ,ABC" }
    ListElement { lab: "Field 2"; typ: "combo"; val: "50"; dat: "5,50,500" }
    ListElement { lab: "Field 3"; typ: "combo"; val: "50"; dat: "40,50,60" }
    ListElement { lab: "Field 4"; typ: "combo"; val: "Normal"; dat: "Normal,Not Normal" }
    ListElement { lab: "Field 5"; typ: "combo"; val: "Normal"; dat: "Normal,Not Normal" }
}

// Data3.qml
import QtQuick
import QtQuick.Controls

ListModel {
    ListElement { lab: "Field 1"; typ: "switch"; val: "true" }
    ListElement { lab: "Field 2"; typ: "combo"; val: "OFF"; dat: "ON,OFF" }
    ListElement { lab: "Field 3"; typ: "switch"; val: "false" }
    ListElement { lab: "Field 4"; typ: "text" }
    ListElement { lab: "Field 5"; typ: "text" }
    ListElement { lab: "Field 6"; typ: "text" }
}

You can Try it Online!

CodePudding user response:

I ended up resolving the sizing issue with a test for which "Section" Rectangle was vertically larger (implicitHeight) before setting the Layout.preferredHeight for both of the "Sections".

Rectangle { //Section 1 rectangle
               color: "black"
               clip: true

               Layout.fillHeight: true
               Layout.fillWidth: true
               Layout.preferredWidth: widthMainControlCommon
               Layout.preferredHeight: { layoutSec1.implicitHeight > layoutSec2.implicitHeight ?
                    layoutSec1.implicitHeight : layoutSec2.implicitHeight}

               GridLayout {
                    id: layoutSec1
                    columns: 2
                    anchors.fill: parent
               }
//..

    Rectangle { //Section 2 rectangle
               color: "black"
               clip: true

               Layout.fillHeight: true
               Layout.fillWidth: true
               Layout.preferredWidth: widthMainControlCommon
               Layout.preferredHeight: { layoutSec1.implicitHeight > layoutSec2.implicitHeight ?
                    layoutSec1.implicitHeight : layoutSec2.implicitHeight}

               GridLayout {
                    id: layoutSec2
                    columns: 2
                    anchors.fill: parent
               }

I don't understand the benefits of the Layouts like I thought I did, or the logic that drives sizing when they're nested. Needing to set a preferredHeight in order to get the Layout to size to make it visible seems strange to me.

  • Related