How can I prevent QDialog class from closing

2019-02-26 11:13发布

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 other cases I don't need to close this window.

3条回答
来,给爷笑一个
2楼-- · 2019-02-26 11:44

The same as from @kuba-ober, but using custom property. Change one function:

void QDialogValidator::checkValidity(QLineEdit * l) {
  if (!l) return;
  QString text = l->text();
  int pos;
  bool isValid = l->validator()->validate(text, pos) == QValidator::Acceptable;
  setSelect(m_validWidgets, isValid, (QWidget*)l);
  if (!l->property("invalid").toBool() != isValid) {
    l->setProperty("invalid", !isValid);
    l->style()->unpolish(l);
    l->style()->polish(l);
  }
  emit newValidity(m_validWidgets.count() == m_needsValid);
}

Now styleSheet... functions are not necessary but we need to add constant style sheet for the dialog or application:

d.setStyleSheet("*[invalid=\"true\"] { background: yellow }");

Or if use Qt designer, add root widget property:

<property name="styleSheet">
 <string notr="true">*[invalid="true"] { border-style: solid; border-width: 1px; border-radius: 2px; border-color: yellow; }</string>
</property>
查看更多
贪生不怕死
3楼-- · 2019-02-26 11:58

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 <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
#include <type_traits>

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 <typename T, typename U>
void setSelect(QSet<T> &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<QWidget*> m_validWidgets;
   int m_needsValid = 0;
   Q_SLOT void checkWidget() {
      if (sender()->isWidgetType())
         checkValidity(static_cast<QWidget*>(sender()));
   }
   Q_SLOT void checkLineEdit() {
      if (auto *l = qobject_cast<QLineEdit*>(sender()))
         checkValidity(l);
   }
   void checkValidity(QLineEdit *l) {
      indicateValidity(l, l->hasAcceptableInput());
   }
   void checkValidity(QWidget *w) {
      auto validator = w->findChild<QValidator*>();
      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<QComboBox*>(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 W>
   typename std::enable_if<!std::is_same<QWidget,W>::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<QLineEdit*>(w), v) ||
            addPoly(qobject_cast<QComboBox*>(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 V, typename W, typename...Args>
   typename std::enable_if<
   std::is_base_of<QWidget, W>::value && std::is_base_of<QValidator, V>::value, V*>::type
   add(W *w, Args...args) {
      V *validator = new V(std::forward<Args>(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<QIntValidator>(&m_cFactor, 0, 50);
      // Generic user property-based validator
      m_validator.add<QIntValidator, QWidget>(&m_dFactor, -50, 0);
   }
};

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   MyDialog d;
   d.show();
   return a.exec();
}
#include "main.moc"
查看更多
Animai°情兽
4楼-- · 2019-02-26 12:00

While you probably shouldn't make a button available that won't do anything when the user clicks it, if you absolutely must override the default close behavior, then you need to override QDialog::accept().

查看更多
登录 后发表回答