Using signals/slots as callback to show the main window
-
I have written a QT application that works well. I have now implemented a license mechanism to check for a license file and if it isn't there, to go to a server and download it. The goal is that the main window doesn't open until the license has been downloaded. I was hoping to do it as follows:
int main(int argc, char *argv[]) { QApplication a(argc, argv); LicenseManager* lm = new LicenseManager(); MainWindow w; QObject::connect( lm, SIGNAL(licenseValidated()), &w, SLOT(display()) ); // only show creator window once license is validated return a.exec(); } void MainWindow::display() { this->show(); }
The LicenseManager object constructor kicks off the process of retrieving the license if necessary. When it has retrieved the license, it emits licenseValidated().
However, it seems that the application exits after a few seconds, before the server has even a chance to respond. I expect it is because there is no gui for the gui thread and it just quits. Is that correct? If so, any suggestions on how to get around this?
As a test, I just showed the window in all cases and then the license retrieval across the network works fine.
When I first thought about implementing this wait-until-validated-before-showing, I thought about using threads. I could instantiate the LicenseManager, start a worker thread, then moving the LicenseManager object into its own thread and connect the two threads through some callback. But this seemed so much simpler, at least initially. But it's not working...
@ambershark: edited to add code tags.
-
@mgreenish That's actually really weird. That code should work fine. There should not be any reason for it to exit just because a widget (even if it is a main window) was not shown at a particular time.
My guess is it is something your license manager is doing. I mean you can literally write something like this:
int main(int ac, char **av) { QApplication app(ac, av); MainWindow mw; return app.exec(); }
In that example it never shows the main window at all, and it will not exit. In fact you would have to ctrl+c or kill it to exit it. So the fact that your application is exiting tells me you are crashing or have an actual exit in your license manager.
Also, you are leaking memory on your license manager since you use
new
but neverdelete
... unless there is a deleteLater or something in the LicenseManager code. -
Hi
Normally app.exec() first exits on last top level windows closed
(controlled with setQuitOnLastWindowClosed(true) )If you sure that LicenseManager to not crash or something like that
i would qDebug() << QApplication::topLevelWidgets().size();
in licenseValidated to check all is as expected. -
It's pretty weird that you want to use Connect in the main().
I would suggest to pass the connect to the constructor of the LinceseManager, and do a catch to retain the processing of the application.
Kind regards.
Carlos
-
@Charlie_Hdz There's nothing wrong with using connect in main. I've done it before in similar situations as he's facing. In fact to not do it there means he would have to couple 2 classes which don't need coupling. He would have to pass his MainWindow to LicenseManager in order to connect the display inside there. Not something I would recommend at all.
and do a catch to retain the processing of the application
If by this you mean he should do a
try { } catch (...) { }
to catch a potential crash then most definitely you should never do that. If you meant something else and I just misunderstood then ignore that. :) -
@ambershark said in Using signals/slots as callback to show the main window:
If by this you mean he should do a try { } catch (...) { } to catch a potential crash then most definitely you should never do that.
Why is that? Certainly in a debug phase of your development, since things don't work as you want, this can only be useful, or what am I missing?
-
@Diracsbracket No catching unhandled exceptions is bad for debugging. You want the exception to be thrown and the program to crash that way the debugger can catch it.
Sometimes coders will add a global catch all, i.e.
catch (...)
to their apps in order to "stop" crashes. However if you have an unhandled exception, catching it doesn't really make your program stable. It won't crash but it won't be usable either.Mostly a global catchall will be used to present the user with an interface to send a bug report to the developers and capture whatever data it can from the exception. But it should never be used to try to "stop a crash".
-
@ambershark
Why is that a problem if you usetry/catch
in the DEBUGGING phase of your development, and in thecatch
part output some debug info with qDebug() ? If it crashes, you will immediately know where. Then of course, you can use the debugger to find the problem, no? -
@Diracsbracket Well in debugging you can, but if you are going to debug you wouldn't really need the try/catch since you let the debugger catch the exception and backtrace from there.
To be clear I'm just talking about a catch all, i.e.
catch (...)
not catching specific exceptions. There is very little extra info you would get fromcatch (...)
that you could not get by just catching the exception in a debugger and actually debugging.I'm not say doing catch specific exceptions, but you never really want to do a catch all unless you have a really good reason.
Also if you catch an exception the debugger doesn't break into the code there. Which imo makes debugging much harder than not having a caught exception. Unless you did something like rethrowing the exception after you log whatever you wanted to.
I'm not saying never use that no matter what, I'm sure there's a place for it, but usually any information you wanted would be available in your debugger if you just let it catch the exception. However I am saying never use this in production code except in the case I mentioned above.
-
@ambershark Thank you for confirming that the application shouldn't close even if there is no open window. The code executes properly when I have a window open but stops executing when I don't open a window. The code is awaiting feedback from the server and otherwise is in a wait state. Please see code below:
LicenseManager::LicenseManager(QWidget *parent) : clientsideLicenseState(LMS_CHECK_LICENSE), serversideLicenseState(LSS_NONE), lms_awaiting_response_cnt(0) { // Start the state machine which will walk through the licensing process lmStateTimer = new QTimer(this); lmStateTimer->setSingleShot(false); lmStateTimer->setInterval( 100 ); connect( lmStateTimer, SIGNAL(timeout()), SLOT(lmHTTPStateMachine()) ); lmStateTimer->start(); machineID = new MachineIDGenerator(); } LicenseManager::~LicenseManager() { delete machineID; } void LicenseManager::quit() { lmStateTimer->stop(); } void LicenseManager::lmHTTPStateMachine() { QMessageBox* msgBox; LicenseManagerFormDialog* form = new LicenseManagerFormDialog(); if( clientsideLicenseState != LMS_AWAITING_RESPONSE ) lms_awaiting_response_cnt = 0; qDebug() << "In state machine function with " + QString::number( clientsideLicenseState ); switch( clientsideLicenseState ) { case LMS_CHECK_LICENSE: // Is there a license if( loadLicense() ) { // Is the license valid if( validateKey() ) { // Is the key still valid if( validateLicense() ) { clientsideLicenseState = LMS_KEY_VALIDATED; emit licenseValidated(); quit(); // Return if license is present and valid break; // If key is expired, will have to renew it } else clientsideLicenseState = LMS_KEY_EXPIRED; // If there is a key but it isn't valid, inform user of invalid key } else { clientsideLicenseState = LMS_KEY_NOT_VALID; QMessageBox* msgBox = new QMessageBox(); msgBox->setStandardButtons( QMessageBox::Ok ); msgBox->setText( "Your license is invalid. Try deleting your license and starting the application again. " ); msgBox->exec(); quit(); break; } } clientsideLicenseState = LMS_ACQUIRE_DATA; case LMS_ACQUIRE_DATA: if( ! form->exec() ) { // get user's email address quit(); break; } email = form->getEmail(); // Todo: Validate email provided clientsideLicenseState = LMS_REQUESTING_STATUS; // fall through case LMS_REQUESTING_STATUS: retrieveServersideLicenseState(); clientsideLicenseState = LMS_AWAITING_RESPONSE; break; case LMS_AWAITING_RESPONSE: if( ++lms_awaiting_response_cnt < 60 ) break; quit(); msgBox = new QMessageBox(); msgBox->setStandardButtons( QMessageBox::Ok ); msgBox->setText( "The license request has timed out. Please check your network connection and restart the application." ); msgBox->exec(); clientsideLicenseState = LMS_FAILED; break; ... } } void LicenseManager::retrieveServersideLicenseState() { QNetworkAccessManager *networkManager = new QNetworkAccessManager(this); connect( networkManager, SIGNAL( finished(QNetworkReply*) ), this, SLOT(finishedServersideLicenseStateRequest(QNetworkReply*)) ); QString url = QString("http://licenses.addhaptics.com/license_manager.php?mode=status"); url += QString("&email=") + email.toUtf8(); url += QString("&uuid=") + machineID->getUuid()->toUtf8(); QNetworkRequest request; request.setUrl( QUrl( url ) ); request.setRawHeader("User-Agent", "HDS/1.0 (+haptics; Qt)"); request.setHeader( QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); networkManager->get( request ); // This may have to be a post } // Process response void LicenseManager::finishedServersideLicenseStateRequest( QNetworkReply* reply ) { qDebug() << "Getting status reply"; if( reply->error() ) { qDebug() << "Retrieve license failed " << reply->errorString(); return; } qDebug() << reply->header(QNetworkRequest::ContentTypeHeader).toString(); qDebug() << reply->header(QNetworkRequest::LastModifiedHeader).toDateTime().toString();; qDebug() << reply->header(QNetworkRequest::ContentLengthHeader).toULongLong(); qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); qDebug() << reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); QString reason = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString(); if( ! reason.compare( QString("none") ) ) clientsideLicenseState = LMS_NONE_AVAILABLE; else if( ! reason.compare( QString("requested") ) ) clientsideLicenseState = LMS_VERIFY_EMAIL; else if( ! reason.compare( QString("assigned") ) || ! reason.compare( QString("validated" ) ) ) { clientsideLicenseState = LMS_RETRIEVING_KEY; retrieveLicenseKey(); } else if( ! reason.compare( QString("expired") ) ) clientsideLicenseState = LMS_LICENSE_EXPIRED; else if( ! reason.compare( QString("available") ) ) clientsideLicenseState = LMS_ASK_REQUEST; emit licenseStatusReceived(); }
By stepping through the code, I know the status request gets sent out but I don't get into the response handler if no window is open. And the only debug output I get is:
"In state machine function with 0"
"In state machine function with 8"
I believe you when you say the application is crashing but I don't think it is happening in my code.I am getting the following warnings:
qt.network.ssl: QSslSocket: cannot resolve SSL_set_psk_client_callback
qt.network.ssl: QSslSocket: cannot resolve TLSv1_1_client_method
qt.network.ssl: QSslSocket: cannot resolve TLSv1_2_client_method
qt.network.ssl: QSslSocket: cannot resolve TLSv1_1_server_method
qt.network.ssl: QSslSocket: cannot resolve TLSv1_2_server_method
qt.network.ssl: QSslSocket: cannot resolve SSL_select_next_proto
qt.network.ssl: QSslSocket: cannot resolve SSL_CTX_set_next_proto_select_cb
qt.network.ssl: QSslSocket: cannot resolve SSL_get0_next_proto_negotiatedbut since I'm not using https I have been ignoring these.
Any ideas?
Thanks also for pointing out the memory leakage in my main.
-
@mgreenish So isn't the licensemanager a window? It derives from QWidget. Does it close while it's waiting for a response? It's more than likely not crashing.
I see multiple places where
quit()
is called. One of these could be called and quitting the application in an area you don't expect it. What is the enum for 8? I.e. what license mode is it in when it prints that during debugging? That could cause aquit()
to be called.When it is not functioning properly, is it still running? Or does the process go away?
Have you tried setting
setQuitOnLastWindowClosed(false)
just to test and see if it's closing?I don't see any code that shows
LicenseManager
either. Is it really a QWidget? -
@ambershark So the QuiteOnLastWindowClosed seemed to be the problem. I added
a.setQuitOnLastWindowClosed(false);
and now it no longer closes.
Thanks to everyone for the help!!
-
@mgreenish If your problem is solved, couldl you please mark your post accordingly? thanks.
-
@mgreenish Just keep in mind you have to call quit() now when your mainwindow closes or your app will continue running after you think it's done.
Just make sure it's actually closing now when you expect it to and not staying hidden and running in the background.