Введение
QMessageBox (статья 221) хорош для простых сообщений, но когда нужно собрать от пользователя несколько полей ввода — например, форму «Новый контакт» с именем, телефоном и email — нужен собственный диалог на основе QDialog. В статье разберём, как создать простое модальное диалоговое окно с полями ввода и кнопками подтверждения/отмены.
Концепция
QDialog — базовый класс для модальных и немодальных диалоговых окон. Модальный диалог (показанный через exec()) блокирует взаимодействие с родительским окном до своего закрытия и возвращает результат — QDialog::Accepted или QDialog::Rejected, в зависимости от того, как диалог был закрыт. Стандартные кнопки «ОК»/»Отмена» удобно создавать через QDialogButtonBox, который автоматически связывает нажатие кнопок с методами accept()/reject() диалога.
Пример кода: QDialog
#include <QDialog>
#include <QLineEdit>
#include <QLabel>
#include <QFormLayout>
#include <QDialogButtonBox>
#include <QApplication>
#include <QPushButton>
#include <QDebug>
class NewContactDialog : public QDialog
{
Q_OBJECT
public:
explicit NewContactDialog(QWidget *parent = nullptr) : QDialog(parent)
{
setWindowTitle("Новый контакт");
m_nameEdit = new QLineEdit(this);
m_phoneEdit = new QLineEdit(this);
m_emailEdit = new QLineEdit(this);
auto *formLayout = new QFormLayout();
formLayout->addRow("Имя:", m_nameEdit);
formLayout->addRow("Телефон:", m_phoneEdit);
formLayout->addRow("Email:", m_emailEdit);
// Стандартный набор кнопок ОК/Отмена с автоматической обработкой
auto *buttonBox = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttonBox, &QDialogButtonBox::accepted, this, &NewContactDialog::onAccept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
auto *mainLayout = new QVBoxLayout(this);
mainLayout->addLayout(formLayout);
mainLayout->addWidget(buttonBox);
}
QString contactName() const { return m_nameEdit->text(); }
QString contactPhone() const { return m_phoneEdit->text(); }
QString contactEmail() const { return m_emailEdit->text(); }
private slots:
void onAccept()
{
// Простая проверка перед принятием диалога — нельзя сохранить контакт без имени
if (m_nameEdit->text().trimmed().isEmpty()) {
m_nameEdit->setFocus();
return; // не вызываем accept() — диалог остаётся открытым
}
accept(); // закрывает диалог с результатом QDialog::Accepted
}
private:
QLineEdit *m_nameEdit;
QLineEdit *m_phoneEdit;
QLineEdit *m_emailEdit;
};
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow()
{
auto *addButton = new QPushButton("Добавить контакт", this);
auto *layout = new QVBoxLayout(this);
layout->addWidget(addButton);
connect(addButton, &QPushButton::clicked, this, &MainWindow::onAddContact);
}
private slots:
void onAddContact()
{
NewContactDialog dialog(this);
// exec() блокирует выполнение до закрытия диалога и возвращает результат
if (dialog.exec() == QDialog::Accepted) {
qDebug() << "Новый контакт:" << dialog.contactName()
<< dialog.contactPhone() << dialog.contactEmail();
} else {
qDebug() << "Добавление контакта отменено";
}
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}
#include "main.moc"
Пояснения к коду
NewContactDialog собирает форму через QFormLayout (автоматически выравнивающий подписи и поля ввода в две колонки) и добавляет стандартный QDialogButtonBox с кнопками «ОК»/»Отмена» — подключение accepted/rejected к соответствующим методам автоматизирует стандартное поведение закрытия диалога. Методы contactName()/contactPhone()/contactEmail() — простой способ «забрать» введённые пользователем данные из диалога уже после его закрытия.
onAccept() показывает важный паттерн — валидацию перед принятием диалога: если имя не заполнено, метод просто возвращается без вызова accept(), и диалог остаётся открытым, позволяя пользователю исправить ошибку, вместо того чтобы закрыть диалог с неполными данными. В MainWindow::onAddContact() диалог создаётся как локальная переменная на стеке, показывается через exec(), и в зависимости от возвращённого значения (QDialog::Accepted/Rejected) код решает, использовать ли введённые данные.
Подводные камни
- Создание диалога без указания родителя. Хотя в примере родитель передан (
this), забытое указание родителя может привести к тому, что диалог появится не по центру относительно главного окна, а в произвольном месте экрана, и не будет визуально связан с вызвавшим его окном в восприятии пользователя. - Чтение значений полей диалога через публичные методы до его закрытия или без проверки результата
exec(). Если забыть проверить, что диалог был именно принят (Accepted), а не отменён, и всё равно использовать введённые данные, можно обработать пустые или невалидные значения, оставшиеся в полях после отмены. - Использование
exec()(модальный, блокирующий показ) там, где на самом деле нужен немодальный диалог (show()), позволяющий пользователю одновременно работать и с диалогом, и с основным окном — выбор междуexec()иshow()должен соответствовать реальной логике взаимодействия, а не быть выбран «по умолчанию» без размышления. - Отсутствие валидации введённых данных перед принятием диалога. Без проверки (как показано с именем в примере) пользователь может закрыть диалог с пустыми или некорректными полями, и эта проблема обнаружится только позже, при попытке использовать эти данные где-то в другом месте программы — лучше проверять данные сразу, не давая диалогу закрыться с неполной информацией.