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