[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 - My data is repressed as QAbstractListModel Subclass.
-
Hi @Tobias,
AFAIKListView::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
byobjectName
first on C++ side usingfindChild
. Later you can pass the object toQAbstractListModel
sub class and then call thatpositionViewAtEnd
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 -
@Tobias Setting the
currentIndex
ofListView
should also work. Set thecurrentIndex
toListView.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 thatonCountChanged
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 evokesfunction 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 hereCheers
Tobi -
Ok now it seams to work.
Code looks not that complicated, but I'm also not sure if the execution order of theonPropyrtChanged handler
s 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 -
This thread is a bit old but....
An easier solution that worked for me is to use callLater to eliminate the problem.
onCountChanged: { if (moveToEnd) { Qt.callLater( listview.positionViewAtEnd ) } }
-
@SiliconKiwi helps, thanks.