PDA

View Full Version : Still QSqlTableModel and friends trouble



waynew
7th January 2010, 23:32
Maybe what I am trying to do is not possible?
Declared the following in a function:



QSqlTableModel *model = new QSqlTableModel(this, logdb);
QTableView *view = new QTableView();
QHeaderView *header = new QHeaderView(Qt::Horizontal);


If it any of these are referenced within the function after they are created, all is well.
If you try to reference say the header, in a different function, maybe to save it - crash.
No doubt null pointer exception, but why? Shouldn't the header still exist?

It seems the design of QSqlTableModel prevents declaring it as a class member since you can't assign a different database to it later. So it would only be good for one database.

Am I missing something here? Or should I try a completely different approach?

Tanuki-no Torigava
8th January 2010, 00:16
If you reference those objects from another thread - then yes, you should have a crash. Everything related to SQL should be kept inside one thread. See assistant for details.


Threads and the SQL Module

A connection can only be used from within the thread that created it. Moving connections between threads or creating queries from a different thread is not supported.

In addition, the third party libraries used by the QSqlDrivers can impose further restrictions on using the SQL Module in a multithreaded program. Consult the manual of your database client for more information


Another possible crash causes are:
1. You don't bind headers with the model and model with database
2. You try to perform operation on empty container.

Hope that helps.

waynew
8th January 2010, 00:48
Thanks for your reply.
I'm certainly not starting a new thread - don't think Qt is doing that on it's own.
I think everything is bound...



view->setHorizontalHeader(header);
view->setModel(model);
model->setTable("log");


And the model is bound to the database in the creation:
QSqlTableModel *model = new QSqlTableModel(this, logdb);

What else is there to bind?

It doesn't crash when the first database comes up - that looks fine, just when you refer to the header you created to try and save it.

Tanuki-no Torigava
8th January 2010, 00:56
Did you fill the header with the names of the columns?

Tanuki-no Torigava
8th January 2010, 00:57
And actually Qt is always starting an application in its own thread. This is part of the trick :)

waynew
8th January 2010, 01:18
The header fills automatically with the column names from the database - that is ok.
I tried the following declaration:



model = new QSqlTableModel(this, logdb);
view = new QTableView();
header = new QHeaderView(Qt::Horizontal);


Now, it opens previously created/saved databases and saves the open db/view header without crashing.

It will also create a new database if I do it from a lineEdit/button on the mainwindow ok,
but if I try that same thing from a lineEdit/button connected with signal/slot from a dialog, then it crashes when it tries to save the existing open header.
Something different is happening when done from the dialog.
The method is called the same way though.


// method called from mainwindow
void MainWindow::addLog() {
if (ui->mwCall->text() != "") {
QMessageBox::information(this, "QtLogger", tr("Last QSO not saved. Please save or clear before creating a new log."));
}
QString log = ui->leNewLog->text();
ControlDB ctrl;
ctrl.createLogDB(log, ctrlConn);
openLog(log); // this is where the model/header/view are created on the heap after existing header is saved and then model/view/header are deleted
ui->leNewLog->setText("");
}



// this is method called from the dialog - same functionality as above
void MainWindow::newLog(QString log) {
if (ui->mwCall->text() != "") {
QMessageBox::information(this, "QtLogger", tr("Last QSO not saved. Please save or clear before creating a new log."));
}
ControlDB ctrl;
ctrl.createLogDB(log, ctrlConn);
openLog(log);
}


So you can see everything that goes on....



void MainWindow::openLog(QString logName) {
ControlDB ctrl;
QString openLog = ctrl.getOpenLog(ctrlConn);

if (openLog != "") {
QSqlDatabase db = QSqlDatabase::database(hdrConn);
db.setDatabaseName("header.sqlite");
db.open();
QByteArray ha;
ha = header->saveState();

QSqlQuery query(db);
query.prepare("SELECT count(*) from header where headerLog = ?");
query.addBindValue(logName);

query.exec();
query.last();
int cnt = query.value(0).toInt();

if (cnt == 0) {
query.prepare("INSERT into header(positions, headerLog) VALUES (?, ?)");
query.addBindValue(ha);
query.addBindValue(logName);
query.exec();
}else{
query.prepare("UPDATE header set positions=? where headerLog=? ");
query.addBindValue(ha);
query.addBindValue(logName);
query.exec();
query.last();
qDebug() << "saveHeader error 3 = " << query.lastError();
}
db.close();

QSqlDatabase openDB = model->database();
openDB.close();

delete header; // these only deleted if there was an open db and after header was saved.
delete view;
delete model;
}

QSqlDatabase hdrdb = QSqlDatabase::database(hdrConn);
QSqlQuery query(hdrdb);
query.prepare("SELECT count (*) from header where headerLog = ?");
query.addBindValue(logName);
query.exec();
query.last();
int cnt = query.value(0).toInt();

ctrl.createConnection(logName + ".log", logName);
QSqlDatabase logdb = QSqlDatabase::database(logName);
logdb.setDatabaseName(logName + ".log.sqlite");
logdb.open();

model = new QSqlTableModel(this, logdb);
view = new QTableView();
header = new QHeaderView(Qt::Horizontal);

view->setHorizontalHeader(header);
//etc


Is Qt starting a new thread for the dialog - could that be the problem?
The dialog passes the new db name to a function in the mainwindow class just like the lineEdit/button on the mainwindow does.

numbat
8th January 2010, 07:27
delete header; // these only deleted if there was an open db and after header was saved.
delete view;
delete model;

model is a child of view, so will be deleted when view is deleted. The documentation doesn't say but I suspect that the view also takes ownership of the header. Probably, just delete the view and the other two will be deleted automatically. OR you could use the QPointer class to make sure you're not redeleting.

JD2000
8th January 2010, 14:52
Your QSqlTableModel is owned by the MainWindow object and will be automatically destroyed when this is closed (line 56)
the QTableView and QHeaderView have no parents and will need to be deleted explicitly.
By deleting the model in line 40 you are unbalancing the call stack which will make the application crash.
Putting delete operators inside an if statement is rarely a good idea, if they are used at all they should unconditionally delete the object created by new when the object is no longer wanted.

A better solution would be to give the table and hearde views parents and let QT deal with the garbage collection.