Pater Mark
Pater Mark

Reputation: 109

Problems of scrolling delegates with different heights in the ListView

Description

There is a well-known problem that has been discussed for a long time, for example, in this thread. However, there is no specific solution that I need, at least I have not found.

As we know, the ListView counts the contentHeight property based on the currently visible delegates. The number of these visible delegates depends on the value that is set for the cacheBuffer property. However, there can be many elements, and at the same time we cannot set the cacheBuffer too large to create all of them. Therefore, we cannot know exactly the height of each delegate (since some of them are not displayed, which means that they are not created), which is why the contentHeight is calculated as if on the assumption of the average height for each of them.

If all delegates have the same height then everything works well. Since the assumption about the height of the invisible elements comes true, because they are all the same and as a result, contentHeight does not change (at least in theory).

However, if the delegates have different heights, then the contentHeight value will constantly change as the displayed delegates change. As a result, if there are any components that depend on contentheight, they also change their behavior depending on the new value.

And thus we come to the most important thing: the components that depend on contentHeight. In my case it is a ScrollBar. Its height and position directly depend on the contentHeight of the ListView. Thus, every time the contentHeight changes - this will provoke changes in the height and position of the ScrollBar.

What I need

I would like to achieve this behavior so that no matter what elements the ListView displays, contentHeight should not change because of this and should always be equal to the sum of the heights of all delegates. As an example I can offer a list of messages in a Telegram. I need this behavior so that the scrollbar is always based on a specific height, and does not change its own height and position for no reason.

What I have tried

I understand that it is possible to increase the cacheBuffer. However, it seems to me that this is a decision of the consequence and not of the reason. Therefore this option is unacceptable for me.

I understand how you can independently calculate the sum of all the heights of the delegates. I got the following code (used inside ListView):

Component.onCompleted: {
    var totalHeight = 0
    for (var i = 0; i < listView.count; ++i) {
        listView.currentIndex = i
        totalHeight += listView.currentItem.height
    }
}

As a result, totalHeight is the value that, in my case, the contentHeight property should be equal to. However, I can't just go and do the binding, like contentHeight: totalHeight, or rather, it won't work, since contentHeight will still be recalculated if visible delegates change, which will definitely happen when scrolling.

Based on this, it makes no sense to somehow try to manually set the size for the ScrollBar by changing the size property for it, because after a guaranteed change of the contentHeight, the size will also change (since it depends on it).

Questions

Is there a way to set some other algorithm for counting the contentHeight of the ListView?

Or at least make it so that the ScrollBar does not depend on it, since it is constantly changing, but depends on the sum of the heights of the delegates?

If possible, is there a quick way to calculate the sum of the heights of all delegates, taking into account the fact that there can be many of them and their number can increase?

Code

An full and extensive example of this problem can be seen in this created issue (you can also find more information about this problem).

In this thread, I will give only test code that can understand how I use ScrollBar with ListView, and how do I reach this problem.

main.qml:

import QtQuick 2.11
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import "."

ApplicationWindow {
    id: root
    visible: true
    width: 1280
    height: 920
    title: "ListViet test"

    RowLayout {
        id: content
        anchors.fill: parent

        PMListView {
            id: listView
            Layout.alignment: Qt.AlignCenter
            width: 500
            height: 900
            model: 50

            delegate: Rectangle {
                id: holder
                width: listView.width
                height: 50 + (model.index % 3 == 0 ? randomInteger(500, 700) : 0)
                border.width: 2
                border.color: model.index % 3 ? "green" : "blue"
                color: "grey"

                Text {
                    anchors.centerIn: parent
                    text: model.index
                    font.pointSize: 22
                    color: "white"
                }
            }
        }
    }

    function randomInteger(min, max) {
        let rand = min + Math.random() * (max + 1 - min);
        return Math.floor(rand);
    }
}

PMListView.qml:

import QtQuick 2.9
import QtQuick.Controls 2.2

ListView {
    id: root
    focus: true
    clip: true
    interactive: false
    currentIndex: -1

    ScrollBar.vertical: ScrollBar {
        id: vScrollBar
        policy: ScrollBar.AlwaysOn
    }

    MouseArea {
        anchors.fill: parent
        acceptedButtons: Qt.NoButton
        propagateComposedEvents: true
        onWheel: {
            if (root.contentHeight > 0) {
                root.contentY -= wheel.angleDelta.y

                // force readjust on top and bottom positions (otherwise content may be shifted wrong way)
                if (root.atYBeginning) {
                    root.positionViewAtBeginning()
                }
                if (root.atYEnd) {
                    root.positionViewAtEnd()
                }
            }
        }
    }
}

Upvotes: 1

Views: 1679

Answers (1)

Adarsh Kale
Adarsh Kale

Reputation: 11

Listview
{ 
    id: listview
    ...
    ...
}


ScrollBar {
        id: vscrollbar
        view: listview
        visible: listview.height < listview.contentHeight
         ...
}

Here the visible property of scrollbar will decide the visibility based on the content height of the list view Thanks!

Upvotes: 0

Related Questions