PDA

View Full Version : Emit endResetModel using a QTimer



stefanadelbert
9th June 2010, 01:48
Existing setup:

I have a custom QAbstractListModel with an attached QTableView. The model receives data from two sources - one source's data goes into a vector and the other source's data goes into a map. The model then provides data for column 1 from the vector and the data for column 2 from the map.

Each time data arrives from source 2 it gets inserted into the map and the beginResetModel/endResetModel signals are emitted.

The problem:

A flood of data from source 2 arrives. Each bit of data that arrives results in beginResetModel/endResetModel being emitted, which results in the QTableView doing a lot of work and the GUI locks up for a while while the data is being processed.

It's not feasible to emit dataChanged instead of beginResetModel/endResetModel because that would require a linear search through the vector to determine which the QModelIndex of the cell to be changed.

My current solution:

Each time some data arrives from source 2, I emit beginResetModel. Then I start/restart a timer that runs for 500ms. When the timer completes the endResetModel signal is emitted. In this way if a flood of data arrives from source 2, endResetModel is only called once after all the data has been loaded into the model (assuming that the time gap between data arriving is less than 500ms of course).

Possible problem with my solution:

I emit beginResetModel every time I receive data from source 2, but I don't emit endResetModel every time. Does there always need to be an endResetModel for every beginResetModel?

Has anyone got any ideas on how to improve this?

tbscope
9th June 2010, 04:45
Call both beginResetModel and endResetModel once.

Why not use insertRows? Why do you need to reset the model everytime you get new data?

stefanadelbert
9th June 2010, 06:41
Thanks for the response

When data arrives from source 2 it is not adding a new row, but rather adding information to or updating information on an existing row. Therefore insertRows will not work.

I reset the whole model each time I get new information from source 2 because it is expensive for me to establish which row of the model would change as a result of the new data. When I say expensive, I mean that I would need to do a search (linear time) through a vector to establish the row and I don't want to have to do this every time. Of course, I don't want to have to begin/endResetModel each time new data from source 2 arrives either as this causes a lot of work for the QTableView.

So this is what I'm trying:

beginResetModel
update model data
[beginResetModel, update model data] X 100
endResetModel


i.e. beginResetModel could be emitted 100 times without endResetModel being emitted. And then endResetModel would be emitted once.

This is the relevant code that creates the endResetModel timer and handles data from source 2:


MyModel::MyModel()
{
...
source2Timer = new QTimer(this);
source2Timer->setInterval(500);
source2Timer->setSingleShot(true);
connect(source2Timer, SIGNAL(timeout()), this, SLOT(endResetModel()));
}

void MyModel::HandleSource2Data(const DataItem& item)
{
beginResetModel();
source2Cache.Add(item);
source2Timer->start();
}

Or do I NEED to emit an endResetModel for every beginResetModel signal emitted? Or is what I'm planning OK?

tbscope
9th June 2010, 06:53
How many rows do you want to update every 500ms ?

stefanadelbert
9th June 2010, 07:14
It's possible that there are 2000 rows in the model (i.e. the vector (source 1) has 2000 elements and the map (source 2) has up to 2000 elements). It is possible that I get ~1000 updates from source 2 in the space of 500ms, i.e. 1000 rows would need to be updated in the model. But it's not necessarily critical that the QTableView updates after every model update. As long as the view updates within 500ms of the last source 2 update, all is well.

Note: This is not a periodic update every 500ms - it's more of a once off.

Note: I edited the source code in my previous post to include "source2Timer->setSingleShot(true);"

stefanadelbert
9th June 2010, 08:34
I think I've managed to make this work, but not quite how I originally intended. My code now looks something like this:



class MyModel {
MyModel();
...
slots:
void EndResetModel();
...
void MyModel::HandleSource2Data(const DataItem& item)
...
}

MyModel::MyModel()
{
...
source2Timer = new QTimer(this);
source2Timer->setInterval(500);
source2Timer->setSingleShot(true);
connect(source2Timer, SIGNAL(timeout()), this, SLOT(EndResetModel()));
}

void MyModel::EndResetModel()
{
endResetModel();
}

void MyModel::HandleSource2Data(const DataItem& item)
{
beginResetModel();
source2Cache.Add(item);
source2Timer->start();
}

endResetModel() is not a slot, its just a protected function. So the timer can't call endResetModel() directly; it needs to call a custom slot that in turn calls endResetModel().