Comment empêcher QListView d'appeler sizeHint de chaque élément?
J'ai un QListView avec de nombreux éléments de différentes hauteurs. J'implémente un délégué personnalisé pour peindre les éléments et définir le mode de mise en page sur Lot.
Cependant, lorsque le modèle est attribué, la vue de liste demande une indication de taille pour chaque élément du modèle, ignorant le paramètre Batched et ruinant ainsi les performances car pour calculer la taille, le délégué doit mettre en page beaucoup de texte (ce qui n'est ).
Il le fait probablement pour calculer la position de la barre de défilement, mais j'ai estimé que lorsque le nombre d'éléments est important, la position de la barre de défilement peut être basée uniquement sur les indices des éléments, sans tenir compte de la hauteur des éléments. Cependant, il semble que ce n'est pas ainsi que QListView fonctionne.
J'ai également essayé d'utiliser canFetchMore / fetchMore dans le modèle, mais cela conduit à une mauvaise expérience utilisateur - la position de la barre de défilement n'est plus précise et la liste saute lorsque plus d'éléments sont chargés, ce n'était pas du tout fluide.
Donc, la question est:
- Existe-t-il un moyen d'empêcher QListView d'appeler sizeHint pour les éléments invisibles?
- Si le seul moyen est d'utiliser canFetchMore / fetchMore, comment obtenir un défilement fluide et une barre de défilement stable et précise?
Merci beaucoup!
UPD: Voici l'exemple minimal qui reproduit ce comportement:https://github.com/ajenter/qt_hugelistview
Notez l'énorme retard de démarrage et les messages de débogage indiquant que sizeHint de tous les 5000 éléments est demandé à l'avance.
Réponses
Eh bien, il semble que j'ai trouvé une solution, alors je vais la partager ici pour le plaisir de quiconque a le même problème et google ce fil.
Tout d'abord, j'ai trouvé qu'il s'agissait en fait d'un bogue dans Qt enregistré en 2011 et toujours ouvert: https://bugreports.qt.io/browse/QTBUG-16592
J'y ai ajouté mon vote (et vous devriez aussi!). J'ai ensuite décidé d'essayer d'utiliser QTableView au lieu de QListView - et, surprise, j'ai réussi à le faire fonctionner, du moins il semble.
Contrairement à QListView, QTableView ne redimensionne les lignes que sur demande explicite, en appelant resizeRowToContents (rowNum). L'astuce consiste donc à l'appeler juste à temps pour les lignes qui deviennent visibles dans la fenêtre.
Voici ce que j'ai fait:
Hériter de QTableView (appelons-le MyTableView)
Remplacez QListView par MyTableView et initialisez-le comme ceci dans le constructeur. Cela attribue un délégué d'élément personnalisé, masque les en-têtes de tableau et applique le mode de sélection "par ligne":
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
}
- Dans MyTableView, ajoutez un champ privé QItemSelection et une fonction publique qui calcule les hauteurs réelles des lignes, mais uniquement celles qui sont actuellement visibles:
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";
}
}
}
Remarque: si la hauteur des éléments dépend de la largeur du contrôle, vous devez remplacer
resizeEvent()
, effacer_itemsWithKnownHeights
et appeler à nouveau updateVisibleRowsHeight ().Appelez updateVisibleRowHeights () après avoir attribué un modèle à l'instance MyTableView, afin que la vue initiale soit correcte:
table.setModel(&myModel);
table.updateVisibleRowHeights();
En fait, cela devrait être fait dans une méthode de MyTableView qui réagit aux changements de modèle, mais je la laisserai comme un exercice.
- Il ne reste plus qu'à appeler updateRowHeights chaque fois que la position de défilement vertical de la table change. Nous devons donc ajouter ce qui suit au constructeur de MyTableView:
connect(verticalScrollBar(), &QScrollBar::valueChanged, [this](int) {
updateRowHeights();
});
Terminé - cela fonctionne très vite même avec un modèle de 100 000 articles! Et le démarrage est instantané!
Un exemple de preuve de concept de base de cette technique (en utilisant QTableView pur au lieu de sous-classe) peut être trouvé ici: https://github.com/ajenter/qt_hugelistview/blob/tableview-experiment/src/main.cpp
Attention: cette technique n'est pas encore prouvée au combat et peut contenir des problèmes encore inconnus. Utilisez à vos risques et périls!