Details
-
Bug
-
Resolution: Unresolved
-
P2: Important
-
None
-
5.6, 6.x
-
None
Description
When using a QTreeView, inserting/removing/moving rows may cause major slowdowns when there are a lot of items in Model.
See attached minimal example in Python (the bug is in C++ code).
A quick Google search reveals that many developers are affected by this issue and they are suggested to mitigate it using 'QAbstractItemModel::fetchMore()' and 'TreeView.setUniformRowHeights(True)' when possible.
Preable
'QTreeView' stores all items in a flat mutable list called 'viewItems' (see 'src\widgets\itemviews\qtreeview_p.h').
Each item in 'viewItems' is a 'QTreeViewItem' (also defined in 'src\widgets\itemviews\qtreeview_p.h') that caches row information like 'height'.
Thanks to this cache, Qt avoids calling 'QAbstractItemDelegate.sizeHint()' millions of times.
Bug
When the 'QAbstractItemModel' changes and 'QTreeView::rowsInserted()' is called (see 'src\widgets\itemviews\qtreeview.cpp'), instead of updating the 'viewItems' cache, Qt calls 'doDelayedItemsLayout()'.
In turn, when needed (e.g. a paint event is emitted), 'doDelayedItemsLayout()' will call 'QTreeView::doItemsLayout()' (also in 'src\widgets\itemviews\qtreeview.cpp') that will smash all 'viewItems' cache (calling 'd->viewItems.clear()') and rebuild it from scratch.
The bad side effect is that the 'viewItems' will need to be rebuilt and refilled causing, in the worst case scenario, a call to 'QAbstractItemDelegate.sizeHint()' for every cell.
The problem is exacerbated if:
- TreeView.uniformRowHeights() is False;
- QAbstractItemDelegate.sizeHint() is expensive to run;
- QAbstractItemModel has many items;
- TreeView.verticalScrollMode is QtWidgets.QAbstractItemView.ScrollPerPixel (in this case the scrollbar needs the height of all items when painted).
The problem also affects items removal and movement (they all call 'doDelayedItemsLayout()').
Fix
Instead of smashing the 'viewItems' cache, 'QTreeView::rowsInserted()' should incremetally change 'viewItems' adding the new items. The code is already there: the 'QTreeViewPrivate::expand()' (also in 'src\widgets\itemviews\qtreeview.cpp') changes the cache incrementally.
The same is true for items removal (code already in 'QTreeViewPrivate::collapse()') and items movement (which is basically a 'remove in source and insert in destination').