Как запретить QListView вызывать sizeHint каждого элемента?

У меня есть QListView с множеством элементов разной высоты. Я реализую настраиваемый делегат для рисования элементов и устанавливаю режим компоновки «Пакетный».

Однако, когда модель назначается, представление списка заранее запрашивает sizeHint для каждого элемента в модели, игнорируя параметр «Пакетная обработка» и тем самым снижая производительность, поскольку для вычисления размера делегату приходится размещать много текста (что небыстро). ).

Вероятно, это делается для вычисления положения полосы прокрутки, но я посчитал, что при большом количестве элементов положение полосы прокрутки может основываться только на индексах элементов, не принимая во внимание высоту элементов. Однако кажется, что QListView работает не так.

Я также пытался использовать в модели canFetchMore/fetchMore, но это приводит к плохому взаимодействию с пользователем — позиция полосы прокрутки становится неточной, и список прыгает, когда загружается больше элементов, это было совсем не гладко.

Итак, вопрос:

  1. Есть ли способ запретить QListView вызывать sizeHint для невидимых элементов?
  2. Если единственный способ — использовать canFetchMore/fetchMore, как получить плавную прокрутку, а также стабильную и точную полосу прокрутки?

Большое спасибо!

UPD: Вот минимальный пример, который воспроизводит это поведение: https://github.com/ajenter/qt_hugelistview

Обратите внимание на огромную задержку запуска и отладочные сообщения, показывающие, что sizeHint всех 5000 элементов запрашивается заранее.


person Alex Jenter    schedule 20.11.2020    source источник
comment
Если вы используете QAbstractItemView::ScrollPerItem и заранее указываете количество предметов (даже с canFetchMore())? Что касается прыгающей полосы прокрутки... Однажды я сделал демонстрацию, чтобы сохранить текущий элемент в стабильном положении просмотра - независимо от того, вставлены ли элементы до или после него: Остановить прокрутку QTableView при добавлении данных над текущей позицией (хотя это зависит от использования ScrollPerPixel.) :-(   -  person Scheff's Cat    schedule 20.11.2020
comment
@Scheff: поведение одинаково независимо от режима вертикальной прокрутки - sizeHints для всех элементов запрашиваются заранее, что просто не имеет смысла для меня, учитывая, что я явно установил размер пакета только 10 элементов. Может ли это быть ошибка в Qt 6, которую я использую?   -  person Alex Jenter    schedule 20.11.2020
comment
Что касается canFetchMore, спасибо за ссылку! Насколько я вижу, для этого требуется QTableView - вы предлагаете переключиться на QTableView вместо QListView?   -  person Alex Jenter    schedule 20.11.2020
comment
Я уже давно борюсь с проблемами производительности в QTreeView и QTableView. (С QListView у меня на самом деле очень мало опыта.) Макетирование (т.е. определение размера для большого количества элементов) — это то, что я считаю одной из основных проблем. Я бы подумал, что это невозможно сделать лучше, но на самом деле я когда-то видел это лучше в gtkmm, с которого мы перешли на Qt, когда поддержка Windows стала ненадежной для gtkmm 3. Извините, что я не могу дать ничего похожего на ответ, но я Хотел бы поделиться с вами своими мыслями и чувствами. ;-)   -  person Scheff's Cat    schedule 20.11.2020
comment
Я вижу, спасибо, что поделились своим опытом в любом случае!   -  person Alex Jenter    schedule 20.11.2020
comment
Не могли бы вы опубликовать минимальный воспроизводимый пример вашего кода? Я хотел бы поиграть с ним.   -  person aalimian    schedule 21.11.2020
comment
@aalimian: Конечно, вот оно: github.com/ajenter/qt_hugelistview   -  person Alex Jenter    schedule 21.11.2020


Ответы (1)


Что ж, кажется, я нашел решение, поэтому я поделюсь им здесь для тех, у кого есть такая же проблема, и кто гуглит эту тему.

Прежде всего, я обнаружил, что на самом деле это ошибка в Qt, зарегистрированная еще в 2011 году и до сих пор открытая: https://bugreports.qt.io/browse/QTBUG-16592

Я добавил к нему свой голос (и вы тоже должны!). Затем решил попробовать использовать QTableView вместо QListView - и, на удивление, мне удалось заставить его работать, или так кажется.

В отличие от QListView, QTableView изменяет размеры строк только по явному запросу, вызывая resizeRowToContents(rowNum). Таким образом, хитрость заключается в том, чтобы вызывать его своевременно для строк, которые становятся видимыми в окне просмотра.

Вот что я сделал:

  1. Наследовать от QTableView (назовем его MyTableView)

  2. Замените QListView на MyTableView и инициализируйте его таким образом в конструкторе. Это назначает делегата пользовательского элемента, скрывает заголовки таблиц и применяется в режиме выбора строки:

   MyTableView::MyTableView(QWidget* parent) : QTableView(parent)
    {
       setSelectionBehavior(QAbstractItemView::SelectRows);
       horizontalHeader()->setStretchLastSection(true); 
       horizontalHeader()->hide();
       verticalHeader()->hide();
       setItemDelegateForColumn(0, new CustomDelegate(&table)); // for custom-drawn items
    }
  1. В MyTableView добавьте приватное поле QItemSelection и публичную функцию, вычисляющую реальную высоту строк, но только тех, которые в данный момент видны:
QItemSelection _itemsWithKnownHeight; // private member of MyTableView

void MyTableView::updateVisibleRowHeights()
{
    const QRect viewportRect = table.viewport()->rect();

    QModelIndex topRowIndex = table.indexAt(QPoint(viewportRect.x() + 5, viewportRect.y() + 5));
    QModelIndex bottomRowIndex = table.indexAt(QPoint(viewportRect.x() + 5, viewportRect.y() + viewportRect.height() - 5));
    qDebug() << "top row: " << topRowIndex.row() << ", bottom row: " << bottomRowIndex.row();

    for (auto i = topRowIndex.row() ; i < bottomRowIndex.row() + 1; ++i)
    {
        auto index = model()->index(i, 0);
        if (!_itemsWithKnownHeights.contains(index))
        {
            resizeRowToContents(i);
            _itemsWithKnownHeights.select(index, index);
            qDebug() << "Marked row #" << i << " as resized";
        }
    }
}
  1. Примечание: если высота элемента зависит от ширины элемента управления, вам необходимо переопределить resizeEvent(), очистить _itemsWithKnownHeights и снова вызвать updateVisibleRowsHeight().

  2. Вызовите updateVisibleRowHeights() после назначения модели экземпляру MyTableView, чтобы исходное представление было правильным:

   table.setModel(&myModel);
   table.updateVisibleRowHeights();

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

  1. Теперь все, что осталось, это заставить что-то вызывать updateRowHeights всякий раз, когда изменяется позиция вертикальной прокрутки таблицы. Итак, нам нужно добавить следующее в конструктор MyTableView:
connect(verticalScrollBar(), &QScrollBar::valueChanged, [this](int) {
        updateRowHeights();
    });

Готово - работает очень быстро даже с моделью из 100 000 элементов! И запуск мгновенный!

Базовый пример проверки концепции этой техники (с использованием чистого QTableView вместо подкласса) можно найти здесь: https://github.com/ajenter/qt_hugelistview/blob/tableview-experiment/src/main.cpp

Предупреждение: этот метод еще не проверен в бою и может содержать некоторые неизвестные проблемы. Используйте на свой страх и риск!

person Alex Jenter    schedule 23.11.2020