Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

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;
                }
            }
        }
    }
    

Log in to reply