2

I have a custom model (extends QAbstractTableModel) where row data is added pragmatically. For performance reasons, I am using the fetch functionality (canFetchMore, fetchMore) to avoid UI lag when items are not visible.

When a new row is inserted, I do not want to trigger QAbstractItemModel::rowsInserted. But if those rows would be visible in the view, I'd like them to appear automatically. They do appear if the user interacts with the view, e.g. selecting the last item (the view calls 'canFetchMore' and will call fetchMore only if those items would be visible).

How do I signal to the view that more rows are available to display, but need to be fetched?

jtooker
  • 1,043
  • 1
  • 8
  • 22
  • My only thought is to emit `rowsInserted` on the first `n` items and then let the view fetch more later, but it is possible the user's view has space for more than `n` items. – jtooker Jun 25 '18 at 19:08
  • Are you sure that the standard views don't ignore `rowsInserted` beyond the last fetched row? – Kuba hasn't forgotten Monica Jun 25 '18 at 19:37
  • @KubaOber, yes, I tried that. The vertical header added sections (rows) for each insert, but the data lookup gave back empty data (since MyModel::index() gave invalid indices - which was correct). But aside from extra empty rows being added, a crash eventually occurs. – jtooker Jun 25 '18 at 20:46
  • Are you properly using `beginInsertRows()` and `endInsertRows()`? I'd guess the rest will happen automatically. – Martin Hennings Jun 28 '18 at 12:27
  • @MartinHennings, yes. If I insert rows properly (where the rows emitted to correspond to my internal data structure), the view will always display them and `canFetchMore` will always return false. – jtooker Jun 28 '18 at 15:13

1 Answers1

1

Solution for Qt 5.6.1 - untested for other versions!

After digging around in the sources of QAbstractItemView, I found the simplest way to trigger fetchMore is to start the QAbstractItemViewPrivate's internal and undocumented fetchMoreTimer.

This is heavily implementation dependent and may change in future Qt versions!

Subclass your QAbstractItemView derivate (e.g. QListView, ...) to get access to one of the protected functions starting the timer:

class CFetchMoreListView : public QListView
{
    Q_OBJECT

public:
    explicit CFetchMoreListView(QWidget *parent = Q_NULLPTR)
        : QListView(parent)
    { }

    inline void CheckFetchMore()
    {
        int mode = 0;
        switch(mode)
        {
        case 0: // works - but I didn't check how much unneccessary updating it performs
            updateGeometries();
            break;

        case 1: // updates the view allright, but also loads items not currently in view
            verticalScrollbarValueChanged(verticalScrollBar()->maximum());
            break;

        case 2: // needs at least one item already inserted
            if(currentIndex().isValid())
            {
                QModelIndex const curr = currentIndex();
                currentChanged(curr, curr);
            }
            break;

        case 3: // leads to flicker
            setVisible(false);
            rowsInserted(QModelIndex(), 0, 0);
            setVisible(true);
            break;
        }
    }
};

Now, after adding items to your model, you can call view->CheckFetchMore();

Edit

It might be possible to override rowsInserted(...) and only call the base implementation if the newly added rows would be visible.
But that seems kludgy as well.

void QListView::rowsInserted(const QModelIndex &parent, int start, int end)
{
    Q_D(QListView);
    // ### be smarter about inserted items
    d->clear();
    d->doDelayedItemsLayout();
    QAbstractItemView::rowsInserted(parent, start, end);
}

(I love how the comment in the Qt code pinpoints your problem...)

Martin Hennings
  • 16,418
  • 9
  • 48
  • 68
  • I saw that timer, and agree with everything you said. I'm not a huge fan of this solution, but it may be the most robust. If that timer could be started _directly_, I'd say that would be a good answer. – jtooker Jul 05 '18 at 15:44
  • @jtooker `I'm not a huge fan of this solution` - me neither. If you already need to subclass your `QAbstractItemView`, there would also be the possibility to manually check if an update is neccessary. You could override `rowsInserted()`, see edit to my answer. – Martin Hennings Jul 06 '18 at 08:52