I am using a QSqlTableModel
and QTableView
to view an SQLite database table.
I would like to have the table auto refresh every second or so (it's not going to be a very large table - a couple of hundred rows). And i can do this - like so:
QTimer *updateInterval = new QTimer(this);
updateInterval->setInterval(1000);
updateInterval->start();
connect(updateInterval, SIGNAL(timeout()),this, SLOT(update_table()));
...
void MainWindow::update_table()
{
model->select(); //QSqlTableModel*
sqlTable->reset(); //QTableView*
}
But this removes any selection I have, so the selections only last for up to a second. This is annoying, as another pane in the GUI depends on what is selected. If nothing is selected, then it resets to an explanation splash page.
I then tried a somewhat hacky approach, which gets the selected row number, resets the table, and then selects that row. But this doesn't work either, as the selected row can move up or down based on additions to the table.
I know other classes have a dataChanged()
signal, which would be ideal.
Do any of you know how I could have the table refresh to reflect changes to the database (from either command line usage, or other instances of the program) AND keep the current selection?
I know I could get data from the current selection, and then after the reset search for the same row and then reselect it, but this seems like a counter productive and bad solution to the problem.
EDIT: Current attempt at solution:
void MainWindow::update_table()
{
QList<QModelIndex> selection = sqlTable->selectionModel()->selection().indexes();
QList<int> selectedIDs;
bool somethingSelected = true;
for(QList<QModelIndex>::iterator i = selection.begin(); i != selection.end(); ++i){
int col = i->column();
QVariant data = i->data(Qt::DisplayRole);
if(col == 0) {
selectedIDs.append(data.toInt());
}
}
if(selectedIDs.empty()) somethingSelected = false;
model->select();
sqlTable->reset();
if(somethingSelected){
QList<int> selectedRows;
int rows = model->rowCount(QModelIndex());
for(int i = 0; i < rows; ++i){
sqlTable->selectRow(i);
if(selectedIDs.contains(sqlTable->selectionModel()->selection().indexes().first().data(Qt::DisplayRole).toInt())) selectedRows.append(i);
}
for(QList<int>::iterator i = selectedRows.begin(); i != selectedRows.end(); ++i){
sqlTable->selectRow(*i);
}
}
}
Okay so this more or less works now...
The real deal is the primary key on the result of your query. Qt's APIs offer a rather circuitous route from
QSqlTableModel::primaryKey()
to a list of columns. The result ofprimaryKey()
is aQSqlRecord
, and you can iterate over itsfield()s
to see what they are. You can also look up all of the fields that comprise the query proper fromQSqlTableModel::record()
. You find the former in the latter to get a list of model columns that comprise the query.If your query doesn't contain a primary key, you'll have to design one yourself and offer it using some protocol. For example, you can choose that if
primaryKey().isEmpty()
is true, the last column returned by the model is to be used as the primary key. It's up to you to figure out how to key the result of an arbitrary query.The selected rows can then be indexed simply by their primary keys (a list of values of the cells that comprise the key -- a
QVariantList
). For this, you could use a custom selection model (QItemSelectionModel
) if its design wasn't broken. The key methods, such asisRowSelected()
aren't virtual, and you can't reimplement them :(.Instead, you can use a proxy model that mimicks selection by providing a custom
Qt::BackgroundRole
for the data. Your model sits on top of the table model, and keeps a sorted list of selected keys. Each time the proxy model'sdata()
is called, you get the row's key from underlying query model, then search for it in your sorted list. Finally, you return a custom background role if the item is selected. You'll have to write relevant comparison operator forQVariantList
. IfQItemSelectionModel
was usable for this purpose, you could put this functionality into a reimplementation ofisRowSelected()
.The model is generic since you subscribe to a certain protocol for extracting the key from a query model: namely, using
primaryKey()
.Instead of using primary keys explicitly, you can also use persistent indices if the model supports them. Alas, until at lest Qt 5.3.2,
QSqlTableModel
does not preserve the persistent indices when the query is rerun. Thus, as soon as the view changes the sort order, the persistent indices become invalid.Below is a fully worked out example of how one might implement such a beast: