Qt QML ListView contentHeight behaviour

17,329

Solution 1

I know this is ancient, but I'll answer it here anyway because I was looking for a solution;

If you have a fixed height for your items, you can set the height of the container dynamically by simply setting the value by formula:

MyContainerWithListItems {
height: MyModel.items.length * height
}

If you have variable height items it will be more difficult; the solution is probably to have an onChange event fire off a function which crawls through your items and manually adds up the heights.

Solution 2

It is because ListView is estimating its contentHeight by currently visible items. Check what happens when your sections cannot be grouped. ListView is avoiding instantiating every item so it doesn't know proper size of non visible content. Look at this thread.

Share:
17,329
ManuelH
Author by

ManuelH

Updated on June 09, 2022

Comments

  • ManuelH
    ManuelH almost 2 years

    I am having a very specific issue when using a QML ListView element in combination with its section properties.

    I am using Qt 4.8.6, but I also have the same issue when I try this in Qt 5.3.1.

    The following code can also be run in older versions of Qt by simply changing the import statement to

    import QtQuick 1.0 (For < Qt 4.7.4)

    or

    import QtQuick 1.1 (For >= Qt 4.7.4)

    Here is a standalone use case to demonstrate my problem:

    import QtQuick 2.2
    
    Rectangle {
        width: 800
        height: 800
        color: "black"
    
        property int pageNumber: 1
        property int totalPages: Math.ceil(animalListView.contentHeight/animalListView.height)
    
        Text {
            x: 2
            y: 90
            color: "Orange"
            text: "Expected height: " + (animalListView.count*70 + (50*10))
            font.pixelSize: 28
        }
    
        Text {
            x: 2
            y: 0
            color: "Orange"
            text: "Actual ContentHeight: " + animalListView.contentHeight
            font.pixelSize: 28
        }
    
        Text {
            x: 2
            y: 30
            color: "Orange"
            text: "Actual ChildrenRectHeight: " + animalListView.childrenRect.height
            font.pixelSize: 28
        }
    
        Text {
            x: 2
            y: 60
            color: "Orange"
            text: "Total model items (minus sections): " + animalListView.count
            font.pixelSize: 28
        }
    
        Rectangle {
            id: boundingRect
            width: 640
            height: 500
            x: 20
            y: 200
            radius: 10
            border.width: 1
            border.color: "green"
            color: "transparent"
    
            // The delegate for each section header
            Component {
                id: sectionHeaderDelegate
                Rectangle {
                    width: parent.width
                    height: 50 // this is the problem                
                    color: "transparent"                
    
                    Text {
                        anchors.left: parent.left
                        id: headerText
                        text: section
                        color: "red"
                    }
    
                    Rectangle {
                        anchors.fill: parent
                        border.color: "purple"                    
                        border.width: 1    
                        color: "transparent"
                    }
                }
            }
    
            ListModel {
                 id: animalsModel
                 ListElement { name: "1Parrot"; size: "Small" }
                 ListElement { name: "2Guinea pig"; size: "Small" }
                 ListElement { name: "3Dog"; size: "Medium" }
                 ListElement { name: "4Cat"; size: "Medium" }
                 ListElement { name: "5Elephant"; size: "Medium" }
                 ListElement { name: "6Parrot"; size: "Small" }
                 ListElement { name: "7Guinea pig"; size: "Small" }
                 ListElement { name: "8Dog"; size: "Medium" }
                 ListElement { name: "9Cat"; size: "Medium" }
                 ListElement { name: "10Elephant"; size: "Large" }
                 ListElement { name: "11Parrot"; size: "Large" }
                 ListElement { name: "12Guinea pig"; size: "Large" }
                 ListElement { name: "13Dog"; size: "Large" }
                 ListElement { name: "14Cat"; size: "Medium" }
                 ListElement { name: "15Elephant"; size: "Large" }
                 ListElement { name: "16Parrot"; size: "Small" }
                 ListElement { name: "17Guinea pig"; size: "Small" }
                 ListElement { name: "18Dog"; size: "Medium" }
                 ListElement { name: "19Cat"; size: "Medium" }
                 ListElement { name: "20Elephant"; size: "Large" }
            }
    
            ListView {
                id: animalListView
                anchors.fill: parent
                anchors.margins: 10
                clip: true
                interactive: true
                flickableDirection: Flickable.VerticalFlick
                boundsBehavior: Flickable.StopAtBounds
                model: animalsModel
    
                delegate: Item {
                    width: parent.width
                    height: 70
                        Text {
                            text: name
                            color: "green"
                        }
    
                        Rectangle {
                            anchors.fill: parent
                            border.color: "yellow"                    
                            border.width: 1    
                            color: "transparent"
                        }
                    }
    
                section.property: "size"
                section.criteria: ViewSection.FullString
                section.delegate: sectionHeaderDelegate
            }
        }
    
        Rectangle {
            anchors.top: boundingRect.top
            anchors.left: boundingRect.right
            anchors.leftMargin: 20        
            width: 40
            height: 40
            color: "blue"
    
             MouseArea {
                 anchors.fill: parent
                 onClicked: {
                    if (pageNumber > 1) {
                        animalListView.contentY -= animalListView.height;
                        animalListView.returnToBounds();
                        --pageNumber;
                    }             
                 }
             }
    
            enabled: (!animalListView.atYBeginning)
            visible: !(animalListView.atYBeginning && animalListView.atYEnd)
    
            Text {
                anchors.centerIn: parent
                font.family: "Wingdings 3"
                font.pixelSize: 40
                text: "Ç" // Up arrow
            }
        }
    
        Text {
            visible: totalPages > 1
            anchors.left: boundingRect.right
            anchors.verticalCenter: boundingRect.verticalCenter
            width: 100
            height: 20
            font.pixelSize: 18
            horizontalAlignment: Text.AlignHCenter
            color: "red"
            text: qsTr("%1 of %2").arg(pageNumber).arg(totalPages)
        }
    
        Rectangle {
            anchors.bottom: boundingRect.bottom
            anchors.left: boundingRect.right
            anchors.leftMargin: 20
            width: 40
            height: 40
            color: "orange"
    
             MouseArea {
                 anchors.fill: parent
                 onClicked: {
                    if (pageNumber < totalPages) {
                        animalListView.contentY += animalListView.height;
                        ++pageNumber;
                    }
                 }
             }
    
            enabled: (!animalListView.atYEnd)
            visible: !(animalListView.atYBeginning && animalListView.atYEnd)
    
            Text {
                anchors.centerIn: parent
                font.family: "Wingdings 3"
                font.pixelSize: 40
                text: "È" // Down arrow
            }
        }
    
    }
    

    I am using the ListView to display a list of animal models, categorized by their size. In order to achieve this categorization in the view, I use the section.property, section.critiria and section.delegate properties of the ListView as implemented in the code given above.

    (Note: Please ignore the fact that the model I supply to the ListView is not sorted, I understand that this will create numerous duplicate category entries in the ListView. This is beside the point here.)

    When the number of models exceed the visible area of the ListView, I am using the property totalPages to calculate how many full ListView pages there are for navigation. The Up arrow and Down arrow buttons simply decrement and increment the content.Y of the ListView by the height of the ListView respectively.

    The problem is that the contentHeight of the ListView does not remain static, it is dynamically changing and causing my totalPages property calculation to be incorrect.

    It is interesting to note that this behavior occurs if and only if I set a height for my sectionHeaderDelegate rectangle. If I comment out the height statement (height: 50), the contentHeight of the ListView remains static, as expected - with the downside that the section headers/categories are now on top of the model text, which is not useful at all.

    So my question is, why does the contentHeight of the QML ListView element dynamically change if and only if I use a section delegate who's height has been set to a non-zero value?

    Also, I have left the following properties in the ListView for testing purposes, the ListView should be used with the Up/Down arrows:

              interactive: true
              flickableDirection: Flickable.VerticalFlick
              boundsBehavior: Flickable.StopAtBounds