custom QAbstractTableModel class updating QTableView
Hi all,
I already read a few threads on this forum dealing with similar issues but didn't provide "the" solution.
I built my own QAbstractTableModel class which fills a QTableView with data from a .csv file. The file is used for logging purposes and stores its (new) data every 10 minutes. The data is shown inside a QTableView but needs to be updated manually (button or closing and reopening the QDialog). What I want to achieve is that the view is automatically updated whenever new data is written to the .csv file.
The code snippets of my TableModel looks as follows:
Code:
int QCsvTableModel
::rowCount(const QModelIndex &parent
) const {
Q_UNUSED(parent);
return csvMatrix.rowCount();
}
int QCsvTableModel
::columnCount(const QModelIndex &parent
) const {
Q_UNUSED(parent);
return csvMatrix.columnCount();
}
{
if (index.isValid())
if (role == Qt::DisplayRole || role == Qt::EditRole)
return csvMatrix.at(index.row(), index.column());
}
{
if (index.isValid() && role == Qt::EditRole) {
csvMatrix.setValue(index.row(), index.column(), value.toString());
emit dataChanged(index,index); // No difference if dataChanged is emitted or not
return true;
}
return false;
}
MainWindow source:
Code:
MainWindow
::MainWindow(QWidget *parent
) : ui(new Ui::MainWindow)
{
ui->setupUi(this);
if (!fileName.isEmpty()) {
QCsvTableModel *model = new QCsvTableModel(this);
if (extension.toLower() == "csv") // known file extension
model->loadFromFile(fileName);
ui->tableView->setModel(model);
} // if fileName ..
connect(ui->tableView->model(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(onModelsDataChanged(const QModelIndex&, const QModelIndex&)));
//connect(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(onModelsDataChanged(const QModelIndex&, const QModelIndex&))); // Didn't work either ..
}
{
Q_UNUSED(topLeft);
Q_UNUSED(bottomRight);
qDebug() << "The data has changed." << endl;
}
But the dataChanged() signal isn't fired at all and "The data has changed." never shows up.
What am I missing? Any help is appreciated!
Michael
Re: custom QAbstractTableModel class updating QTableView
The code you posted does not contain any call to setData(), which is where you emit dataChanged().
Do you edit the table through the view?
Cheers,
_
Re: custom QAbstractTableModel class updating QTableView
Hi anda_skoa,
thank you for replying!!
the model gets it's data from the loadFromFile() method which looks as follows:
Code:
bool QCsvTableModel
::loadFromFile(const QString &fileName,
const QChar &delim
) {
beginResetModel();
csvMatrix.clear();
if (delim == 0) {
if (extension.toLower() == "csv")
}
else if (delim
== QChar('"')) return false; //the ONLY invalid delimiter is double quote (")
else
delimiter = delim;
if (!file.isOpen())
if (!file.
open(QFile::ReadOnly|QFile
::Text)) return false;
QList<QString> row;
while (true) {
in >> character;
if (in.atEnd()) {
if (lastCharacter == delimiter) //cases where last character is equal to the delimiter
temp = "";
checkString
(temp, row, csvMatrix, delimiter,
QChar('\n'));
break;
} else if (character
== delimiter || character
== QChar('\n')) checkString(temp, row, csvMatrix, delimiter, character);
else {
temp.append(character);
lastCharacter = character;
}
}
file.close();
in.flush();
in.reset();
return true;
endResetModel();
}
Re: custom QAbstractTableModel class updating QTableView
That looks mostly good, but you need to do the endResetModel() before you return :)
Regarding your original question: since this method doesn't emit dataChanged() (doesn't need to since it resets the model), why would the slot connected to the signal be called?
Cheers,
_
Re: custom QAbstractTableModel class updating QTableView
My bad! Did that already.
The code right now looks as follows:
Code:
bool QCsvTableModel
::loadFromFile(const QString &fileName,
const QChar &delim
) {
QCsvTableModel::beginResetModel();
csvMatrix.clear();
if (delim == 0) {
if (extension.toLower() == "csv")
}
else if (delim
== QChar('"')) return false; // The only invalid delimiter is double quote (")
else
delimiter = delim;
if (!file.isOpen())
if (!file.
open(QFile::ReadOnly|QFile
::Text)) return false;
QList<QString> row;
while (true) {
in >> character;
if (in.atEnd()) {
if (lastCharacter == delimiter) // Cases where last character is equal to the delimiter
temp = "";
checkString
(temp, row, csvMatrix, delimiter,
QChar('\n'));
break;
} else if (character
== delimiter || character
== QChar('\n')) checkString(temp, row, csvMatrix, delimiter, character);
else {
temp.append(character);
lastCharacter = character;
}
}
file.close();
in.flush();
in.reset();
QCsvTableModel::endResetModel();
return true;
}
I just need to update the data structure with replacing the csvMatrix variable since it gets all the model data. But apparently I'm completely lost on that one right now........
Re: custom QAbstractTableModel class updating QTableView
I am not sure I understand the current state correctly.
Still something you need help with?
Cheers,
_
Re: custom QAbstractTableModel class updating QTableView
Well yes. The auto-update still doesn't work since I'm not able to figure out what else I'm missing..
Re: custom QAbstractTableModel class updating QTableView
But the initial loading works?
Are you calling the same load method for updating or do you use something else?
Cheers,
_
Re: custom QAbstractTableModel class updating QTableView
The data shows up in my QTableView correctly but still when the data of the csv file changes (doesn't matter if that happens outside of the qt app or with a button which adds new data) it doesn't update/refresh itself. It only refreshes if the app is restarted or if the QDialog is closed and reopened.
Re: custom QAbstractTableModel class updating QTableView
How do you handle these two cases?
Do you watch the file for changes and then call the load method?
What do you do in the slot connected to the button? Also reload the file?
Cheers,
_
Re: custom QAbstractTableModel class updating QTableView
When opening the dialog I've connected the dataChanged() signal to a slot which shows a debug message in the console. the connect() call returns true in debug.
Code:
{
...
connect(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(onModelsDataChanged(const QModelIndex&, const QModelIndex&)));
...
}
{
Q_UNUSED(topLeft);
Q_UNUSED(bottomRight);
qDebug() << "YES" << endl;
// model->loadFromFile(fileName); // fileName = path/to/value.csv
// ui->tableView->setModel(model);
}
The commented lines (reloading the model) didn't make any difference.
The button just increments a number when it's clicked and writes it to the file:
Code:
void Dialog::onButtonPush()
{
QFile log(":/value.csv");
x++;
if (log.
open(QFile::WriteOnly |
QFile::Truncate)) { stream << "Clicked button number;" << x << endl;
ui
->pushButton
->setText
(QString::number(x
));
}
log.flush();
log.close();
}
Button and tableview are on the same dialog window and the tableview is supposed to be updated as soon as the data changes - e.g. every time the button is clicked. The updating doesn't work though. :/
Re: custom QAbstractTableModel class updating QTableView
I don't understand this ":/value.csv" filename. That is the name for a file that would ordinarily be compiled into your program as a resource. You can't write to it from a logging process, because it doesn't really exist as a disk file. So if you keep trying to load the same file (from resources), of course the content never changes.
As anda_skoa has asked, where is your code for watching the log file on disk and reacting if it changes? You should have a QFileSystemWatcher or some other object that is watching this file, and slot connected to its signal that tells your app when the file has changed. Then you load it into your model using the name of the log file on disk, not the name of the file from the resources.
Re: custom QAbstractTableModel class updating QTableView
Hi d_stranz,
thanks for your reply.
I've got a logger class which writes data into that csv file lying on my fs. You're right with the notation - just wanted to shorten it. :)
The path to the file is /home/micha/qt-app/value.csv. In my case the delimiter of the csv file is ";" instead of ",".
The file is loaded correctly into my tableview and as said i just want to update the tableview during runtime which should work with e.g. the dataChanged() signal. New data is written into that file correctly as well (when I open the file from desktop) or when I restart the application the new data is shown correctly in the tableview underneath the "old" data.
My only problem is that it's not inserted during runtime. The tableview is not showing it until I load the model into it again manually via pushbutton or whatever. I've read a lot about the dataChanged() signal which should be fired as soon as the data (row / column change) of the file changes. Somehow one can connect it to repaint, update, whatever the tableview to automatically show newly added data during runtime. And that is what's not working in my case and I simply don't know why.. :(
Re: custom QAbstractTableModel class updating QTableView
The dataChanged() signal applies only to the model, not to your log file. How is your model being notified that the data in the file is changed? What is causing your model to reload the data from the file (besides your manual push button method)?
I don't see anything in the code you have posted that indicates your program is watching the disk file for changes. There is no automatic connection between the disk file and your model. You have to write some code to watch the disk file that will trigger your model to reload it when it is changed.
Re: custom QAbstractTableModel class updating QTableView
Quote:
Originally Posted by
MichaH
When opening the dialog I've connected the dataChanged() signal to a slot which shows a debug message in the console. the connect() call returns true in debug.
And you are not emitting it in any method that you are executing.
How is a signal supposed to happen if its emit is not on a code path that is being executed?
Quote:
Originally Posted by
MichaH
The commented lines (reloading the model) didn't make any difference.
Of course that didn't make any difference, there is not a single emit dataChanged() in your load method.
The load method resets the model, if you want to trigger on that connect to modelReset().
Quote:
Originally Posted by
MichaH
The button just increments a number when it's clicked and writes it to the file:
No call on the model, so how would the model emit a signal?
Cheers,
_
Re: custom QAbstractTableModel class updating QTableView
That's embarrassing... you're absolutely right! a QFileSystemWatcher could do the trick.
Thank you both so much for your efforts and replies!!! Maybe I'll get back to you if I get stuck on this! :)
Re: custom QAbstractTableModel class updating QTableView
Quote:
Originally Posted by
MichaH
The file is loaded correctly into my tableview and as said i just want to update the tableview during runtime which should work with e.g. the dataChanged() signal.
The only code you have posted so far that contains an emit for that signal is setData(), but you have not posted any code that calls setData().
As far as we can tell you just never execute any code that emits dataChanged()
Quote:
Originally Posted by
MichaH
My only problem is that it's not inserted during runtime.
You haven't posted any code that would insert data.
Quote:
Originally Posted by
MichaH
I've read a lot about the dataChanged() signal which should be fired as soon as the data (row / column change) of the file changes
It is emitted then you tell your model to emit it.
Which you do in setData().
Which you don't seem to call at all.
Given your data it is probably never emitted, as that would require that a log record changes.
But in all likelyhood you are just appending new records to the log file.
Quote:
Originally Posted by
MichaH
Somehow one can connect it to repaint, update, whatever the tableview to automatically show newly added data during runtime.
The table view does that internally.
Quote:
Originally Posted by
MichaH
And that is what's not working in my case and I simply don't know why.. :(
You mean that when you edit a cell in the table view through the UI, then it doesn't call setData()?
Or that setData() is being called but the signal is not emitted?
Cheers,
_
Re: custom QAbstractTableModel class updating QTableView
You're right, I'm not using setData(..) at all. I'm only calling my own slot loadFromFile(..). I'm finally understanding what's causing my problems. Takes some time to refactor everything but I'm letting you know if I managed to get it working!! :)
Thank you for your advices!
Re: custom QAbstractTableModel class updating QTableView
If you are completely replacing the model's content when you re-read the file, and you are not editing the model through the GUI, then don't bother with implementing setData(). In your file load method, first call beginResetModel(), then clear the model and replace the contents, then call endResetModel(). This will take care of notifying any watchers (like your table view) that they need to update their display.
I use this method when I map the output of a data analysis computation into a QAbstractItemModel. The computational results are almost all entirely new, so it is easier to simply trash the model contents and refill it from the new results. This is slower but far easier than having to try to figure out what's changed and update only those bits.
Re: custom QAbstractTableModel class updating QTableView
Well I thought that I did that in my loadFromFile() slot (as you can see above) since I don't need to edit the model through the gui at all. :o
That "static" approach would meet my needs. But I reckon I'm missing the replacing part in my code!?