Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I prevent QDialog class from closing

Tags:

c++

qt

qt-creator

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.

like image 763
FrozenHeart Avatar asked Mar 14 '14 11:03

FrozenHeart


2 Answers

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"
like image 157
Kuba hasn't forgotten Monica Avatar answered Oct 22 '22 17:10

Kuba hasn't forgotten Monica


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().

like image 22
tawnos178 Avatar answered Oct 22 '22 18:10

tawnos178