Lilly
Lilly

Reputation: 107

Qt Modal dialog without exec

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

Answers (1)

Atmo
Atmo

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:

  1. Take the code that contains QDialog::exec().
  2. Move everything that needs the result of exec() into a slot.
  3. Connect that slot to the finished signal of your messsage box.
  4. Substitute 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

Related Questions