QtQuick TableView lazy loading / fetchMore support
-
Hi folks,
I'm currenlty trying try something out with the new (at least for me) Qt Qtuick TableView Component which exists since Qt 5.12.
So I've written a simple model inheriting QAbstractTableModel and wanted to use lazy loading, so use canFetchMore and fetchMore in my model and let the view request new data, if the user scrolled down.
It seems, that TableView does not event call these methods, it only displays the first rows returned by rowCount.
If I change the view to ListView, these methods gets called as expected.See the example code below: Page1 won't fetch any new data, except the ListView from Page2 requests it (they share the same model added as context property). Code can be copy-pasted to the stackview project in QtCreator.
Did I miss anything or is this just not supported?
Any help or just a "no, you didn't miss anything" would be great :)
P.S.: I tried Qt 5.12.7 on Windows and 5.13.2 on Linux.
lazymodel.cpp
LazyModel::LazyModel(QObject* parent): QAbstractTableModel(parent), m_curcount(20), m_totcount(100) { } QVariant LazyModel::headerData(int section, Qt::Orientation orientation, int role) const { return QAbstractTableModel::headerData(section, orientation, role); } int LazyModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; return m_curcount; } int LazyModel::columnCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; return 3; } QVariant LazyModel::data(const QModelIndex &index, int role) const { if (index.isValid() && role == Qt::DisplayRole) { return QString("%1 %2").arg(index.row()).arg(index.column()); } return QVariant(); } bool LazyModel::canFetchMore(const QModelIndex& parent) const { qDebug() << "canFetchMore"; if (parent.isValid()) return false; return m_curcount < m_totcount; } void LazyModel::fetchMore(const QModelIndex& parent) { if (parent.isValid()) return; int remainder = m_totcount - m_curcount; int itemstofetch = qMin(2, remainder); beginInsertRows(QModelIndex(), m_curcount, m_curcount + itemstofetch -1); m_curcount += itemstofetch; endInsertRows(); } QHash<int, QByteArray> LazyModel::roleNames() const { QHash<int, QByteArray> roles; roles[Qt::DisplayRole] = "display"; return roles; }
lazymodel.h
class LazyModel: public QAbstractTableModel { Q_OBJECT public: LazyModel(QObject* parent = nullptr); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; // Basic functionality: int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool canFetchMore(const QModelIndex& parent) const override; void fetchMore(const QModelIndex& parent) override; QHash<int, QByteArray> roleNames() const override; private: int m_curcount; int m_totcount; };
PageForm1.ui.qml
Page { width: 600 height: 400 title: qsTr("Page 1") function rowHeight(row) { return root.height / 10 } function columnWidth(col) { return 100 } TableView { id: cattable anchors.fill: parent flickableDirection: Flickable.VerticalFlick rowHeightProvider: rowHeight columnWidthProvider: columnWidth model: lazymodel delegate: Label { text: display } ScrollBar.vertical: ScrollBar { width: 40 } } }
PageForm2.ui.qml
Page { width: 600 height: 400 title: qsTr("Page 2") ListView { anchors.fill: parent flickableDirection: Flickable.VerticalFlick model: lazymodel delegate: Label { text: display font.pixelSize: 24 } } }
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include "lazymodel.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; LazyModel model; engine.rootContext()->setContextProperty("lazymodel", &model); const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
-
I found a workaround by overloading canFetchMore and fetchMore without arguments:
Q_INVOKABLE bool LazyModel::canFetchMore() const { return canFetchMore(QModelIndex()); } Q_INVOKABLE void LazyModel::fetchMore() { return fetchMore(QModelIndex()); }
Then I can call these methods manually if necessary: a) when the content is scrolled down and b) initially to fill the "initial space" (contentHeight). But I have to calculate contentHeight with the models rowCount and the rowHeight myself, because the TableViews contentHeight is not updated as it seems.
I'm not sure yet about the perfomance.
Page1Form.ui.qml
Page { id: root width: 600 height: 400 title: qsTr("Page 1") function rowHeight(row) { return root.height / 10 } function columnWidth(col) { return 100 } TableView { id: cattable anchors.fill: parent flickableDirection: Flickable.VerticalFlick rowHeightProvider: rowHeight columnWidthProvider: columnWidth model: lazymodel delegate: Label { text: display } onContentYChanged: { if (contentY + height >= contentHeight) { if (model.canFetchMore()) { model.fetchMore(); } } } ScrollBar.vertical: ScrollBar { width: 40 } } Component.onCompleted: { while (lazymodel.rowCount() * rowHeight(-1) <= cattable.height) { if (lazymodel.canFetchMore()) { lazymodel.fetchMore(); } else { break; } } } }