Como evitar que QListView chame sizeHint de cada item?
Eu tenho um QListView com muitos itens de várias alturas. Implemento um delegado personalizado para itens de pintura e defino o modo de layout como Lote.
No entanto, quando o modelo é atribuído, a exibição de lista solicita sizeHint para cada item no modelo antecipadamente, ignorando a configuração em lote e, assim, arruinando o desempenho porque, para calcular o tamanho, o delegado precisa fazer o layout de muito texto (o que não é rápido )
Provavelmente ele faz isso para calcular a posição da barra de rolagem, mas calculei que quando o número de itens é grande, a posição da barra de rolagem pode ser baseada apenas nos índices dos itens, sem levar em consideração as alturas dos itens. No entanto, parece que não é assim que QListView funciona.
Eu também tentei usar canFetchMore / fetchMore no modelo, mas isso leva a uma experiência ruim do usuário - a posição da barra de rolagem não é mais precisa e a lista pula quando mais itens são carregados, não foi nada uniforme.
Então, a questão é:
- Existe uma maneira de impedir que QListView chame sizeHint para itens invisíveis?
- Se a única maneira é usar canFetchMore / fetchMore, como obter uma rolagem suave e uma barra de rolagem estável e precisa?
Muito obrigado!
UPD: aqui está o exemplo mínimo que reproduz esse comportamento:https://github.com/ajenter/qt_hugelistview
Observe o grande atraso na inicialização e as mensagens de depuração mostrando que sizeHint de todos os 5000 itens é solicitado antecipadamente.
Respostas
Bem, parece que encontrei uma solução, então vou compartilhá-la aqui para o bem de qualquer pessoa que tenha o mesmo problema e pesquise este tópico no Google.
Em primeiro lugar, descobri que este é realmente um bug no Qt registrado em 2011 e ainda aberto: https://bugreports.qt.io/browse/QTBUG-16592
Eu adicionei meu voto a ele (e você também deveria!). Então decidi tentar usar QTableView em vez de QListView - e, surpreendentemente, consegui fazer funcionar, ou assim parece.
Ao contrário de QListView, QTableView apenas redimensiona linhas mediante solicitação explícita, chamando resizeRowToContents (rowNum). Portanto, o truque é chamá-lo de maneira just-in-time para as linhas que se tornam visíveis na janela de visualização.
Aqui está o que eu fiz:
Herdar de QTableView (vamos chamá-lo de MyTableView)
Substitua QListView por MyTableView e inicialize-o assim no construtor. Isso atribui delegado de item personalizado, oculta cabeçalhos de tabela e aplica o modo de seleção "por linha":
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
}
- Em MyTableView, adicione um campo privado QItemSelection e uma função pública que calcula alturas reais de linhas, mas apenas aquelas que estão visíveis no momento:
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";
}
}
}
Observação: se as alturas dos itens dependem da largura do controle, você precisa substituir
resizeEvent(), limpar_itemsWithKnownHeightse chamar updateVisibleRowsHeight () novamente.Chame updateVisibleRowHeights () após atribuir um modelo à instância MyTableView, para que a visualização inicial esteja correta:
table.setModel(&myModel);
table.updateVisibleRowHeights();
Na verdade, isso deve ser feito em algum método de MyTableView que reage às alterações do modelo, mas deixarei como um exercício.
- Agora, tudo o que resta é ter algo chamado updateRowHeights sempre que a posição de rolagem vertical da tabela muda. Portanto, precisamos adicionar o seguinte ao construtor de MyTableView:
connect(verticalScrollBar(), &QScrollBar::valueChanged, [this](int) {
updateRowHeights();
});
Pronto - funciona muito rápido mesmo com um modelo de 100.000 itens! E a inicialização é instantânea!
Um exemplo básico de prova de conceito desta técnica (usando QTableView puro em vez de subclasse) pode ser encontrado aqui: https://github.com/ajenter/qt_hugelistview/blob/tableview-experiment/src/main.cpp
Aviso: esta técnica ainda não foi comprovada em batalha e pode conter alguns problemas ainda desconhecidos. Use por sua conta e risco!