[Interest] QAbstractItemModel and lazy loading

Jan Kundrát jkt at flaska.net
Mon May 14 16:59:08 CEST 2012

On 05/11/12 20:25, Adam Light wrote:
> I have tried emitting SourceModel::dataChanged() when a node goes from
> not having children to having children, but that does not cause the view
> to update correctly. Emitting layoutAboutToBeChanged()/layoutChanged()
> *does* cause the view to be updated correctly, but often I get crashes
> when layoutChanged() is called (possibly related
> to https://bugreports.qt-project.org/browse/QTBUG-19261).

Hi Adam,
I'd strongly encourage to check your model using ModelTest [1]. The
trouble with Qt models is that any error you make will usually result in
subtle errors or segfaults at a later time.

The dataChanged() signal itself only informs the attached views/proxies
that the *contents* of an index (or an index range at one level) has
changed, but it says nothing about existence of children of the affected
indexes. That's what rowsInserted() and friends is for.

> What is my model supposed to do when a node in the model now has
> (unloaded) children to alert views using the model of the change? I
> can't use beginInsertRows() because the child rows are not yet actually
> inserted into the model. Emitting dataChanged() doesn't seem to have the
> desired effect.

Lazy loading can still work, even in this scenario. You're right that
there seems to be a slight asymmetry in the MVC API when dynamically
updating the model; unlike the hasChildren() method, there's nothing
like childrenArrived() signal and you really have to emulate that by
calling beginInsertRows etc. However, these items that you somehow have
to manufacture can easily be created by your model without actually
fetching their real data; that can be deferred to the time the model
gets aksed for them (through data() or any other relevant function in
your scenario).

In my use case (an IMAP e-mail client), user opening a mailbox results
in being asked for hasChildren() (or rowCount()) being called for an
index which correspond to a mailbox. The mailbox is not opened yet, so
my code returns 0 and queues a netowrk operation for finding out what is
in that mailbox. Some time later, when the mailbox is resynchronized so
that I know how many messages are in there, my model calls the
beginInsertRows()/endInsertRows() combination, but no actual message
data is downloaded yet. The QTreeView now knows how many rows of e-mails
to show, and begins asking for their data by calling my Model's data()
method on each of these indexes (actually just those of them which are
visible thanks to the view's uniformRowHeights). This means that when
users opens a mailbox with 50k messages, but only 20 of them are visible
on screen, I get requests for (some columns) x 20 of index data. That's
the time when my model begins downloading stuff from network.

Now, there are obviously use cases when it's too expensive to even
compute the amount of child indexes below some parent. In that case, I'd
probably return true from the hasChildren() method when the status of
children is not known. The Qt's views will call rowCount() when the view
gets expanded and the child items shall become visible. I'd probably
request your expensive operation from that rowCount() method and return
0; when the computation finishes and your result is ready, you shall
call the beginInsertRows() etc.

One last comment -- you've mentioned that your code observes the
internal state of the application and presents the data to other layers.
You shall be *very* careful in there; when you merely observe a state,
you shall make sure that you always return consistent data, which might
easily become a hard problem (one of my favorite examples is model
calling rowsInserted, which results in view calling rowCount() on me,
which leads to model checking an on-disk cache which populates its
in-memory tree structures, resulting in yet more rowsInserted signals --
but those cannot be emitted, simply because the view is already
processing that event and doing it recursively *will* fail and corrupt
memory -- that's just pure fun). So I'd strongly suggest to double-check
that whatever you report about the state of your data remains consistent
at all times. Delayed updates have served me well here.


[1] http://qt-project.org/wiki/Model_Test

Trojita, a fast e-mail client -- http://trojita.flaska.net/

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 262 bytes
Desc: OpenPGP digital signature
URL: <http://lists.qt-project.org/pipermail/interest/attachments/20120514/b001ddd1/attachment.sig>

More information about the Interest mailing list