Различные статьи по Qt и c++

QDialog: создание собственных диалоговых окон

Введение

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() должен соответствовать реальной логике взаимодействия, а не быть выбран «по умолчанию» без размышления.
  • Отсутствие валидации введённых данных перед принятием диалога. Без проверки (как показано с именем в примере) пользователь может закрыть диалог с пустыми или некорректными полями, и эта проблема обнаружится только позже, при попытке использовать эти данные где-то в другом месте программы — лучше проверять данные сразу, не давая диалогу закрыться с неполной информацией.