When to emit dataChanged from a QAbstractItemModel - qt

In Qt, I have a model subclassing QAbstractItemModel - it's a tree displayed in a QTreeView.
The model supports various forms of change which all work OK. The two of relevance are:
1) Some data in a small number of related rows changes
2) A visualisation change means that the majority of rows should change their formatting - in particular they have a change of background highlighting. Their DisplayRole data does not change.
The current design deals with both of these in the same way: for every row that has any change the model emits dataChanged(start_of_row_index,end_of_row_index). I emit the signal for both parent rows that change and for any of their children that have changed.
However, this performs badly in case 2 as the model gets big: a very large number of dataChanged signals are emitted.
I have changed the code so that in case 2 the model emits dataChanged only for the (single) row that is the parent of the entire tree.
This still appears to work correctly but does not accord with my understanding of the responsibilities of the model. But I suspect I may be wrong.
Perhaps I am misunderstanding the dataChanged signal? Does it actually cause the view to update all children as well as the specified range? Or can I avoid emitting dataChanged when it is not the DisplayRole that is changing?
Edited with my progress so far
As Jan points out, I ought to emit dataChanged either for most or all of the rows in case 2.
My code originally did this by emitting dataChanged for every changed row but this is too expensive - the view takes too long to process all these signals.
A possible solution could be to aggregate the dataChanged signal for any contiguous blocks of changed rows but this will still not perform well when, for example, every other row has changed - it would still emit too many signals.
Ideally I would like to just tell the view to consider all data as potentially changed (but all indexes still valid - the layout unchanged). This does not seem to be possible with a single signal.
Because of a quirk of the QTreeView class, it is possible (though incorrect according to the spec) to emit only one dataChanged(tl,br) as long as tl != br. I had this working and it passed our testing but left me nervous.
I have settled for now on a version which traverses the tree and emits a single dataChanged(tl,br) for every parent (with tl,br spanning all the children of that parent). This conforms to the model/view protocol and for our models it typically reduces the number of signals by about a factor of 10.
It does not seem ideal however. Any other suggestions anyone?

You are expected to let your views know whenever any data gets changed. This "letting know" can happen through multiple ways; emitting dataChanged is the most common one when the structure of the indexes has not changed; others are the "serious" ones like modelReset or layoutChanged. By a coincidence, some of the Qt's views are able to pick up changes even without dataChanged on e.g. a mouseover, but you aren't supposed to rely on that. It's an implementation detail and a subject to change.
To answer the final bit of your question, yes, dataChanged must be emitted whenever any data returned from the QAIM::data() changes, even if it's "just" some other role than Qt::DisplayRole.
You're citing performance problems. What are the hard numbers -- are you actually getting any measurable slowdown, or are you just prematurely worried that this might be a problem later on? Are you aware of the fact that you can use both arguments to the dataChanged to signal a change over a big matrix of indexes?
EDIT:
A couple more things to try:
Make sure that your view does not request extra data. For example, unless you set the QTreeView's uniformRowHeights (IIRC), the view will have to execute O(n) calls for each dataChanged signal, leading to O(n^2) complexity. That's bad.
If you are really sure that there's no way around this, you might get away by combining the layoutAboutToBeChanged, updatePersistentIndexes and layoutChanged. As you are not actually changing the structure of your indexes, this might be rather cheap. However, the optimization opportunity in the previous point is still worthwhile taking.

Related

Having Qt read-only model and view in different threads

