Jak uniemożliwić QListView wywoływanie sizeHint dla każdego elementu?
Mam QListView z wieloma elementami o różnej wysokości. Wdrażam niestandardowego delegata do malowania elementów i ustawiam tryb układu na wsadowy.
Jednak gdy model jest przypisany, widok listy żąda z góry wskazówki o rozmiarze dla każdego elementu w modelu, ignorując ustawienie Wiązane, a tym samym psując wydajność, ponieważ aby obliczyć rozmiar, delegat musi ułożyć dużo tekstu (co nie jest szybkie ).
Prawdopodobnie robi to w celu obliczenia pozycji paska przewijania, ale wyliczyłem, że gdy liczba elementów jest duża, pozycja paska przewijania może opierać się tylko na indeksach elementów, nie biorąc pod uwagę wysokości elementów. Jednak wygląda na to, że QListView nie działa tak.
Próbowałem też użyć canFetchMore / fetchMore w modelu, ale prowadzi to do złego wrażenia użytkownika - pozycja paska przewijania nie jest już dokładna, a lista przeskakuje, gdy ładuje się więcej elementów, wcale nie była płynna.
Tak więc pytanie brzmi:
- Czy istnieje sposób, aby uniemożliwić QListView wywoływanie funkcji sizeHint dla niewidocznych elementów?
- Jeśli jedynym sposobem jest użycie canFetchMore / fetchMore, jak uzyskać płynne przewijanie oraz stabilny i dokładny pasek przewijania?
Wielkie dzięki!
UPD: Oto minimalny przykład, który odtwarza to zachowanie:https://github.com/ajenter/qt_hugelistview
Zwróć uwagę na ogromne opóźnienie uruchamiania i komunikaty debugowania pokazujące, że WSKAZÓWKA rozmiaru wszystkich 5000 elementów jest wymagana z góry.
Odpowiedzi
Wygląda na to, że znalazłem rozwiązanie, więc podzielę się nim tutaj ze względu na każdego, kto ma ten sam problem i przeglądam ten wątek w Google.
Przede wszystkim odkryłem, że jest to tak naprawdę błąd w Qt zarejestrowany w 2011 roku i wciąż otwarty: https://bugreports.qt.io/browse/QTBUG-16592
Dodałem do tego mój głos (i ty też powinieneś!). Potem zdecydowałem się wypróbować użycie QTableView zamiast QListView - i, niespodzianka, udało mi się sprawić, że zadziała, a przynajmniej tak się wydaje.
W przeciwieństwie do QListView, QTableView zmienia rozmiar wierszy tylko na wyraźne żądanie, wywołując resizeRowToContents (rowNum). Tak więc sztuczka polega na wywołaniu tego w trybie just in time dla wierszy, które stają się widoczne w widoku.
Oto co zrobiłem:
Dziedzicz po QTableView (nazwijmy to MyTableView)
Zastąp QListView MyTableView i zainicjuj go w ten sposób w konstruktorze. Spowoduje to przypisanie delegata elementu niestandardowego, ukrycie nagłówków tabeli i zastosowanie trybu wyboru „według wiersza”:
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
}
- W MyTableView dodaj pole prywatne QItemSelection i funkcję publiczną, która oblicza rzeczywiste wysokości wierszy, ale tylko te, które są aktualnie widoczne:
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";
}
}
}
Uwaga: jeśli wysokości elementów zależą od szerokości kontrolki, musisz nadpisać
resizeEvent()
, wyczyścić_itemsWithKnownHeights
i ponownie wywołać updateVisibleRowsHeight ().Wywołaj updateVisibleRowHeights () po przypisaniu modelu do instancji MyTableView, aby widok początkowy był poprawny:
table.setModel(&myModel);
table.updateVisibleRowHeights();
W rzeczywistości należy to zrobić w jakiejś metodzie MyTableView, która reaguje na zmiany modelu, ale zostawię to jako ćwiczenie.
- Teraz pozostaje tylko wywołać updateRowHeights za każdym razem, gdy zmienia się pozycja przewijania tabeli w pionie. Musimy więc dodać następujące elementy do konstruktora MyTableView:
connect(verticalScrollBar(), &QScrollBar::valueChanged, [this](int) {
updateRowHeights();
});
Gotowe - działa naprawdę szybko nawet z modelem 100 000 pozycji! A uruchomienie jest natychmiastowe!
Podstawowy przykład weryfikacji koncepcji tej techniki (przy użyciu czystego QTableView zamiast podklasy) można znaleźć tutaj: https://github.com/ajenter/qt_hugelistview/blob/tableview-experiment/src/main.cpp
Ostrzeżenie: ta technika nie została jeszcze sprawdzona w walce i może zawierać jeszcze nieznane problemy. Używaj na własne ryzyko!