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...
Related
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?
I have a small IDE for a modeling language I wrote, implemented in PyQt/PySide, and am trying to implement a code navigator that let's you jump to different sections in the file being edited.
The current implementation is: (1) connect to QPlainTextEditor.textChanged, (2) any time a change is made, (sloppily) parse the file and update the navigator pane
It seems to work OK, but I'm worried this could cause major performance issues for large files on slower systems, in particular if more stuff is connected to textChanged in the future.
My question: Has anybody here implemented a delayed reaction to events, so that multiple events (i.e. keystrokes) within a short period only trigger a single update (say once per second)? And is there a proper QT way of doing this?
Thanks,
Michael
You can try using timers if you want some "delay".
There would be 2 ways to use them (with different results).
One is only parse after no input has been done for a certain amount of time
NOTE: I only know C++ Qt but I assume the same things are valid for pyqt so this is kind of "pseudocode" I hope you get the concept though.
QTimer timer; //somewhere
timer.setSingleShot(true); //only fire once
connect(timer,QTimer::timeout(),OnTimerDone(...);
OnTextChanged(...)
{
timer.start(500); //wait 500ms
}
OnTimerDone(...)
{
DoStuff(...);
}
This will restart the timer every input, so when you call that and the timer is not done the timeout signal is not emitted. When no input is done for an amount of time the timer timeouts and you parse the file.
The second option would be to have a periodic timer running (singleShot(false)).
Just start the timer for like each second. and timeout will be called once a second. You can combine that with a variable which you set to true when the input changes and to false when the file is parsed. So you avoid parsing when nothing has changed.
In C++Qt you won't have to worry about multi-threading because the slot gets called in the GUI thread. I assume it is the same for python but you should probably check this.
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.
It is known that QWidget::paintEvent is triggered automatically the moment widget becomes visible or any event from the basic window happens. What should I do if I only want the paintEvent to be issued in response to update()?
It'd make no sense for a paintEvent not to be issued whenever the widget needs to be painted. Your reason to demand such an option means that you're trying to use the Qt API in a way it wasn't meant to be used. I can't quite imagine yet a particular design that would lead you to such use, sorry about that.
So, the only thing I can tell you is how you'd use update(). The idiomatic way of handling widget updates is as follows:
The source of data that the widget uses is updated. For example, the text or some variable affecting the visible contents is changed. Often this data is a Q_PROPERTY of the widget. The setter function is updating the member variable that stores the value and calls update(). The setter should not perform any expensive computations - they should be deferred until the paint event.
If the properties are changed multiple times before the event loop has a chance to run, the update events get coalesced. Internally, a call to update() results in posting an event to the event queue of the GUI thread. If there already is an update event in the queue for the given widget, the events get coalesced. The following invariant is preserved: at any given time, there can only be one update event for any particular widget in an event queue.
When the control returns to the event loop, the update event gets dispatched to the widget, ending up in calling your reimplementation of QWidget::paintEvent(...). This implementation should do the calculations necessary to paint the widget, and do the actual painting. If the calculations are extensive, they should be relegated to a worker thread.
Example
Let's say an application is receiving data from a serial port, modeled as a QIODevice (a QSerialPort is one). You could do as follows:
Connect QIODevice's readyRead signal to a slot in a parser QObject.
The slot receives and parses the data. It then emits a signal with processed data (for example, a vector of floating point values). This signal is connected to a newData slot in the widget.
The newData slot adds the data as-is to a queue, and schedules an update(). This is very fast if you're using Qt's data structures, or if your data class is modeled after them and uses implicit sharing with copy-on-write.
The update() dequeues all data sets and plots them. The QWidget::scroll() method comes handy for scrolling plots.
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.