As I learn from Qt docs, in Qt Model/View framework, models and their attached views should live in the same (GUI) thread. This might lead to some undesirable effects like the following. I have subclassed QAbstractTableModel and implemented the required virtual functions. Internally, the model makes queries into sqlite database which have lots of records, and provides the data accordingly to attached views via reimplemented data() function.
Now, in GUI I have a QTableView, which I attached to that model. Also I have a QLineEdit input field. Printing text in this field emits a textChanged() signal, which is connected to a (custom) query() slot of the model. In this way, typing a new character in the input field should update the table with the records matching the typed phrase.
Well, as my database is large, I do not expect the table updating to follow immediately after typing another letter - updating waits for the query to complete which might take a second or two.
But what bothers me is that since I am obliged to have model and table in the same GUI thread, the input field also friezes after each letter untill the table is updated. I would like to make it such that I might type the phrase without freezing effect, and let wait the table to update. Notifying the model only when the entire phrase is typed with pressing Enter is not an option for me - I need the textChanged() signal to work.
So then I thought - would it be of a big offend to Qt if I ignore the docs and put the model into a non-GUI thread? To my surprise, it worked! Now typing does not freeze, and the program does not crash (at least for now).
So my question is - is it nonetheless unsafe to use model in a non-GUI thread, and my program might suddenly crash any other day? I also should mention that I want to use the model in a read-only way. If I need to change data underlying the model, I will not do it using the view/delegates, I will just send appropriate signals to the model's thread, and all changes will be performed within that thread.
Imagine this example of removing the last row:
Synchronous (same thread)
emit beginRemoveRows(int r = last row)
view reacts and removes references to r
remove r from model
endRemoveRows()
view knows it may repaint
Asynchronous (different threads)
emit beginRemoveRows(r)
remove r from model
endRemoveRows()
Both signals are in the event queue of the GUI thread.
If the GUI event queue contains a repaint event before the beginRemoveRows(), the view will call model->data(r) and your program will likely crash*.
(*) Or at least run into the safeties of your data() implementation, but there are other things under the hood like QPersistentModelIndex which you don't control...

QAbstractItemView - fetchMoreTimer

I created dynamic loading model that supplies data to a modified qTreeView. Everything seemed to work fine. Until I realized that sometimes the GUI "waits" for something.
It showed up that fetchMore of my model is called every now and then. I found out that there is "fetchMoreTimer" running in the QAbstractItemView (base class of most views)
rowInserted AND/OR updateGeometires gets called and starts the timer.
when the timer ticks it triggers timerEvent
timerEvent results in doItemsLayout()
doItemsLayout() ask canFetchMore (it sure can! I am dynamically loading millions of rows) and so the model fetches more.
And some time later the timer ticks again... resulting in virtually endless repaint of treeView (in main thread as all GUI opperations). This prevents for example load/save that is memory expensive. (saving loading data associated with millions of rows of data.)
Can someone suggest how to disable the fetchMoreTimer (of course in QAbstractItemViewPrivate :/)
I tried subclassing the timerEvent. But unfortunately about 6 different timers triggers the event and I don't know how to find out which I can ignore.
Some suggestions?

Dynamic Tree Model (Qt)

