How to properly exit GUI (with QT Widgets) application
-
I've written a GUI app using Qt Creator and Qt 6.9.1, and I'm running it on Linux. To close the application, I have a File->Exit menu action connected to QCoreApplication::quit using a queued connection as suggested in the documentation.
exitAction = new QAction ( "E&xit", this ) ; connect ( exitAction, &QAction::triggered, this, &QCoreApplication::quit, Qt::QueuedConnection ) ;
So I can perform some cleanup and store the application's configuration at the close, I've written a closeEvent handler:
void K4Remote::closeEvent ( QCloseEvent *event ) { Configuration::WriteConfiguration () ; m_audioDecoder->Stop () ; m_audioEncoder->Stop () ; m_pttSwitch->Close () ; event->accept () ; }
When I run the application in QtCreator and select File->Exit, I see the message in the application output window that says the application finished successfully.
However, it seems as if the call to WriteConfiguration() is not fully executed. I have put qDebug() statements at the start and end of WriteConfiguration(), and I see both output as expected. However, the several QSettings::setValue() calls inside don't seem to be effective - that is, nothing is written to the config file, and its modification time stamp is unchanged. I have not verified that the other calls within the close event handler have run successfully, but I have not detected an issue that would result from them failing.
On the other hand, if I exit the application by clicking on the close box ("X" at the extreme right end of the title bar), the application exits with a "terminated abnormally" status, and the config file IS updated.
My questions:
(1) Why does WriteConfiguration() fail when terminating the application via File->Exit?
(2) Why does WriteConfiguration() succeed when terminating the application via the close box?
(3) Why does the application return "terminated abnormally" status when terminated using the close box?
Thank you.
-
I've written a GUI app using Qt Creator and Qt 6.9.1, and I'm running it on Linux. To close the application, I have a File->Exit menu action connected to QCoreApplication::quit using a queued connection as suggested in the documentation.
exitAction = new QAction ( "E&xit", this ) ; connect ( exitAction, &QAction::triggered, this, &QCoreApplication::quit, Qt::QueuedConnection ) ;
So I can perform some cleanup and store the application's configuration at the close, I've written a closeEvent handler:
void K4Remote::closeEvent ( QCloseEvent *event ) { Configuration::WriteConfiguration () ; m_audioDecoder->Stop () ; m_audioEncoder->Stop () ; m_pttSwitch->Close () ; event->accept () ; }
When I run the application in QtCreator and select File->Exit, I see the message in the application output window that says the application finished successfully.
However, it seems as if the call to WriteConfiguration() is not fully executed. I have put qDebug() statements at the start and end of WriteConfiguration(), and I see both output as expected. However, the several QSettings::setValue() calls inside don't seem to be effective - that is, nothing is written to the config file, and its modification time stamp is unchanged. I have not verified that the other calls within the close event handler have run successfully, but I have not detected an issue that would result from them failing.
On the other hand, if I exit the application by clicking on the close box ("X" at the extreme right end of the title bar), the application exits with a "terminated abnormally" status, and the config file IS updated.
My questions:
(1) Why does WriteConfiguration() fail when terminating the application via File->Exit?
(2) Why does WriteConfiguration() succeed when terminating the application via the close box?
(3) Why does the application return "terminated abnormally" status when terminated using the close box?
Thank you.
@Art-Greenberg We don't know how Configuration::WriteConfiguration is implemented. Do you call QSettings::sync()?
(3) Use the debugger to see what exactly happens.
-
This simple example, modelled on your problem, calls closeEvent either way for me (Linux, Qt 6)
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); // QWidget interface protected: virtual void closeEvent(QCloseEvent *event) override; }; #endif // MAINWINDOW_H
#include "mainwindow.h" #include <QDebug> #include <QCloseEvent> #include <QMenuBar> #include <QMenu> #include <QAction> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QAction *exitAction = new QAction ( "E&xit", this ) ; connect ( exitAction, &QAction::triggered, this, &QCoreApplication::quit, Qt::QueuedConnection ) ; QMenu *fileMenu = menuBar()->addMenu(tr("&File")); fileMenu->addAction(exitAction); } MainWindow::~MainWindow() { } void MainWindow::closeEvent(QCloseEvent *event) { qDebug() << Q_FUNC_INFO; event->accept(); }
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
With regard to QSettings. Changes are not immediately (synchronously) written to disk. To quote the docs, " For efficiency, the changes may not be saved to permanent storage immediately. (You can always call sync() to commit your changes.)" With the QCoreApplication::quit() path, the event loop might exit before the sync to disk happens. (You may have this race condition either way.)
Are you calling QSettings::sync() in your WriteConfiguration routine?The closeEvent for a widget is called when you explicitly call QWidget::close() or the user takes an action to close the widget (e.g. your application's main window close button). This is exactly what is happening in case 2.
-
This simple example, modelled on your problem, calls closeEvent either way for me (Linux, Qt 6)
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); // QWidget interface protected: virtual void closeEvent(QCloseEvent *event) override; }; #endif // MAINWINDOW_H
#include "mainwindow.h" #include <QDebug> #include <QCloseEvent> #include <QMenuBar> #include <QMenu> #include <QAction> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QAction *exitAction = new QAction ( "E&xit", this ) ; connect ( exitAction, &QAction::triggered, this, &QCoreApplication::quit, Qt::QueuedConnection ) ; QMenu *fileMenu = menuBar()->addMenu(tr("&File")); fileMenu->addAction(exitAction); } MainWindow::~MainWindow() { } void MainWindow::closeEvent(QCloseEvent *event) { qDebug() << Q_FUNC_INFO; event->accept(); }
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
With regard to QSettings. Changes are not immediately (synchronously) written to disk. To quote the docs, " For efficiency, the changes may not be saved to permanent storage immediately. (You can always call sync() to commit your changes.)" With the QCoreApplication::quit() path, the event loop might exit before the sync to disk happens. (You may have this race condition either way.)
Are you calling QSettings::sync() in your WriteConfiguration routine?The closeEvent for a widget is called when you explicitly call QWidget::close() or the user takes an action to close the widget (e.g. your application's main window close button). This is exactly what is happening in case 2.
@ChrisW67 said in How to properly exit GUI (with QT Widgets) application:
With regard to QSettings. Changes are not immediately (synchronously) written to disk. To quote the docs,
For efficiency, the changes may not be saved to permanent storage immediately. (You can always call sync() to commit your changes.)
With the QCoreApplication::quit() path, the event loop might exit before the sync to disk happens. (You may have this race condition either way.)
Are you calling QSettings::sync() in your WriteConfiguration routine?I edited the quoting of the QSettings documentation to make it clear that the comment about a race is not from the documentation. Syncing through invocation of the QSettings destructor doesn't depend on the event loop.
QSettings::~QSettings():QSettings::~QSettings() { Q_D(QSettings); if (d->pendingChanges) { // Don't cause a failing flush() to std::terminate() the whole // application - dtors are implicitly noexcept! QT_TRY { d->flush();
-
@ChrisW67 , @jeremy_k
I read what you both say. Personally I have never had a problem withQSettings
or its writing to file, but then again I have never tried to save an extra setting during acloseEvent()
or similar. What you say worries me: the user should not have to worry about syncing/flushing just as, say, program is exiting. During my program exit code I have "no idea" whether the rest of the code might have saved a setting, and I should not have to know. On that basis there might be a hundred other things which I ought to deal with, just in case. Doesn't either exiting the top-level Qt event loop cause flush, or the destructor you show should be hit and that will flush? -
Thanks to all. I've resolved the issue. Here is what I found:
-
My declaration for closeEvent() did not look like the declaration in the example from @ChrisW67.
-
I did not call QSettings::sync() in my implementation of WriteConfiguration(). Thanks @ChrisW67.
-
The implementation of m_pttSwitch->Close() left a timer running and that caused the abnormal termination. Thanks to @jsulm for suggesting running under the debugger.
After those corrections, my application closes normally and updates the configuration file as expected with either selecting File->Exit or by clicking the close box.
I think @JonB maybe has a point. The Qt platform takes care of so much for us that we might expect QSetting::sync() to be done for us. The documentation is clear enough, but I missed it until it was pointed out.
Art
-
-
Thanks to all. I've resolved the issue. Here is what I found:
-
My declaration for closeEvent() did not look like the declaration in the example from @ChrisW67.
-
I did not call QSettings::sync() in my implementation of WriteConfiguration(). Thanks @ChrisW67.
-
The implementation of m_pttSwitch->Close() left a timer running and that caused the abnormal termination. Thanks to @jsulm for suggesting running under the debugger.
After those corrections, my application closes normally and updates the configuration file as expected with either selecting File->Exit or by clicking the close box.
I think @JonB maybe has a point. The Qt platform takes care of so much for us that we might expect QSetting::sync() to be done for us. The documentation is clear enough, but I missed it until it was pointed out.
Art
@Art-Greenberg said in How to properly exit GUI (with QT Widgets) application:
I think @JonB maybe has a point. The Qt platform takes care of so much for us that we might expect QSetting::sync() to be done for us
If @jeremy_k is saying
QSettings::~QSettings()
has the code he shows then shouldn't it write to file when/assuming that is called? -
-
-
Hi,
Out of curiosity, why do that in the closeEvent rather than the class destructor ?