Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt QML ListView contentHeight behaviour

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
like image 916
ManuelH Avatar asked Sep 09 '14 13:09

ManuelH


People also ask

What is ListView in QML?

A ListView displays data from models created from built-in QML types like ListModel. A ListView has a model, which defines the data to be displayed, and a delegate, which defines how the data should be displayed. Items in a ListView are laid out horizontally or vertically.

What is Flickable in QML?

The Flickable item places its children on a surface that can be dragged and flicked, causing the view onto the child items to scroll. This behavior forms the basis of Items that are designed to show large numbers of child items, such as ListView and GridView.

What is Modeldata in QML?

Similarly, encapsulating an instance of the data in a delegate allows the developer to dictate how to present or handle the data. Model - contains the data and its structure. There are several QML types for creating models. View - a container that displays the data. The view might display the data in a list or a grid.

What is loader in QML?

Loader is used to dynamically load QML components. Loader can load a QML file (using the source property) or a Component object (using the sourceComponent property).


1 Answers

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.

like image 114
Kver Avatar answered Oct 01 '22 22:10

Kver