[Interest] Models and aborting structural changes

Elvis Stansvik elvstone at gmail.com
Thu Jun 4 09:23:25 CEST 2020

Den tors 4 juni 2020 kl 09:00 skrev Elvis Stansvik <elvstone at gmail.com>:
> Den ons 3 juni 2020 10:38Jonathan Purol <contact at folling.de> skrev:
>> Hello everyone,
>> I've been in a bit of a tough spot recently with `QAbstractItemModel`.
>> I'll try to delineate my situation, then my problem, and then the
>> potential solutions and why I think they're all inferior to what I wish
>> to have.
>> We have a desktop applications using QtWidgets, however instead of a
>> data model we obtain our data from an "external source" (this is a
>> little complicated in our use case, but you can think of it as a remote
>> server for all intents and purposes).
>> Now the server emits events for changes in the underlying data model.
>> For example there is an event for `itemAdded` with all appropriate data
>> points being passed to us via a web socket.
>> This is all fine and dandy.
>> The problem arises when I try to now connect this to the model
>> representing all `Item` objects.
>> Qt requires both `beginInsertRows` and `endInsertRows` to be called when
>> these changes are made. Furthermore it requires the underlying data to
>> really only be changed in-between those calls. I.e. `rowCount` should
>> return something different at the time `beginInsertRows` is called than
>> at the time `endInsertRows` is called. So far so good. But we don't
>> really have that in-between point. We only get notified when the change
>> has already happened.
>> There are a few solutions I could see here:
>> 1. Keep a local cache of all of the data, update it incrementally when
>> changes come through with this pattern:
>> - change events arrives
>> - emit "begin change"
>> - update internal data
>> - emit "end change"
>> 2. Keep a local cache of all the data on a per-model basis then repeat
>> as can be seen above
>> I dislike both of these changes, even if they do solve the issue
>> flawlessly. The reason for that is that it is quite a bit of overhead.
>> Both regarding code complexity as well as regarding memory usage and
>> performance. Don't get me wrong, having a cache is important, and we
>> will have one as well. What doesn't stick right with me is a cache that
>> in turn keeps track of all the data all the time.
>> Now you might say "why not just have the server emit both a "before" and
>> a "after" change event for whatever happens.
>> Yeah, I would love to do that - but with Qt's current setup it just
>> isn't possible.
>> What Qt is missing is an `abortInsertRows` function to reset the model's
>> internal state.
>> There are many, many things that can go wrong in our setup, even IF the
>> data was successfully updated. And if we cannot tell Qt to revert the
>> changes in case such an error occurs we cannot possible use this approach.
>> The only solution I would see here would be to call
>> `begin/endResetModel` in case an error occurs, but that sounds like
>> trying to put a nail into a coffin with a wrecking ball.
> I understand your problem. I've not been in your situation, but how about
> - Change server to emit before and after events, plus a cancel event.
> - Upon receiving a before event, call beginInsertRows,
> - Upon receiving an after event, call endInsertRows,
> - Upon receiving a cancel event, call endInsertRows, then call beginRemoveRows, remove the cancelled rows, then call endRemoveRows.
> Could that work? I don't think the models would ever show the cancelled data, as control never returned to the event loop during the dance in the last step.

Thinking a bit more (and I'm no expert), if I understand your setup
correctly, I still think there's a race that could confuse your views.

If I understand you right, your model always reports "live" data from
the server, which I presume means your data(..) returns data fetched
from the server.

If so, then I think this scenario could happen (?):

1. View requests some data at index X, and gets back data Y.
2. Something happens on the server so the data changes.
3. The server sends out a change event.
4. View again requests some data at index X, but gets back data Z (!).
5. The change event arrives at your application.

This is a situation in which the data returned at some index changes
from the POV of the view, with no beginInsertRows/endInsertRows in
between, which I think is a breach of the Qt model/view contract.

I may be wrong though, so maybe someone more knowledgeable can step in
an confirm/deny.


> Cheers,
> Elvis
>> My questions now are as follows:
>> Is there any chance `abort*` methods would be added in the future?
>> Could I perhaps add such a method myself?
>> Is it safe to just call `endInsertRows` when no changes were made?
>> Is there a simple solution I overlooked?
>> Sincerely,
>> Jonathan Purol
>> _______________________________________________
>> Interest mailing list
>> Interest at qt-project.org
>> https://lists.qt-project.org/listinfo/interest

More information about the Interest mailing list