Reputation: 107
In basically all relevant places I look, it says to avoid exec
because it blocks the parent. There's also reports stating they should deprecate it. However I find myself practically unable to achieve my current task without exec.
For our company, I cloned a library which allows us to render reports and modified it to suit our needs (licenses were all checked), and within those changes was adjusting the variables so the user could input something for them. A couple of bugs/requests came up, and I moved away from QMessageBox::getItem
/getText
/getDouble
/etc to my own implemented QDialog derivative.
#include <QDialog>
#include "const.h"
namespace Ui {
class VariableInput;
}
class VarInput : public QDialog {
Q_OBJECT
public:
// constructor, couple of methods, etc
void open();
private:
Ui::VariableInput *ui;
}
Now, all of these documentations/user requests generally said to avoid using exec
, so I attempted to find solutions which did not use it, not a single one of which could match what I needed to do. The variable dialogs get shown to the user, then saved to a cache so they dont get asked multiple times, and finally the report renders.
bool DataManager::replaceVariables(QString query)
{
// bunch of code checking for variable and extracting the name
while (query.contains(variablemarker)) {
QString name = "statically assigned for test purposes";
VarInput inputDia;
inputDia.setName(name);
inputDia.setValue(getVarDefaultValue(name));
inputDia.setType(getVarDataType(name))
inputDia.open();
if(inputDia.isCanceled) return false; //Cancel rendering
m_cache.insert(name,inputDia.getValue());
query.replace(name, inputDia.getValue());
}
return true;
}
Of course, all code shortened/condensed for relevancy.
I don't want to completely overhaul basically the entire library, simply so in this tiny instance I can use a slot in the DataManager
so I can "loop" over the variables here, and assign a slot, especially given that I would currently simply reconstruct the thing over and over (currently: getting the variable in the real code is handled by a different function, which gets called in multiple places, if the user cancels, an error is thrown, which gets caught in this function and then false is returned; in the end it basically condenses down to this)
Our company told us to write "future proof" code as best as we can, so in an attempt to follow that, I wanted to avoid using exec
instead opting for an open
approach as Qt suggests. However, unless I rewrite basically the entire core library, I cannot simply open the input dialog, and then have the rendering proceed. I effectively have to block execution of the thread which handles the rendering.
I modified an existing dialog (which is in a similar situation) to use ApplicationModal
and open
which just causes it to flick in and out of existence for a split second. As far as I understand it, open
doesn't spin up its own event loop, hence why it does that, I'd need to modify it (which would also be a massive rework) to make it respond to a slot in the outer object (the DataManager
in this case), then somehow make the execution respond to wherever the function was originally called (the modified dialog gets called in around 10 different places for various reasons, the VarInput dialog would get called only in 4 different places) which would then continue the rendering process (somehow I'd have to pause that process too, since blocking the event loop is bad)
The only way I can see a dialog using open
/show
in a situation like this working out is a massive rework of the entire project, which is rather undesirable. I can also see other times when I'd be a similar situation where not blocking the event loop might be absolutely terrible for user experience and the programming effort required to do that (Things of the sort that Wireshark might do).
Why are some people so heavily discouraging the use of exec
(when its literally the thing you use for the application start) and how would I get around it in a case like this, where I'm using a simple loop to replace the variables, blocking execution to allow the user to input the value?
Edit: Some minimal examples that show the behavior (compiled locally using Qt6, C++17, with this CMakeLists: https://pastebin.com/GmMSy2Rr)
Code equivalent original: https://pastebin.com/wSh8Risu (Uses Qt's messagebox)
Code equivalent with exec: https://pastebin.com/VHYkzieQ (Custom messagebox, but using exec)
How would I achieve the same functionality using open
or show
Upvotes: 1
Views: 891
Reputation: 3967
As I hope I clarified in the comments, your issue comes from a misunderstanding that you can call QDialog::show()
before starting the main event loop, in QApplication::exec()
.
This is not how it works. The only widget you can call before starting the event loop is a modal message box displayed with exec()
, as stated in the assistant.
Replacing QDialog::exec()
by QDialog::show()
is trivial when inside the event loop. You simply have to:
QDialog::exec()
.exec()
into a slot.finished
signal of your messsage box.exec
for show
The below example demonstrates this method. See MyWidget::execMsgBox
vs MyWidget::showMsgBox
, with the 2 ways MyWidget::handleMessageBoxResult
is called to use the result.
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QLabel>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QApplication>
class MyWidget : public QWidget {
public:
MyWidget() : QWidget(nullptr) {
label = new QLabel("You clicked nothing yet", this);
label->setMinimumWidth(280);
QPushButton* execButton = new QPushButton("Exec", this);
execButton->move(4, 30);
QPushButton* showButton = new QPushButton("Show", this);
showButton->move(84, 30);
connect(execButton, &QAbstractButton::clicked,
this, &MyWidget::execMsgBox);
connect(showButton, &QAbstractButton::clicked,
this, &MyWidget::showMsgBox);
}
void execMsgBox()
{
QMessageBox* msgBox = createMsgBox("Exec dialog");
int result = msgBox->exec();
handleMsgBoxResult(result);
}
void showMsgBox()
{
QMessageBox* msgBox = createMsgBox("Show dialog");
msgBox->setModal(true);
connect(msgBox, &QMessageBox::finished,
this, &MyWidget::handleMsgBoxResult);
msgBox->show();
}
protected:
QMessageBox* createMsgBox(const QString title)
{
return new QMessageBox(
QMessageBox::Icon::NoIcon,
title,
"Click yes or no",
QMessageBox::Yes | QMessageBox::No
);
}
void handleMsgBoxResult(int result)
{
switch (result) { //With exec, default is never reached.
case QMessageBox::No: label->setText("You clicked no."); break;
case QMessageBox::Yes: label->setText("You clicked yes."); break;
default: label->setText("You closed the message box without answering.");
}
}
private:
QLabel* label;
};
int main(int argc, char** arga)
{
QApplication a(argc, arga);
MyWidget w;
w.show();
return a.exec();
}
Upvotes: 0