title: Problems of scrolling delegates with different heights in the ListView
body: <h2>Description</h2>
  <p>There is a well-known problem that has been discussed for a long time, for example, in this <a href="https://forum.qt.io/topic/9501/solved-listview-s-flickable-problems" rel="nofollow noreferrer">thread</a>. However, there is no specific solution that I need, at least I have not found.</p>
  <p>As we know, the <a href="https://doc.qt.io/qt-5/qml-qtquick-listview.html" rel="nofollow noreferrer">ListView</a> counts the <a href="https://doc.qt.io/qt-5/qml-qtquick-flickable.html#contentHeight-prop" rel="nofollow noreferrer">contentHeight</a> property based on the currently visible delegates. The number of these visible delegates depends on the value that is set for the <a href="https://doc.qt.io/qt-5/qml-qtquick-listview.html#cacheBuffer-prop" rel="nofollow noreferrer">cacheBuffer</a> property. However, there can be many elements, and at the same time we cannot set the <code>cacheBuffer</code> 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 <code>contentHeight</code> is calculated as if on the assumption of the average height for each of them.</p>
  <p>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, <code>contentHeight</code> does not change (at least in theory).</p>
  <p>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 <code>contentheight</code>, they also change their behavior depending on the new value.</p>
  <p>And thus we come to the most important thing: the components that depend on contentHeight. In my case it is a <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-scrollbar.html" rel="nofollow noreferrer">ScrollBar</a>. Its height and position directly depend on the <code>contentHeight</code> of the <code>ListView</code>. Thus, every time the <code>contentHeight</code> changes - this will provoke changes in the height and position of the <code>ScrollBar</code>.</p>
  <h2>What I need</h2>
  <p>I would like to achieve this behavior so that no matter what elements the <code>ListView</code> displays, <code>contentHeight</code> 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 <a href="https://github.com/telegramdesktop/tdesktop" rel="nofollow noreferrer">Telegram</a>. 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.</p>
  <h2>What I have tried</h2>
  <p>I understand that it is possible to increase the <code>cacheBuffer</code>. 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.</p>
  <p>I understand how you can independently calculate the sum of all the heights of the delegates. I got the following code (used inside <code>ListView</code>):</p>
  <pre><code>Component.onCompleted: {
      var totalHeight = 0
      for (var i = 0; i &lt; listView.count; ++i) {
          listView.currentIndex = i
          totalHeight += listView.currentItem.height
      }
  }
  </code></pre>
  <p>As a result, <code>totalHeight</code> is the value that, in my case, the <code>contentHeight</code> property should be equal to. However, I can't just go and do the binding, like <code>contentHeight: totalHeight</code>, or rather, it won't work, since <code>contentHeight</code> will still be recalculated if visible delegates change, which will definitely happen when scrolling.</p>
  <p>Based on this, it makes no sense to somehow try to manually set the size for the ScrollBar by changing the <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-scrollbar.html#size-prop" rel="nofollow noreferrer">size</a> property for it, because after a guaranteed change of the <code>contentHeight</code>, the <code>size</code> will also change (since it depends on it).</p>
  <h2>Questions</h2>
  <p>Is there a way to set some other algorithm for counting the <code>contentHeight</code> of the <code>ListView</code>?</p>
  <p>Or at least make it so that the <code>ScrollBar</code> does not depend on it, since it is constantly changing, but depends on the sum of the heights of the delegates?</p>
  <p>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?</p>
  <h2>Code</h2>
  <p>An <em><strong>full and extensive example</strong></em> of this problem can be seen in this created <a href="https://github.com/mirukana/mirage/issues/225#issue-896517764" rel="nofollow noreferrer">issue</a> (you can also find more information about this problem).</p>
  <p>In this thread, I will give only test code that can understand how I use <code>ScrollBar</code> with <code>ListView</code>, and how do I reach this problem.</p>
  <p><code>main.qml</code>:</p>
  <pre><code>import QtQuick 2.11
  import QtQuick.Controls 2.12
  import QtQuick.Layouts 1.12
  import &quot;.&quot;
  
  ApplicationWindow {
      id: root
      visible: true
      width: 1280
      height: 920
      title: &quot;ListViet test&quot;
  
      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 ? &quot;green&quot; : &quot;blue&quot;
                  color: &quot;grey&quot;
  
                  Text {
                      anchors.centerIn: parent
                      text: model.index
                      font.pointSize: 22
                      color: &quot;white&quot;
                  }
              }
          }
      }
  
      function randomInteger(min, max) {
          let rand = min + Math.random() * (max + 1 - min);
          return Math.floor(rand);
      }
  }
  </code></pre>
  <p><code>PMListView.qml</code>:</p>
  <pre><code>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 &gt; 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()
                  }
              }
          }
      }
  }
  </code></pre>