I use a QAbstractItemModel to represent a tree model (of up to a few hounded items). The data itself is dynamic, at any time nodes may appear or disappear, values (or other roles) may change.
Making changes to the model is easy; I am wondering how to efficiently emit the signals in order to notify a QTreeView of the changes (most of it's nodes are collapsed).
At any given time multiple changes may occur simultaneously (row insertions and/or deletions).
Using beginInsertRows / endInsertRows / beginRemoveRows / endRemoveRows - shouldn't there be a method to notify the view of multiple changes?
In terms of performance, what would be the best strategy? For example, starting at the leaves and going up to the root / for each node - bottom to top (vs top to botton) / deletions before insertions / etc.
Would beginResetModel / endResetModel necessarily be less efficient?
Is there any advantage for using QStandardItemModel? (for this specific case).
Yes. The method of notifying everyone of disjoint removals/additions is to emit multiple signals. It would, in most cases, cause more overhead to pass some complex data structure instead of just the parent index and delimiting row/column indices.
You should only notify about the removal/addition of an item closes to the root. It makes no sense to notify about removal of children if their parent is subsequently to vanish. The notification about the parent means that the children, obviously, aren't there anymore.
It's not only about efficiency, but also about state. A model reset resets the view state. The view, upon receiving a reset, can't but assume that it got an entirely new, unrelated model - so you lose selections, expanded/collapsed state, etc. There's no way for a view to act any other way in face of a reset. Doing otherwise would require a view to keep its own copy of the model's contents.
Since a model reset implies relayout of all of the items, and it can be a very expensive thing to do, you should only do it if, in aggregate, more than 50% of the original items are changed (removed/replaced/added).
No, there is no advantage, and unless you store your data as variants, using QStandardItemModel will always end up with larger memory overhead. It is a convenience class that makes sense if it fits your needs exactly. In fact, if you're not careful about how you use it, it will work worse.
For example, if you remove an item by iterating depth-first and removing the farthest children first, then the QStandardItemModel can't foresee the future - namely, that you really want to remove the common ancestor of all of those children, and will emit a lot of change events unnecessarily. You can deal with it properly in your own model, or if you simply remove the common parent without touching the children - as they will be implicitly removed, too.

Disconnecting slots from signals

PySide and PyQt employ Qt signal-slot mechanism with which we can connect any/multiple signals to any/multiple slots as far as the trasmistted data types match.
The signalling object has some knowledge of the receiving slots, e.g. knows their number via method receivers or the signal can disconnect from the receiving slots via its disconnect method.
My problem relates to the oposite direction - e.g. does a slot know to which signals it is connected? Can a slot disconnect from the signals?
UPDATE: So why I am asking this - I have an object that performs some calculation. The calculation is defined by a user editable Python expression. The expression is parsed and necessary data sources are identified from this... The calculation object (acts as a slot) then connects to these data sources (they act as signals) and once the data sources produce/update a value, this fact is signalled to the slot and the expression is reevaluated. And when the expression is changed by a user, it needs to be parsed again and disconnected from the existing signals (i.e. data sources) and connect to new data sources. You can imagine it is something like a formula in Excel that is connected to other cells.
I know there are a few ways to work around this, e.g. keeping track of connections manually (well, this is extra work) or deleting the expression object and creating a new one everytime it is changed (seems not good enough, because user might want to trace back the calculation data sources and this will not help). But I was curious if this can be solved purely using simple signal-slot mechanism. In other words, I am not interested in any workarounds... I know of them and will use them I signals-slots will not help here. :)
The approach you propose forces a very close relationship between the concrete data widgets and the calculation engine. You mingle UI with the calculations. This makes it much harder than it needs to be.
What you could try instead is the model-view approach. The model would be a simple implementation of QAbstractTableModel. The view would be the individual data-entry widgets mapped to the model's cells using QDataWidgetMapper. The calculation engine would access only the model, completely unaware of how the model is modified by the widgets. This make life easier.
The calculation object can connect to the model using just one dataChanged signal and it will be notified of changes to any of the variables. You can easily pass both the value and the variable name by having two columns in the table.
The implementation of the model can be very simple, you can have a list of strings for the variable names in the first column, and a list of variants for the second column. The model must correctly emit the dataChanged signal whenever setData is called, of course.

QTreeView: how to be inform of the beginning/end of item expansion?

I have a QtreeView that is displaying file lists (using a model derived from QFileSystemModel). As the building of the file list needs a lot of time (I must read the content of each file to determine if the file is visible or not) I want to display the wait cursor during the analysis process. The wait cursor must starts when the user select an item (directory), and stays as long as all the list is not displayed.
For this I did a lot of tries:
using the expanded signal. But this signal is not related to drawing. Hence it arrves almost immediately,
managing the cursor in data() function of my model. But in this case I have an horrible blinking cursor,
managing the cursor by overridding the painEvent. In this case I have a small blinking, and the cursor appears lately
...
So, none of my "solutions" is perfect. Hence, do you have a way to do what I want?
Thanks a lot.
One more idea, but i did't try it:
Try to check QAbstractItemView::State in a timer after QTreeView::expanded() signal.

Resources