How can I prevent QDialog class from closing

后端 未结 3 821
没有蜡笔的小新
没有蜡笔的小新 2021-01-18 16:09

How can I prevent QDialog class from closing after \"Ok\" button was pressed? I need to close the window only if the some actions were performed correctly on this dialog, in

3条回答
  •  囚心锁ツ
    2021-01-18 16:52

    Generally speaking, it's a bad habit to lie to the user. If a button is not disabled, then it better work when the user clicks on it.

    So, the obvious solution is to disable the button until the necessary preconditions are met. For buttons that finish the dialog, you should be using QDialogButtonBox instead of discrete buttons, since on different platforms those buttons will be arranged differently within the box - based on the roles/types of the buttons.

    Below is an example of how it might be done. Works with Qt 4 and 5.

    Care has been taken for the code to interoperate with existing stylesheets.

    screenshot

    // https://github.com/KubaO/stackoverflown/tree/master/questions/buttonbox-22404318
    #include 
    #if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
    #include 
    #endif
    #include 
    

    First, let's have some stylesheet manipulation helpers:

    void styleSheetSet(QWidget *w, const QString &what) {
       auto const token = QStringLiteral("/*>*/%1/*<*/").arg(what);
       if (what.isEmpty() || w->styleSheet().contains(token)) return;
       w->setStyleSheet(w->styleSheet().append(token));
    }
    
    void styleSheetClear(QWidget *w, const QString &what) {
       const auto token = QStringLiteral("/*>*/%1/*<*/").arg(what);
       if (what.isEmpty() || ! w->styleSheet().contains(token)) return;
       w->setStyleSheet(w->styleSheet().remove(token));
    }
    
    void styleSheetSelect(QWidget *w, bool selector,
                          const QString & onTrue,
                          const QString & onFalse = {})
    {
       styleSheetSet(w, selector ? onTrue : onFalse);
       styleSheetClear(w, selector ? onFalse : onTrue);
    }
    
    template 
    void setSelect(QSet &set, bool b, const U &val) {
       if (b) set.insert(val); else set.remove(val);
    }
    

    And a recursive parent search:

        bool hasParent(QObject *obj, QObject *const parent) {
           Q_ASSERT(obj);
           while (obj = obj->parent())
              if (obj == parent) return true;
           return obj == parent;
        }
    

    The DialogValidator manages validators for a single dialog box. First, the slots invoked when the contents of a QLineEdit and a generic widget change:

    class DialogValidator : public QObject {
       Q_OBJECT
       QSet m_validWidgets;
       int m_needsValid = 0;
       Q_SLOT void checkWidget() {
          if (sender()->isWidgetType())
             checkValidity(static_cast(sender()));
       }
       Q_SLOT void checkLineEdit() {
          if (auto *l = qobject_cast(sender()))
             checkValidity(l);
       }
       void checkValidity(QLineEdit *l) {
          indicateValidity(l, l->hasAcceptableInput());
       }
       void checkValidity(QWidget *w) {
          auto validator = w->findChild();
          if (!validator) return;
          auto prop = w->metaObject()->userProperty();
          QVariant value = prop.read(w);
          int pos;
          QString text = value.toString();
          bool isValid =
                validator->validate(text, pos) == QValidator::Acceptable;
          indicateValidity(w, isValid);
       }
       void indicateValidity(QWidget *w, bool isValid) {
          auto *combo = qobject_cast(w->parentWidget());
          setSelect(m_validWidgets, isValid, combo ? combo : w);
          styleSheetSelect(w, !isValid,
                           QStringLiteral("%1 { background: yellow }")
                           .arg(QLatin1String(w->metaObject()->className())));
          emit newValidity(m_validWidgets.count() == m_needsValid);
       }
    

    The validators are added to the dialog validator using the add methods. If we desire special handling for a dynamically typed widget, the addPoly method should be used - it'll dispatch to type-specific overloads, if any:

       template
       typename std::enable_if::value, bool>::type
       addPoly(W* w, QValidator *v) {
          if (!w) return false;
          return (add(w,v), true);
       }
    public:
       DialogValidator(QObject *parent = {}) : QObject(parent) {}
       Q_SIGNAL void newValidity(bool);
       void addPoly(QWidget *w, QValidator *v) {
          addPoly(qobject_cast(w), v) ||
                addPoly(qobject_cast(w), v) ||
                (add(w, v), true);
       }
    

    Then, the static-typed add methods:

       void add(QComboBox *b, QValidator *v) {
          if (auto *l = b->lineEdit())
             add(l, v);
       }
       void add(QLineEdit *l, QValidator *v) {
          l->setValidator(v);
          connect(l, SIGNAL(textChanged(QString)), SLOT(checkLineEdit()));
          m_needsValid++;
          checkValidity(l);
       }
       void add(QWidget *w, QValidator *v) {
          Q_ASSERT(hasParent(v, w));
          auto prop = w->metaObject()->userProperty();
          auto propChanged = prop.notifySignal();
          static auto check = metaObject()->method(metaObject()->indexOfSlot("checkWidget()"));
          Q_ASSERT(check.isValid());
          if (!prop.isValid() || !propChanged.isValid())
             return qWarning("DialogValidator::add: The target widget has no user property with a notifier.");
          if (connect(w, propChanged, this, check)) {
             m_needsValid++;
             checkValidity(w);
          }
       }
    

    And finally, the convenience method that constructs the validator:

       template 
       typename std::enable_if<
       std::is_base_of::value && std::is_base_of::value, V*>::type
       add(W *w, Args...args) {
          V *validator = new V(std::forward(args)..., w);
          return add(w, validator), validator;
       }
    };
    

    Our dialog with validation:

    class MyDialog : public QDialog {
       Q_OBJECT
       QFormLayout m_layout{this};
       QLineEdit m_cFactor;
       QLineEdit m_dFactor;
       QDialogButtonBox m_buttons{QDialogButtonBox::Ok | QDialogButtonBox::Cancel};
       DialogValidator m_validator;
    public:
       MyDialog(QWidget *parent = {}, Qt::WindowFlags f = {}) : QDialog(parent, f) {
          m_layout.addRow("Combobulation Factor", &m_cFactor);
          m_layout.addRow("Decombobulation Factor", &m_dFactor);
          m_layout.addRow(&m_buttons);
          connect(&m_buttons, SIGNAL(accepted()), SLOT(accept()));
          connect(&m_buttons, SIGNAL(rejected()), SLOT(reject()));
          connect(&m_validator, SIGNAL(newValidity(bool)),
                  m_buttons.button(QDialogButtonBox::Ok), SLOT(setEnabled(bool)));
          // QLineEdit-specific validator
          m_validator.add(&m_cFactor, 0, 50);
          // Generic user property-based validator
          m_validator.add(&m_dFactor, -50, 0);
       }
    };
    
    int main(int argc, char *argv[])
    {
       QApplication a(argc, argv);
       MyDialog d;
       d.show();
       return a.exec();
    }
    #include "main.moc"
    

提交回复
热议问题