[Solved] Automatically scroll list to end / bottom



  • Hi,
    I think it is a common use case that a list should always show the last element as long as the user never scrolls manually to a another position. Especially console like thinks or log outputs behave like this.
    The newest entry is always at the bottom of an upwards scrolling list. And if a new entry is added the list scrolls up just enough to show the new entry. As soon as the user scrolls manually the auto- scroll stops. And if the user scrolls back to the bottom auto-scroll is re-enabled.

    Currently my setup is the following:

    • My data is repressed as QAbstractListModel Subclass.
      • If new entries are added my model calls beginInsertRows and endInsertRows. Entries are only added at the end of the list.
      • If entries are removed beginRemoveRows and endRemoveRows are called. Entries are only removed from the front of the list.
      • The model can change quite quickly like adding and removing ~100 elements in a second. This can happen rarely but exact this brakes my current solution most the times.
    • The model is displayed in a ListView which is included in a ScrollView. (

    What I'm trying:
    In onContentYChanged (my indication if the user changed the position) I check the atYEnd flag and remember it in an own property scrollToEnd (form my observations it is not rally really able to only track this flag)
    As I can't find any other indication that the entries of the model have changed (added/removed) I use the onCountChanged handler to invoke ListView::positionViewAtEnd if my scrollToEnd property is true .
    This works kind of but also brakes a lot of times :(

    I would also considerer to trigger the scrolling from my QAbstractListModel, maybe someone found a working solution on this path.

    From my expectation I'm not the first how tries to achieve this,
    so any pointer or hint is welcome.

    Cheers
    Tobi



  • @Tobias From the docs i take that you should call positionViewAtEnd only once and after your component has been created.


  • Moderators

    Hi @Tobias,
    AFAIK ListView::positionViewAtEnd is the only way to do that (correct me if I'm wrong). May be calling that method onCountChanged is not a good idea as the count will always keep on changing as you go on adding it.

    From C++ side QML methods can be invoked using QMetaObject::invokeMethod. Call it when all the new items get added into the model. To make this work you will need to find the ListView by objectName first on C++ side using findChild. Later you can pass the object to QAbstractListModel sub class and then call that positionViewAtEnd method.
    To find ListView:

    QQuickView view;
    view.setSource(QUrl(QStringLiteral("qrc:/main.qml")));
    QQuickItem *itm = view.rootObject()->findChild<QQuickItem*>("mylistview"); //ListView objectName
    view.show();
    if(itm) {
        qDebug() << "ListView Found";
    }
    
    QMetaObject::invokeMethod(itm, "positionViewAtEnd"); //invoke that function
    


  • Hi
    @onek24 Could be red this way you are right but from other use cases I've found that you can (might be shouldn't) call it at any point after the Component.onCompleted was called
    Additional if found (but can not find it again) a stackoverflow entry which recommended to set the currentItem to the last item of the list if you want to use positionViewAtEnd.

    @p3c0 Since I posted I tried doing what you recomaned but with a C++-signal and a QML-"slot" instead of a direct invoke. Which at least gives me the possibility to alway scroll down triggered form C++, this is nice but the same effekt was already possible from just calling positionViewAtEnd() in onCountChanged :(
    I still cant find a way to disable and re-enable the auto scrolling as a user.

    Do you think this feature is common enough for a feature request to Qt or is this really a rare use case?

    Cheers and thanks for the input
    Tobi


  • Moderators

    @Tobias Setting the currentIndex of ListView should also work. Set the currentIndex to ListView.count-1.

    onCountChanged: {
                listview.currentIndex = count - 1
    }
    

    Have a try requesting it but I guess they would suggest the other two ways to do it.



  • Hi,
    I found that onCountChanged is not quit called at the right time, at least it feels like this. The auto-scroll works fine (mostly) but the scrollbar seams to lack behind the change which looks not nice.

    My current solution forwards the atYEnd property to my C++-Controller.
    And the code that adds a new entry looks like this.

        void TController::addEntry(const TEntry &Entry)
        {     
          bool OldAutoScrollToEnd = m_AutoScrollToEnd;
          beginInsertRows(QModelIndex(), rowCount(), rowCount());
          m_Storage.append(Entry);
          endInsertRows();
    
          if (OldAutoScrollToEnd) {
            emit scrollViewToEnd();
          }
        }
    

    In QML the scrollViewToEnd signal evokes

     function scrollViewToEnd() {
        var newIndex = listView.count - 1; // last index
        listView.positionViewAtEnd();
        listView.currentIndex = newIndex;
     } //function scrollToEnd()
    

    So that is all fine, until I resize my ListView :D
    If I find a solution to this I'll post it here

    Cheers
    Tobi



  • Ok now it seams to work.
    Code looks not that complicated, but I'm also not sure if the execution order of the onPropyrtChanged handlers might end up being an issue if they change ...

    property bool lastAtYEnd: true
    onAtYEndChanged: {
      if(controller!= null) {
        controller.setAutoScrollToEnd(atYEnd); 
      }
      lastAtYEnd = atYEnd;
    } //onAtYEndChanged
    
    onHeightChanged: {
      if(lastAtYEnd) {
        scrollViewToEnd();
      }
    } //onHeightChanged
    

    I'm still not completely happy with the solution as it looks a bit fragile but time is short so until our test team does not find a bug I'll stick to this solution so far...

    Cheers
    Tobi



  • Marked as solved.
    Till now no further problems occurred with the solution mentioned.



Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.