Wie kann verhindert werden, dass QListView sizeHint für jedes Element aufruft?
Ich habe eine QListView mit vielen Elementen unterschiedlicher Höhe. Ich implementiere einen benutzerdefinierten Delegaten zum Malen von Elementen und setze den Layoutmodus auf "Gestapelt".
Wenn das Modell zugewiesen wird, fordert die Listenansicht im Vorfeld sizeHint für jedes Element im Modell an, ignoriert die Einstellung "Batch" und beeinträchtigt somit die Leistung, da der Delegat zur Berechnung der Größe viel Text erstellen muss (was nicht schnell ist) ).
Wahrscheinlich wird dies getan, um die Position der Bildlaufleiste zu berechnen, aber ich habe angenommen, dass bei einer großen Anzahl von Elementen die Position der Bildlaufleiste nur auf Elementindizes basieren kann, ohne die Elementhöhen zu berücksichtigen. Es scheint jedoch, dass QListView nicht so funktioniert.
Ich habe auch versucht, canFetchMore / fetchMore im Modell zu verwenden, aber dies führt zu einer schlechten Benutzererfahrung - die Position der Bildlaufleiste ist nicht mehr genau und die Liste springt herum, wenn mehr Elemente geladen werden. Sie war überhaupt nicht glatt.
Die Frage ist also:
- Gibt es eine Möglichkeit zu verhindern, dass QListView sizeHint für unsichtbare Elemente aufruft?
- Wenn die einzige Möglichkeit darin besteht, canFetchMore / fetchMore zu verwenden, wie kann man einen reibungslosen Bildlauf und eine stabile und genaue Bildlaufleiste erzielen?
Vielen Dank!
UPD: Hier ist das minimale Beispiel, das dieses Verhalten reproduziert:https://github.com/ajenter/qt_hugelistview
Beachten Sie die enorme Startverzögerung und die Debug-Meldungen, aus denen hervorgeht, dass sizeHint aller 5000 Elemente im Voraus angefordert wird.
Antworten
Nun, es scheint, dass ich eine Lösung gefunden habe, also werde ich sie hier für alle teilen, die das gleiche Problem haben und diesen Thread googeln.
Zunächst habe ich festgestellt, dass dies tatsächlich ein Fehler in Qt ist, der bereits 2011 registriert wurde und noch offen ist: https://bugreports.qt.io/browse/QTBUG-16592
Ich habe meine Stimme hinzugefügt (und du solltest es auch!). Dann habe ich beschlossen, QTableView anstelle von QListView auszuprobieren - und überraschenderweise habe ich es geschafft, dass es funktioniert, oder so scheint es.
Im Gegensatz zu QListView ändert QTableView die Größe von Zeilen nur auf explizite Anforderung, indem resizeRowToContents (rowNum) aufgerufen wird. Der Trick besteht also darin, es just-in-time für Zeilen aufzurufen, die im Ansichtsfenster sichtbar werden.
Folgendes habe ich getan:
Von QTableView erben (nennen wir es MyTableView)
Ersetzen Sie QListView durch MyTableView und initialisieren Sie es wie folgt im Konstruktor. Dadurch wird ein benutzerdefinierter Elementdelegierter zugewiesen, Tabellenüberschriften ausgeblendet und der Auswahlmodus "Nach Zeilen" angewendet:
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
}
- Fügen Sie in MyTableView ein privates QItemSelection-Feld und eine öffentliche Funktion hinzu, die die tatsächlichen Zeilenhöhen berechnet, jedoch nur die aktuell sichtbaren:
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";
}
}
}
Hinweis: Wenn die Elementhöhe von der Breite des Steuerelements abhängt, müssen Sie updateVisibleRowsHeight () überschreiben
resizeEvent(), löschen_itemsWithKnownHeightsund erneut aufrufen.Rufen Sie updateVisibleRowHeights () auf, nachdem Sie der MyTableView-Instanz ein Modell zugewiesen haben, damit die ursprüngliche Ansicht korrekt ist:
table.setModel(&myModel);
table.updateVisibleRowHeights();
Tatsächlich sollte dies in einer MyTableView-Methode erfolgen, die auf Modelländerungen reagiert, aber ich werde es als Übung belassen.
- Jetzt müssen Sie nur noch updateRowHeights aufrufen, wenn sich die vertikale Bildlaufposition der Tabelle ändert. Daher müssen wir dem Konstruktor von MyTableView Folgendes hinzufügen:
connect(verticalScrollBar(), &QScrollBar::valueChanged, [this](int) {
updateRowHeights();
});
Fertig - es funktioniert sehr schnell, selbst bei einem Modell mit 100.000 Artikeln! Und der Start ist sofort!
Ein grundlegendes Proof-of-Concept-Beispiel für diese Technik (Verwendung von reinem QTableView anstelle der Unterklasse) finden Sie hier: https://github.com/ajenter/qt_hugelistview/blob/tableview-experiment/src/main.cpp
Warnung: Diese Technik ist noch nicht kampferprobt und kann einige noch unbekannte Probleme enthalten. Verwendung auf eigene Gefahr!