I'm using QFileDialog::getOpenFileName right now. However, as suggested in this article, this crashes when the main application closes while the dialog is open. You can see an example of how to reproduce the crash here:
int main(int argc, char **argv) {
QApplication application{argc, argv};
QMainWindow *main_window = new QMainWindow();
main_window->show();
QPushButton *button = new QPushButton("Press me");
main_window->setCentralWidget(button);
QObject::connect(button, &QPushButton::clicked, [main_window]() {
QTimer::singleShot(2000, [main_window]() { delete main_window; });
QFileDialog::getOpenFileName(main_window, "Close me fast or I will crash!");
});
application.exec();
return 0;
}
I can use QFileDialog with the normal constructor instead, as described here. However, then I don't seem to get the native windows file open dialog.
Is there a way to get a non crashing program and use the native Windows file open dialog through Qt?
If you close your main_window instead of deleting it, you won't get any crash.
By the way, you could check if there is any QFileDialog opened to avoid a wrong app exit.
In the next example, I'm closing the dialog, but you could implement another solution:
#include <QTimer>
#include <QApplication>
#include <QMainWindow>
#include <QPushButton>
#include <QFileDialog>
#include <QDebug>
int main(int argc, char **argv) {
QApplication application{argc, argv};
QMainWindow *main_window = new QMainWindow();
main_window->show();
QPushButton *button = new QPushButton("Press me");
main_window->setCentralWidget(button);
QObject::connect(button, &QPushButton::clicked, [main_window]() {
QTimer::singleShot(2000, [main_window]() {
QObjectList list = main_window->children();
while (!list.isEmpty())
{
QObject *object= list.takeFirst();
if (qobject_cast<QFileDialog*>(object))
{
qDebug() << object->objectName();
QFileDialog* fileDialog = qobject_cast<QFileDialog*>(object);
fileDialog->close();
}
}
main_window->close();
});
QFileDialog::getOpenFileName(main_window, "Close me fast or I will crash!");
});
application.exec();
return 0;
}
The design of your application is broken. The shut down of the application normally happens when the outernmost event loop in the main thread exists. This won't happen while a file dialog is active - by definition, its event loop is running then. Thus you're doing something you shouldn't be doing, and the file dialog is merely a scapegoat, or a canary in the coalmine indicating brokenness elsewhere.
Related
I have the following code
#include "dialog.h"
#include <QApplication>
#include <QProgressDialog>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QProgressDialog w;
//w.show();
return a.exec();
}
The execution of this code shows a QProgressDialog UI.
I would like to have the explanation why my QProgressDialog shows up without having an exec()or show() instructions.
I have read the documentation but did not find the explanation on it.
Both constructors of QProgressDialog call QProgressDialogPrivate::init, where forceTimer : QTimer is started:
...
forceTimer = new QTimer(q);
QObject::connect(forceTimer, SIGNAL(timeout()), q, SLOT(forceShow()));
...
forceTimer->start(showTime);
The forceShow() slot in turn looks like this:
void QProgressDialog::forceShow() {
Q_D(QProgressDialog);
d->forceTimer->stop();
if (d->shown_once || d->cancellation_flag)
return;
show();
d->shown_once = true;
}
The show(); statement there is the very reason the QProgressDialog is shown on object creation.
Since it will automatically open after being constructed, follow it with a cancellation.
auto * progressDialog = new QProgressDialog{};
progressDialog.cancel();
I am using the Urho3D engine with Qt for an application. The problem is that both Urho3D and QApplication require to be ran from main(). For now I am using it in separate processes but IPC makes it complicated.
Is there any way to solve this issue? Thanks
My platform is Urho3D 1.5, Qt 4.71 and Windows 7 x64 and VS2015 (C++)
I'm new to both c++ and Urho3D, but I've successfully achieved it.
Simple code, haven't had further test:
awidget.h:
#ifndef AWIDGET_H
#define AWIDGET_H
#include <QWidget>
#include <QPushButton>
#include <Urho3D/Engine/Application.h>
class aWidget : public QWidget
{
Q_OBJECT
public:
explicit aWidget(QWidget *parent = 0)
{
QPushButton *button = new QPushButton(this);
connect(button, SIGNAL(clicked()), this, SLOT(pressed()));
}
public slots:
void pressed()
{
Urho3D::Context* context = new Urho3D::Context();
Urho3D::Application *application = new Urho3D::Application(context);
application->Run();
}
};
#endif // AWIDGET_H
main.cpp:
#include <QApplication>
#include <awidget.h>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
aWidget *widget = new aWidget();
widget->show();
return app.exec();
}
By the way, I'm using Qt 5.9.0
So the answer is quite simple. Instead of running QApplication by calling
app->exec();
it is needed to manually and regularily call this from your main loop:
app->processEvents();
This will take care that all events used by Qt are processed and QApplication will respond accordingly.
Example:
#include <QApplication>
#include <awidget.h>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
bool shallrun = true;
aWidget *widget = new aWidget();
widget->show();
while (shallrun)
{
app->processEvents();
...
}
...
}
I have QDialogButtonBox buttons on my dialog which are Ok and Cancel button pair. I have implemented accepted() signal to process when 'Ok' button is pressed but I want to abort quitting dialog if the directory path is invalid.
void SettingsDialog::on_buttonBox_accepted()
{
QDir path( ui->lineEditRootPath->text() );
if ( path.exists( ui->lineEditRootPath->text() ) )
{
QSettings settings; // save settings to registry
settings.setValue(ROOT_PATH, ui->lineEditRootPath->text() );
}
else
{
// abort cancelling the dialog here
}
}
Can the dialog quitting be abort from this handler? Do I have to implement the above code in some other signal? Do I have to use simple button to accomplish this instead of QDialogButtonBox?
This issue comes from the dialog template bundled with Qt Creator. When you create an empty dialog with buttons, the .ui file has connections between the button box and and the underlying dialog. They are created behind your back, so to speak:
So, there really is no problem, since the button box doesn't actually accept the dialog. You must accept the dialog, if you don't then the dialog stays open.
The simple fix is to remove the default connection(s).
Other Nitpicks
You should not use the QDir::exists(const QString &) overload - it won't work. You already provided the path to dir's constructor. Simply use exists().
Thus:
void SettingsDialog::on_buttonBox_accepted()
{
QDir path(ui->lineEditRootPath->text());
if (!path.exists()) return;
QSettings settings; // save settings to registry
settings.setValue(ROOT_PATH, ui->lineEditRootPath->text());
accept(); // accepts the dialog, closing it
}
You could also use the static QFileInfo::exists:
void SettingsDialog::on_buttonBox_accepted()
{
if (! QFileInfo.exists(ui->lineEditRootPath->text()) return;
...
}
Finally, it's probably a nice idea to provide some sort of feedback to the user when an input is invalid. In C++11, that's quite easy to do:
#include <QApplication>
#include <QFileInfo>
#include <QDialog>
#include <QDialogButtonBox>
#include <QLineEdit>
#include <QGridLayout>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QDialog dialog;
QLineEdit edit("/");
QDialogButtonBox buttons(QDialogButtonBox::Ok | QDialogButtonBox::Close);
QGridLayout layout(&dialog);
layout.addWidget(&edit, 0, 0);
layout.addWidget(&buttons, 1, 0);
QObject::connect(&buttons, &QDialogButtonBox::accepted, [&]{
if (!QFileInfo::exists(edit.text())) return;
//...
dialog.accept();
});
QObject::connect(&buttons, &QDialogButtonBox::rejected, [&]{ dialog.reject(); });
QObject::connect(&edit, &QLineEdit::textChanged, [&](const QString&){
if (QFileInfo::exists(edit.text()))
edit.setStyleSheet("");
else
edit.setStyleSheet("* { background: red; }");
});
dialog.show();
return a.exec();
}
After some testing, you've realized that the users have a bad tendency to enter paths that might be on disconnected network volumes. When you attempt to check if they exist, it blocks the GUI just so that the OS can politely tell you "umm, nope".
The solution is to perform the check in a worker thread, so that if it blocks, the UI will not be directly affected. If the worker thread blocks, the path editor background will turn yellow. If the path doesn't exist, the background will turn red and the OK button will be disabled.
One bit of code requires some explanation: QObject::connect(&checker, &Checker::exists, &app, [&](...){...}) connects the checker's signal to a lambda in the thread context of the application object. Since checker's signals are emitted in the checker's thread, without the context (&app), the code would be executed in the checker's thread. We definitely don't want that, the GUI changes must be executed in the main thread. The simplest way to do it is to pass one object we surely know lives in the main thread: the application instance. If you don't pass the proper context, e.g. QObject::connect(&checker, &Checker::exists, [&](...){...})), you'll get undefined behavior and a crash.
#include <QApplication>
#include <QFileInfo>
#include <QDialog>
#include <QDialogButtonBox>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include <QThread>
#include <QTimer>
class Thread : public QThread {
using QThread::run; // final
public:
~Thread() { quit(); wait(); }
};
class Checker : public QObject {
Q_OBJECT
public:
Q_SIGNAL void exists(bool, const QString & path);
Q_SLOT void check(const QString & path) { emit exists(QFileInfo::exists(path), path); }
};
int main(int argc, char *argv[])
{
bool pathExists = true;
QApplication app(argc, argv);
QDialog dialog;
QLineEdit edit("/");
QDialogButtonBox buttons(QDialogButtonBox::Ok | QDialogButtonBox::Close);
QGridLayout layout(&dialog);
layout.addWidget(&edit, 0, 0);
layout.addWidget(&buttons, 1, 0);
QTimer checkTimer;
Checker checker;
Thread checkerThread;
checker.moveToThread(&checkerThread);
checkerThread.start();
checkTimer.setInterval(500);
checkTimer.setSingleShot(true);
QObject::connect(&buttons, &QDialogButtonBox::accepted, [&]{
if (!pathExists) return;
//...
dialog.accept();
});
QObject::connect(&buttons, &QDialogButtonBox::rejected, [&]{ dialog.reject(); });
QObject::connect(&edit, &QLineEdit::textChanged, &checker, &Checker::check);
QObject::connect(&edit, &QLineEdit::textChanged, &checkTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
QObject::connect(&checkTimer, &QTimer::timeout, [&]{ edit.setStyleSheet("background: yellow"); });
QObject::connect(&checker, &Checker::exists, &app, [&](bool ok, const QString & path){
if (path != edit.text()) return; // stale result
checkTimer.stop();
edit.setStyleSheet(ok ? "" : "background: red");
buttons.button(QDialogButtonBox::Ok)->setEnabled(ok);
pathExists = ok;
});
dialog.show();
return app.exec();
}
#include "main.moc"
There is a QApplication::lastWindowClosed() signal. The Qt docs say:
This signal is emitted from QApplication::exec()
when the last visible primary window [...] is closed.
However, I used QApplication::processEvents() instead of QApplication::exec() in a loop. See this minimal example. (save as qapp.h, it must end on .h, and run qmake -project && qmake && make)
#include <QApplication>
#include <QDebug>
#include <QObject>
#include <QMainWindow>
int exitprogram = 0;
class MyMainWindow : public QMainWindow
{
Q_OBJECT
public slots:
void request_exit() {
qDebug() << "exit requested";
exitprogram = 1;
}
};
int main(int argc, char** argv)
{
QApplication app(argc, argv);
MyMainWindow w;
QObject::connect(&app, SIGNAL(lastWindowClosed()),
&w, SLOT(request_exit()));
w.show();
while(!exitprogram)
{
app.processEvents(QEventLoop::AllEvents, 20);
}
}
Is there still a good way to find out, or to even get a signal, if the last such window is being closed?
Whatever reason you have for using processEvents in place of exec is wrong. The two are not equivalent. exec() will, for example, process the deferred deletion events, while processEvents will not. As you've just found out, the lastWindowClosed signal is not emitted either. This should be telling you right there that you're doing it wrong.
The idiomatic Qt way of doing something each time the event loop goes for another iteration, is to use a zero-timeout timer. Those are virtual timers that do not use operating system resources, they are an internal Qt construct.
The example below illustrates the following:
Use of a zero-timeout timer within a QObject.
Use of the State Machine Framework to manage the state of the application. We have three states:
sWindows is the state when the application windows are still shown. The application is set not to quit on the last window being closed.
sSetup is the state reached when the last of the windows was closed. In this state we ask our Object to send its notification signal with the number of times it executed the zero-timeout timer. This will set the proper count in the message label (C++11 code) or in the count label (legacy code). The state machine automatically transitions to the following state.
sMessage is the state when the message labels are shown, and the application is set to quit upon the last window being closed.
The use of a state machine leads to declarative code: you tell the state machine how to behave, without implementing all of the behavior. You only have to implement the behaviors that are specific to your application and not already provided by Qt. The objects that the state machine manages can be very much decoupled, and the code that declares the behavior of the machine is cohesive - it can be all in one function, instead of being spread around. This is considered to be good software design.
Do note that the zero-timeout timer is very diligent: it will force your handler code to execute constantly whenever the event loop is empty. This will force 100% CPU consumption on the core where the GUI thread happens to be executing. If you have nothing to do, you should stop() the timer.
Qt 5 C++11 Code
// https://github.com/KubaO/stackoverflown/tree/master/questions/close-process-19343325
#include <QtWidgets>
int main(int argc, char** argv)
{
QApplication app{argc, argv};
QLabel widget{"Close me :)"};
QLabel message{"Last window was closed"};
int counter = 0;
auto worker = [&]{
counter++;
};
QTimer workerTimer;
QObject::connect(&workerTimer, &QTimer::timeout, worker);
workerTimer.start(0);
QStateMachine machine;
QState sWindows{&machine};
QState sSetup {&machine};
QState sMessage{&machine};
sWindows.assignProperty(qApp, "quitOnLastWindowClosed", false);
sWindows.addTransition(qApp, &QGuiApplication::lastWindowClosed, &sSetup);
QObject::connect(&sSetup, &QState::entered, [&]{
workerTimer.stop();
message.setText(QString("Last window was closed. Count was %1.").arg(counter));
});
sSetup.addTransition(&sMessage);
sMessage.assignProperty(&message, "visible", true);
sMessage.assignProperty(qApp, "quitOnLastWindowClosed", true);
machine.setInitialState(&sWindows);
machine.start();
widget.show();
return app.exec();
}
Qt 4/5 C++11 Code
#include <QApplication>
#include <QLabel>
#include <QStateMachine>
#include <QBasicTimer>
class Object : public QObject {
Q_OBJECT
QBasicTimer m_timer;
int m_counter = 0;
protected:
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() == m_timer.timerId())
m_counter ++;
}
public:
Object(QObject * parent = 0) : QObject{parent} {
m_timer.start(0, this);
}
Q_SLOT void stop() const {
m_timer.stop();
emit countedTo(m_counter);
}
Q_SIGNAL void countedTo(int) const;
};
int main(int argc, char** argv)
{
QApplication app{argc, argv};
Object object;
QLabel widget{"Close me :)"};
QLabel message{"Last window was closed"};
QLabel count;
QStateMachine machine;
QState sWindows{&machine};
QState sSetup{&machine};
QState sMessage{&machine};
sWindows.assignProperty(qApp, "quitOnLastWindowClosed", false);
sWindows.addTransition(qApp, "lastWindowClosed()", &sSetup);
object.connect(&sSetup, SIGNAL(entered()), SLOT(stop()));
count.connect(&object, SIGNAL(countedTo(int)), SLOT(setNum(int)));
sSetup.addTransition(&sMessage);
sMessage.assignProperty(&message, "visible", true);
sMessage.assignProperty(&count, "visible", true);
sMessage.assignProperty(qApp, "quitOnLastWindowClosed", true);
machine.setInitialState(&sWindows);
machine.start();
widget.show();
return app.exec();
}
#include "main.moc"
I modified your code and now it works. I override closeEvent
==> test.cpp <==
#include "windows.hpp"
int exitprogram = 0;
int main(int argc, char** argv)
{
QApplication app(argc, argv);
MyMainWindow w;
w.show();
while(!exitprogram)
{
app.processEvents(QEventLoop::AllEvents, 20);
}
}
==> test.pro <==
TEMPLATE = app
TARGET = test
QT += widgets
INCLUDEPATH += .
HEADERS += windows.hpp
# Input
SOURCES += test.cpp
==> windows.hpp <==
#include <QApplication>
#include <QDebug>
#include <QObject>
#include <QMainWindow>
#include <QCloseEvent>
extern int exitprogram;
class MyMainWindow : public QMainWindow
{
Q_OBJECT
public slots:
void closeEvent(QCloseEvent *event) override {
exitprogram = 1;
event->accept();
}
};
Basically, I want a simple pushButton with a colorful text which when pressed exits the application.
Why cant I press PushButton in this simple program. I am using QT 4.6 on Arch x86_64.
#include <QtGui/QApplication>
#include <QLabel>
#include <QPushButton>
#include<QtGui>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow *Main=new QMainWindow;
QPushButton *button = new QPushButton(Main);
QLabel *label = new QLabel(Main);
label->setText("<h2><i>Hello</i> ""<font color=red>Qt!</font></h2>");
label->setVisible(true);
QObject::connect(button, SIGNAL(clicked()),label, SLOT(close()));
label->setAlignment(Qt::AlignCenter|Qt::AlignVCenter);
label->setWindowTitle("HelloWorld Test Program");
Main->show();
return a.exec();
}
Beside the use of a button class that will allow you to display rich text, you also need to make sure your connections are correct.
In your example, you're connecting the clicked signal of the button to the clear() slot of the label, which is non-sense.
To exit your app when the button is clicked, you need to close the main window. Here is the code to get the right connection :
QObject::connect(button, SIGNAL(clicked()),Main, SLOT(close()));
Changing this single line of code in your example is not enough, because your label is drawn on top of your button, so it's not possible to graphically click on it. You need to hide your label and put some text into your button :
button->setText("Hello");
label->setVisible(false);
Regarding the rich text feature in a QPushButton, AFAIK it is not possible to do it with a QPushButton.
UPDATE :
Here is a way to put some richtext on a QPushButton. It uses the solution described by my comment : painting a QTextDocument onto a pixmap and setting this pixmap as the button's icon.
#include <QtGui/QApplication>
#include <QLabel>
#include <QPushButton>
#include <QtGui>
#include <QTextDocument>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow *Main=new QMainWindow;
QPushButton *button = new QPushButton(Main);
QTextDocument Text;
Text.setHtml("<h2><i>Hello</i> ""<font color=red>Qt!</font></h2>");
QPixmap pixmap(Text.size().width(), Text.size().height());
pixmap.fill( Qt::transparent );
QPainter painter( &pixmap );
Text.drawContents(&painter, pixmap.rect());
QIcon ButtonIcon(pixmap);
button->setIcon(ButtonIcon);
button->setIconSize(pixmap.rect().size());
QObject::connect(button, SIGNAL(clicked()),Main, SLOT(close()));
Main->show();
return a.exec();
}
Take a look here. Widget called QwwRichTextButton.
The QwwRichTextButton widget provides a button that can display rich text.