You can't reimplement closeEvent
to prevent closing a window. The close()
call that you do is either redundant or an error (infinite recursion), since a closeEvent
method call is just a way of being notified that a closing is imminent. At that point it's too late to do anything about it.
Keep in mind the following:
Closing a dialog usually is equivalent to canceling the dialog. Only clicking OK should accept the changes.
When a user wants to close a dialog, you don't have to ask them about it. They initiated the action. But:
It is proper to ask a user about dialog closure if there are changes have not been accepted - on platforms other than OS X.
So, you have to do several things:
Reimplement the void event(QEvent*)
method. This allows you to reject the close event.
Offer Apply/Reset/Cancel
buttons.
Your flag approach can be automated. You can find all the controls of the dialog box and set the connections automatically. Repeat the statement below for every type of control - this gets tedious rather quickly:
foreach(QTextEdit* w, findChildren<QTextEdit*>())
connect(w, SIGNAL(textChanged(QString)), SLOT(functChanged()));
You can leverage the meta property system. Most controls have a user
property - that's the property that holds the primary value of the control (like text, selected item, etc). You can scan all of the widget children, and connect the property change notification signal of the user property to your flag:
QMetaMethod slot = metaObject().method(
metaObject().indexOfSlot("functChanged()"));
foreach (QWidget* w, findChildren<QWidget*>()) {
QMetaObject mo = w->metaObject();
if (!mo.userProperty().isValid() || !mo.userProperty().hasNotifySignal())
continue;
connect(w, mo.notifySignal(), this, slot);
}
Each widget is a QObject
. QObjects can have properties, and one of the properties can be declared to be the user property. Most editable widget controls have such a property, and it denotes the user input (text, numerical value, selected index of the item, etc.). Usually such properties also have change notification signals. So all you do is get the QMetaMethod
denoting the notification signal, and connect it to your function that sets the flag.
To determine the changed fields, you don't necessarily need a flag. In many dialog boxes, it makes sense to have a data structure that represent the data in the dialog. You can then have a get
and set
method that retrieves the data from the dialog, or sets it on the dialog. To check for changed data, simply compare the original data to current data:
struct UserData {
QString name;
int age;
UserData(const QString & name_, int age_) :
name(name_), age(age_) {}
UserData() {}
};
class DialogBase : public QDialog {
QDialogButtonBox m_box;
protected:
QDialogButtonBox & buttonBox() { return m_box; }
virtual void isAccepted() {}
virtual void isApplied() {}
virtual void isReset() {}
virtual void isRejected() {}
public:
DialogBase(QWidget * parent = 0) : QDialog(parent) {
m_box.addButton(QDialogButtonBox::Apply);
m_box.addButton(QDialogButtonBox::Reset);
m_box.addButton(QDialogButtonBox::Cancel);
m_box.addButton(QDialogButtonBox::Ok);
connect(&m_box, SIGNAL(accepted()), SLOT(accept()));
connect(&m_box, SIGNAL(rejected()), SLOT(reject()));
connect(this, &QDialog::accepted, []{ isAccepted(); });
connect(this, &QDialog::rejected, []{ isRejected(); });
connect(&buttonBox(), &QDialogButtonBox::clicked, [this](QAbstractButton* btn){
if (m_box.buttonRole(btn) == QDialogButtonBox::ApplyRole)
isApplied();
else if (m_box.buttonRole(btn) == QDialogButtonBox::ResetRole)
isReset();
});
}
}
class UserDialog : public DialogBase {
QFormLayout m_layout;
QLineEdit m_name;
QSpinBox m_age;
UserData m_initialData;
public:
UserDialog(QWidget * parent = 0) : QDialog(parent), m_layout(this) {
m_layout.addRow("Name", &m_name);
m_layout.addRow("Age", &m_age);
m_age.setRange(0, 200);
m_layout.addRow(&buttonBox());
}
/// Used by external objects to be notified that the settings
/// have changed and should be immediately put in effect.
/// This signal is emitted when the data was changed.
Q_SIGNAL void applied(UserData const &);
UserData get() const {
return UserData(
m_name.text(), m_age.value());
}
void set(const UserData & data) {
m_name.setText(data.name);
m_age.setValue(data.age);
}
void setInitial(const UserData & data) { m_initialData = data; }
bool isModified() const { return get() == m_initialData; }
protected:
void isAccepted() Q_DECL_OVERRIDE { emit applied(get()); }
void isApplied() Q_DECL_OVERRIDE { emit applied(get()); }
void isReset() Q_DECL_OVERRIDE { set(m_initialData); }
};