¿Cómo evitar que QListView llame a sizeHint de cada elemento?
Tengo un QListView con muchos elementos de varias alturas. Implemento un delegado personalizado para pintar elementos y configuro el modo de diseño en Por lotes.
Sin embargo, cuando se asigna el modelo, la vista de lista solicita sizeHint para cada elemento del modelo por adelantado, ignorando la configuración por lotes y, por lo tanto, arruinando el rendimiento porque para calcular el tamaño, el delegado tiene que diseñar una gran cantidad de texto (lo cual no es rápido ).
Probablemente hace esto para calcular la posición de la barra de desplazamiento, pero calculé que cuando el número de elementos es grande, la posición de la barra de desplazamiento puede basarse únicamente en índices de elementos, sin tener en cuenta las alturas de los elementos. Sin embargo, parece que no es así como funciona QListView.
También intenté usar canFetchMore / fetchMore en el modelo, pero esto conduce a una mala experiencia del usuario: la posición de la barra de desplazamiento ya no es precisa y la lista salta cuando se cargan más elementos, no fue nada suave.
Entonces, la pregunta es:
- ¿Hay alguna forma de evitar que QListView llame a sizeHint para elementos invisibles?
- Si la única forma es usar canFetchMore / fetchMore, ¿cómo obtener un desplazamiento suave y una barra de desplazamiento estable y precisa?
¡Muchas gracias!
UPD: Aquí está el ejemplo mínimo que reproduce este comportamiento:https://github.com/ajenter/qt_hugelistview
Tenga en cuenta el enorme retraso de inicio y los mensajes de depuración que muestran que sizeHint de los 5000 elementos se solicita por adelantado.
Respuestas
Bueno, parece que encontré una solución, así que la compartiré aquí por el bien de cualquiera que tenga el mismo problema y busque en Google este hilo.
En primer lugar, descubrí que esto es en realidad un error en Qt registrado en 2011 y aún abierto: https://bugreports.qt.io/browse/QTBUG-16592
Le agregué mi voto (¡y usted también debería hacerlo!). Luego decidí probar usando QTableView en lugar de QListView y, sorprendentemente, logré que funcionara, o eso parece.
A diferencia de QListView, QTableView solo cambia el tamaño de las filas por solicitud explícita, llamando a resizeRowToContents (rowNum). Entonces, el truco es llamarlo justo a tiempo para las filas que se vuelven visibles en la ventana gráfica.
Esto es lo que hice:
Heredar de QTableView (llamémoslo MyTableView)
Reemplace QListView con MyTableView e inicialícelo así en el constructor. Esto asigna un delegado de elemento personalizado, oculta los encabezados de la tabla y aplica el modo de selección "por fila":
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
}
- En MyTableView, agregue un campo privado QItemSelection y una función pública que calcula alturas reales de filas, pero solo aquellas que están visibles actualmente:
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";
}
}
}
Nota: si las alturas de los elementos dependen del ancho del control, debe anular
resizeEvent(), borrar_itemsWithKnownHeightsy llamar a updateVisibleRowsHeight () nuevamente.Llame a updateVisibleRowHeights () después de asignar un modelo a la instancia de MyTableView, para que la vista inicial sea correcta:
table.setModel(&myModel);
table.updateVisibleRowHeights();
De hecho, debería hacerse en algún método de MyTableView que reaccione a los cambios del modelo, pero lo dejaré como un ejercicio.
- Ahora todo lo que queda es tener algo llamado updateRowHeights cada vez que cambia la posición de desplazamiento vertical de la tabla. Entonces necesitamos agregar lo siguiente al constructor de MyTableView:
connect(verticalScrollBar(), &QScrollBar::valueChanged, [this](int) {
updateRowHeights();
});
Listo, ¡funciona muy rápido incluso con modelos de 100,000 artículos! ¡Y el inicio es instantáneo!
Aquí puede encontrar un ejemplo básico de prueba de concepto de esta técnica (usando QTableView puro en lugar de subclase): https://github.com/ajenter/qt_hugelistview/blob/tableview-experiment/src/main.cpp
Advertencia: esta técnica aún no está probada en batalla y puede contener algunos problemas aún desconocidos. ¡Usar bajo su propio riesgo!