Низкая начальная производительность QComboBox с большим количеством элементов - PullRequest
0 голосов
/ 10 января 2020

Похоже, что вызов show для виджета / окна, содержащего QComboBox с большим количеством элементов, очень медленный.

Возьмем следующий фрагмент, который сравнивает производительность использования QComboBox с 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_()

Я получаю следующие тайминги:

  1. с использованием QComboBOx -> 8,09
  2. с использованием QTreeWidget -> 0,06 с

Использование QComboBox на несколько порядков медленнее.

1 Ответ

1 голос
/ 10 января 2020

QTreeWidget, как и другие представления элементов, являющиеся потомками QAbstractItemView, не осведомлены о своем содержимом, поэтому они обычно используют подсказку о минимальном размере для размера виджета по умолчанию и в конечном итоге могут сами изменить размер (обычно путем расширения, если есть свободное место).

QComboBox, с другой стороны, имеет свойство sizeAdjustPolicy, которое предоставляет подсказку другого размера в соответствии с содержимым его внутренней модели.

Значение по умолчанию для этого свойства - AdjustToContentsOnFirstShow, что заставляет виджет перемещаться по всему содержимому модели, чтобы найти наибольший размер элемента, и использовать этот размер для подсказки размера, как только в комбинированном списке отображается first time.
Чтобы получить каждый размер элемента, Qt использует QItemDelegate, который инициализируется для каждого элемента, вычисляет размер текста, добавляет значок (если он существует) и необходимый интервал между значком и текстом, и настраивает его на полях делегата. Как вы можете себе представить, выполнение этого процесса для большого количества элементов требует много времени.

Поскольку документация для значения AdjustToMinimumContentsLengthWithIcon сообщает:

Поле со списком откорректируется до минимальной длины содержимого плюс место для значка. По соображениям производительности используйте эту политику для больших моделей.

Как только вы установите для свойства политики это значение, вычисление размера, описанное выше, не произойдет, и виджет будет будет отображаться мгновенно.

Конечно, недостатком этого является то, что если комбо находится в очень маленьком окне или в макете, который содержит другой виджет, который требует больше места, он будет очень маленьким, возможно, не даже вообще не отображать текстовое содержимое.

Чтобы предотвратить это, вы можете установить произвольную минимальную ширину для комбинированного списка на основе текста элемента; не будет идеально (так как Qt добавляет некоторые поля вокруг текста, и вам также следует учитывать стрелку вниз), но это будет намного быстрее.
Обратите внимание, что в зависимости от шрифта, это может дать неожиданный результат, так как я использую max против длины строки, а не фактической ширины шрифта: строка с 8 «i» будет считаться больше, чем строка с 7 «w», но последняя будет вероятно, больше, если вы не используете моноширинные шрифты.

    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)
...