Heavy flickering in QListView when appending items if VerticalScrollBarPolicy == Qt::ScrollBarAlwaysOff
-
Hi!
I have a QListView whose model is getting items appended from a worker thread (as discussed here: https://forum.qt.io/topic/98481/ui-responsiveness-and-qlistview-updating-during-load-of-large-data-into-model). After implementing this in my actual program I noticed the listview flickering a lot while items were being added. After some testing I found that removing the call to
QListView::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff)
removed all the flickering. I have made a little video showing this. I do not have the privileges to upload it here directly, but I've posted it here. At the start of the video, everything is fine with the scrollbar present, then without the scrollbar the list is barely usable while items are added.It looks to me like the QListView is doing a lot of extra (unnecessary) repaints when the scrollbar is not there. I almost think this might be a bug, but maybe I'm just being stupid or missing something obvious. Does anyone know what's going on?
Also, is there a workaround? Any classes or functions to reimplement?
The code I used:
//main.cc #include <QtWidgets> #include "myitemmodel.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget widget; QVBoxLayout *layout = new QVBoxLayout(&widget); QListView *listview = new QListView(&widget); QPushButton *button = new QPushButton("Populate list", &widget); MyItemModel *model = new MyItemModel; listview->setModel(model); listview->setMouseTracking(true); listview->setEditTriggers(QListView::NoEditTriggers); listview->setResizeMode(QListView::Adjust); listview->setWordWrap(true); listview->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); listview->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); listview->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // <-- PROBLEM! QObject::connect(button, &QPushButton::clicked, model, &MyItemModel::loadData); layout->addWidget(listview); layout->addWidget(button); widget.setMinimumSize(QSize(320, 380)); widget.show(); return app.exec(); }
//myitemmodel.h #ifndef MYITEMMODEL_H_ #define MYITEMMODEL_H_ #include <QtWidgets> class MyItemModel : public QStandardItemModel { Q_OBJECT; bool d_cancel; QThread *d_worker; public: inline MyItemModel(QWidget *parent = nullptr); public slots: inline void loadData(); inline void addAvailableItem(QStandardItem *item); signals: void itemAvailable(QStandardItem *item); private: inline void loadDataWorker(); }; inline MyItemModel::MyItemModel(QWidget *parent) : QStandardItemModel(parent), d_cancel(false), d_worker(nullptr) { connect(this, &MyItemModel::itemAvailable, this, &MyItemModel::addAvailableItem, Qt::QueuedConnection); } inline void MyItemModel::loadData() { // here, I should cancel any running threads and wait for them to stop d_cancel = true; if (d_worker) { d_worker->wait(); delete d_worker; d_worker = nullptr; } d_cancel = false; // clear the model clear(); // start thread to load data d_worker = QThread::create([=]{loadDataWorker();}); d_worker->start(); } inline void MyItemModel::loadDataWorker() { for (uint i = 0; i < 2000; ++i) { if (d_cancel) break; QStandardItem *item = new QStandardItem; item->setData("This is item " + QString::number(i), Qt::DisplayRole); QThread::msleep(2); emit itemAvailable(item); } } inline void MyItemModel::addAvailableItem(QStandardItem *item) { appendRow(item); } #endif
Should compile with
qmake -project QT+=widgets && qmake && make
.Thanks!
-
I tried to hide the scrollbar by setting the style width to 0. I hoped it might achieve the desired behavior through a different code path, but alas, using
QListView::verticalScrollBar()->setStyleSheet("QScrollBar {width:0px;}");
produces the exact same flickering as callingQListView::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff)
, while setting to 1px works flawlessly (with a weirdly slim scrollbar).Any workaround suggestion is still highly appreciated.
-
Hi,
What version of Qt are you using ?
On what OS ?
Did you try to profile your application ? -
I am using Qt 5.12 on Arch Linux. I have not tried profiling the application, I would not even know how. Honestly, I am not even completely sure what it means. What would I be able to learn about this problem by profiling?
I have seen the same behavior on my laptop as well, but it runs the same Qt and OS. I would also be interested to hear if anyone could verify the problem on any other OS, or possibly other (recent) Qt versions.
Thanks!
-
@bepaald
Just a word about "profiling". You will (probably) compile/link your code with special flags so that when it runs it gathers statistics about just how much time is spent executing what in your code and where everything is being called from. Then there will be an external tool which presents these statistics to you for analysis. This is often used to understand where a program is being inefficient; if you are a programmer then profiling is well worth getting to know. In your case @SGaist is suggesting it may show just where this redrawing originates.What is available to you for profiling depends on your platform + compiler. Certainly if Linux + Qt Creator then the latter already supplies the tools; if you are Windows + Visual Studio then VS has the tools. Further details for Qt profiling at https://wiki.qt.io/Profiling_and_Memory_Checking_Tools & http://doc.qt.io/qtcreator/creator-cache-profiler.html.
-
Thanks for your responses. I tried profiling the application by running the application through valgrind's callgrind tool. I then looked at the generated fiel with kcachegrind, but while I do see some differences, nothing looks like a clear problem to me. I noticed
QListView::paintEvent
is called (throughQWidget::event
) byQFrame::event
in both cases, but in the working code (the one with the scrollbars visible)QAbstractSlider::event
is also one of the callers, while it is missing in the bad example. But that's probably expected, and not necessarily a problem.If someone with more experience feels like looking at the callgrind files, that would be much appreciated. EDIT: apparently I can not attach files, so never mind this.
Also, should I compile my own Qt with debugging symbols to have more useful output?
[0_1549381136500_callgrind.out.2561.GOOD](Uploading 100%)
[0_1549381144300_callgrind.out.2523.BAD](Uploading 100%) -
Qt 5.12.1 just got released, can you check again if you still have the same problem ?
-
d_cancel
is a race condition, change its type tostd::atomic_bool
What I suspect is happening is that this intersect check is not working correctly when you disable the scrollbars -
Thanks for the replies and apologies for the late response. I'm having a very busy period. I have in the meantime submitted a bug report: https://bugreports.qt.io/browse/QTBUG-73177
@SGaist said in Heavy flickering in QListView when appending items if VerticalScrollBarPolicy == Qt::ScrollBarAlwaysOff:
Qt 5.12.1 just got released, can you check again if you still have the same problem ?
Yes, I did check 5.12.1 and nothing has changed. I've updated my bug report accordingly.
@VRonin said in Heavy flickering in QListView when appending items if VerticalScrollBarPolicy == Qt::ScrollBarAlwaysOff:
d_cancel
is a race condition, change its type tostd::atomic_bool
What I suspect is happening is that this intersect check is not working correctly when you disable the scrollbarsThanks, I just wrote that little example code quickly and indeed,
d_cancel
could lead to problems (though not very likely). rest assured, in the actual project where I'm seeing this problem, the equivalent bool is already astd::atomic_bool
.Thanks for the hint as to where the problem is, I will try to investigate.
I have tried to install the debug packages of qt on my machine, but the easiest way to do so, they overwrite the normal packages and make my desktop unusable (since I'm running KDE which uses qt). So I need to figure out how to install a second, separate version or try to debug on a different (virtual) machine. Either way, just getting it set up will take more time than i have had lately. To be continued.
Thanks again!