Search code examples
c++qtqmessageboxqt5.7

QMessageBox result as a buttonrole


I wanna get result of QMessagebox as a buttonrole. but result always 16384 or 65536 I dont wantto use standartresults, want to use only buttonrole kind of buttons. what am I doing wrong here ?
(Im very newbie in QT)

    void MainWindow::on_pushButton_clicked()
    {
        QMessageBox msgBox;
        QPushButton *a=msgBox.addButton("OK",QMessageBox::ActionRole);
        QPushButton *b=msgBox.addButton("CANCEL",QMessageBox::RejectRole);

        int result=msgBox.question(this,"Hola","My 1st Msg");
        //result always return 16384 or 65536(integer) PROBLEM HERE
        if(result==QMessageBox::RejectRole)
            this->setWindowTitle("rejected");
        else
            this->setWindowTitle("accepted");
    }

Solution

  • The question method is static. It doesn't use the message box you have above. The first 3 lines of your method essentially don't do anything.

    Here's what your method really does:

    void MainWindow::on_pushButton_clicked()
    {
        int result = QMessageBox::question(this,"Hola","My 1st Msg");
        [...]
    }
    

    Alas, the QMessageBox has a long-standing bug: it ignores (!) the custom button roles when it comes to acceptance or rejection of the dialog. While the role is passed to the underlying QDialogButtonBox, it is not interpreted correctly when the button is clicked.

    Although you can get the role back using QMessageBox::buttonRole, the QMessageBoxPrivate::_q_buttonClicked invokes QDialog::done with the index of the button.

    Thus, the first button you add will cause the dialog to be rejected, the second one will cause it to be accepted, and further buttons will cause neither. The acceptance/rejection ignores the role completely and is only based on the index of the button, due to the order it was added in.

    Thus you should not use the rejected/accepted signals, unless the first two buttons map directly to these roles in this order, and should use the buttonClicked signal and obtain the role of the button directly:

    void MainWindow::on_pushButton_clicked()
    {
       auto box = new QMessageBox{this};
       box->setAttribute(Qt::WA_DeleteOnClose);
       box->addButton("OK", QMessageBox::ActionRole);
       box->addButton("CANCEL", QMessageBox::RejectRole);
       box->setIcon(QMessageBox::Question);
       box->setWindowTitle("Hola");
       box->setText("My 1st message.");
       box->show();
    
       connect(box, &QMessageBox::buttonClicked, [=](QAbstractButton *button){
          switch (box->buttonRole(button)) {
          case QMessageBox::AcceptRole: return setWindowTitle("accept-role");
          case QMessageBox::ActionRole: return setWindowTitle("action-role");
          case QMessageBox::RejectRole: return setWindowTitle("reject-role");
          }
       });
    }
    

    Alas, there's another problem: the dialog will be also rejected by closing it via the platform's window manager (the close button on the dialog's title bar). So you need to be able to use the rejected signal, but not when it's in error. It's best to factor this functionality out to a MessageBoxAdapter class that will only emit correct accepted and rejected signals:

    // https://github.com/KubaO/stackoverflown/tree/master/questions/messagebox-roles-40753898
    #include <QtWidgets>
    
    class MessageBoxAdapter : public QObject {
       Q_OBJECT
    public:
       MessageBoxAdapter(QObject *parent = nullptr) : QObject(parent) {
          watch(parent);
       }
       void watch(QObject *obj) {
          auto box = qobject_cast<QMessageBox*>(obj);
          if (!box) return;
          connect(box, &QMessageBox::rejected, [=]{
             if (!box->clickedButton()) emit rejected();
          });
          connect(box, &QMessageBox::buttonClicked, [=](QAbstractButton *button){
             auto role = box->buttonRole(button);
             if (role == QMessageBox::AcceptRole) emit accepted();
             else if (role == QMessageBox::RejectRole) emit rejected();
             emit roleClicked(role);
          });
       }
       Q_SIGNAL void accepted();
       Q_SIGNAL void rejected();
       Q_SIGNAL void roleClicked(QMessageBox::ButtonRole role);
    };
    

    And some user interface to try it out:

    struct Ui : public QWidget {
       QVBoxLayout layout{this};
       QTextBrowser browser;
       QPushButton button{"Open"};
       MessageBoxAdapter adapter{this};
    public:
       Ui() {
          layout.addWidget(&browser);
          layout.addWidget(&button);
          connect(&button, &QPushButton::clicked, this, &Ui::onClicked);
          connect(&adapter, &MessageBoxAdapter::accepted, [=]{ browser.append("accepted"); });
          connect(&adapter, &MessageBoxAdapter::rejected, [=]{ browser.append("rejected"); });
          connect(&adapter, &MessageBoxAdapter::roleClicked, [=](QMessageBox::ButtonRole role){
             browser.append(QStringLiteral("clicked role=%1").arg(role));
          });
       }
       void onClicked() {
          auto box = new QMessageBox{this};
          adapter.watch(box);
          box->setAttribute(Qt::WA_DeleteOnClose);
          box->addButton("OK", QMessageBox::AcceptRole);
          box->addButton("ACTION", QMessageBox::ActionRole);
          box->addButton("CANCEL", QMessageBox::RejectRole);
          box->setIcon(QMessageBox::Question);
          box->setWindowTitle("Hola");
          box->setText("My 1st message.");
          box->show();
       }
    };
    
    int main(int argc, char ** argv) {
       QApplication app{argc, argv};
       Ui ui;
       ui.show();
       return app.exec();
    }
    #include "main.moc"