It looks like calling show on a widget/window that contains a QComboBox with large item count is very slow.
Take the following snippet that compares the performance of using QComboBox vs QTreeWidget
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from time import perf_counter
import sys
class MainWindowComboBox(QMainWindow):
def __init__(self, items, *args, **kwargs):
super().__init__(*args, **kwargs)
widget = QComboBox()
widget.addItems(items)
self.setCentralWidget(widget)
class MainWindowTreeWidget(QMainWindow):
def __init__(self, items, *args, **kwargs):
super().__init__(*args, **kwargs)
widget = QTreeWidget()
items = [QTreeWidgetItem([item]) for item in items]
widget.addTopLevelItems(items)
self.setCentralWidget(widget)
items = [f'item {i}' for i in range(100_000)]
app = QApplication(sys.argv)
window = MainWindowTreeWidget(items)
s = perf_counter()
window.show()
print('took', perf_counter()-s)
app.exec_()
I get the following timings:
Using QComboBox is orders of magnitude slower.
QTreeWidget, like other item views that are descendant of QAbstractItemView, are not aware of their contents, so they normally use a minimum size hint for the widget size by default, and can eventually be able to resize themselves (normally by expanding if there's available space).
QComboBox, on the other hand, has the sizeAdjustPolicy
property which provides a different size hint according to the contents of its internal model.
The default value for that property is AdjustToContentsOnFirstShow
, which causes the widget to navigate through the whole content of the model to find the largest item size and use that size for the size hint as soon as the combo is shown the first time.
To obtain each item size, Qt uses a QItemDelegate that is initialized for each item, computes the text size, adds an icon (if it exists) and the spacing required between the icon and the text, and adjusts it to the delegate's margins. As you can imagine, doing that process for a large amount of items requires a lot of time.
As the documentation for the AdjustToMinimumContentsLengthWithIcon
value reports:
The combobox will adjust to minimumContentsLength plus space for an icon. For performance reasons use this policy on large models.
As soon as you set the policy property to this value, the size computation explained above will not happen, and the widget will be shown instantly.
Of course, the drawback of this is that if the combo is in a window that's very small or in a layout that contains other widget that require more space, it will be very small, possibly not even showing any text content at all.
To prevent that, you could set an arbitrary minimum width for the combo based on the item text; it will not be perfect (as Qt adds some margins around the text, and you should also account for the down arrow), but it will be much faster.
Note that, depending on the font, this could give you unexpected result, since I'm using max
against the string length, not the actual font width: a string with 8 "i" will be considered larger than one with 7 "w", but the latter will be probably larger whenever you don't use monospaced fonts.
combo = QComboBox()
combo.setSizeAdjustPolicy(combo.AdjustToMinimumContentsLengthWithIcon)
combo.addItems(items)
# use font metrics to obtain the pixel width of the (possibly) longest
# text in the list;
textWidth = self.fontMetrics().width(max(items))
# add some arbitrary margin
combo.setMinimumWidth(textWidth + 20)