IT Notes

Диалоговые окна в Qt: Модальные и немодальные

Модальное диалоговое окно блокирует родительский виджет. Блокировка снимается только при закрытии диалогового окна. Такое поведение оказывается проще при реализации, поскольку оно ограничивает свободу пользователя и снижает количество возможных проблем. Однако пользователи могут счесть это недостатком.

Немодальное диалоговое окно обладает в точности противоположным поведением по сравнению с модальным. Пользователь может взаимодействовать и с родительским виджетом, и с диалоговым окном, переключаясь между ними. Управлять подобной логикой взаимодействия сложнее. Но в замен пользователь получает свободу, которой ему могло не хватать в модальном варианте.

Выбор типа диалогового окна основывается на здравом смысле и относится к этапу юзабилити-тестирования. Мы этого вопроса касаться не станем. Рассмотрим лишь технику создания и тех, и других диалоговых окон с помощью Qt.

Класс диалогового окна

Реализуем простой класс диалогового окна. Именно его мы попробуем вызывать в модальном и немодальном режимах.

class DemoDialog : public QDialog {
    Q_OBJECT

public:
    DemoDialog( QWidget* parent = 0 );
    ~DemoDialog();

    QString getInput() const;

signals:
    void applied();

private:
    QLineEdit* m_edit;
};

DemoDialog::DemoDialog( QWidget* parent ) : QDialog( parent ) {
    QBoxLayout* layout = new QHBoxLayout;
    m_edit = new QLineEdit;
    layout->addWidget( m_edit );

    QPushButton* okBtn = new QPushButton( "OK" );
    connect( okBtn, SIGNAL( clicked() ), SLOT( accept() ) );
    layout->addWidget( okBtn );

    QPushButton* applyBtn = new QPushButton( "Apply" );
    connect( applyBtn, SIGNAL( clicked() ), SIGNAL( applied() ) );
    layout->addWidget( applyBtn);

    QPushButton* cancelBtn = new QPushButton( "Cancel" );
    connect( cancelBtn, SIGNAL( clicked() ), SLOT( reject() ) );
    layout->addWidget( cancelBtn );

    setLayout( layout );
}

DemoDialog::~DemoDialog() {
}

QString DemoDialog::getInput() const {
    return m_edit->text();
}

qt-easy-dialog

На нашем диалоговом окне мы разместили одно поле ввода и три кнопки: OK, Apply и Cancel.

Кнопка OK предназначена для подтверждения ввода с последующим закрытием окна. Этого мы добились, связав сигнал щелчка по кнопке со стандартным слотом QDialog::accept().

Кнопка Apply так же предназначена для подтверждения ввода. Но предполагает продолжение работы с диалоговым окном. Для реализации такой логики мы предусмотрели сигнал applied().

Последняя кнопка Cancel просто закрывает диалоговое окно. Больше ничего не происходит. Введенные данные отклоняются. Для этого мы используем слот QDialog::reject().

Других кодов в QDialog не предусмотрено (кроме Accepted и Rejected). Поэтому существует слот QDialog::done( int ). Он закрывает окно и возвращает в качестве результата вызова указанное целочисленное значение.

Нажатие клавиши Esc в диалоговом окне приводит к вызову QDialog::reject(), а Enter связывается с кнопкой по умолчанию. Такой кнопкой становится либо первая добавленная кнопка (в нашем случае OK), либо кнопка, для которой вызвана функция btn->setDefault( true ).

Чтобы извлечь введенное значение в текстовое поле, мы предусмотрели константную функцию-член getInput().

Модальное диалоговое окно

Реализуем виджет, который будет использовать наше диалоговое окно:

class MainWidget : public QWidget {
    Q_OBJECT

public:
    MainWidget( QWidget* parent = 0 );
    ~MainWidget();

private slots:
    void onModalDemo();
    void onNonModalDemo();

    void onApplied();

private:
    QLineEdit* m_edit;

    DemoDialog* m_nonModalDlg;
};

MainWidget::MainWidget( QWidget* parent ) : QWidget( parent ), m_nonModalDlg( NULL ) {
    QBoxLayout* layout = new QHBoxLayout;
    m_edit = new QLineEdit;
    m_edit->setReadOnly( true );
    layout->addWidget( m_edit );

    QPushButton* btnModal = new QPushButton( "ModalDemo" );
    layout->addWidget( btnModal );
    connect( btnModal, SIGNAL( clicked() ), SLOT( onModalDemo() ) );

    QPushButton* btnNonModal = new QPushButton( "NonModalDemo" );
    layout->addWidget( btnNonModal );
    connect( btnNonModal, SIGNAL( clicked() ), SLOT( onNonModalDemo() ) );

    setLayout( layout );
}

MainWidget::~MainWidget() {
}

void MainWidget::onModalDemo() {
    DemoDialog dlg( this );
    connect( &dlg, SIGNAL( applied() ), SLOT( onApplied() ) );
    switch( dlg.exec() ) {
    case QDialog::Accepted:
        qDebug() << "Accepted";
        m_edit->setText( dlg.getInput() );
        break;
    case QDialog::Rejected:
        qDebug() << "Rejected";
        break;
    default:
        qDebug() << "Unexpected";
    }
}

void MainWidget::onNonModalDemo() {
    // TO DO
}

void MainWidget::onApplied() {
    if( DemoDialog* dlg = qobject_cast< DemoDialog* >( sender() ) ) {
        m_edit->setText( dlg->getInput() );
    }
}

qt-parent-widget

Виджет содержит текстовое поле и две кнопки. Первая предназначена для вызова модального диалогового окна, а вторая - для немодального. Сосредоточимся на первой.

Модальное диалоговое окно обеспечивает блокировку вызывающей функции с помощью вызова exec() (как у QApplication). Это дает нам полное право создавать такой диалог в виде локальной переменной. Обращаю внимание, что в конструкторе диалогового окна мы передаем указать на виджет. Это обеспечивает необходимую привязку.

Мы должны не забыть привязать сигнал applied() нашего диалогового окна к специально созданному слоту onApplied(). В нем мы просто устанавливаем значение введенной строки в качестве значения текстового поля виджета.

проститутки варшавская.

Момент отображения диалогового окна соответствует строке с вызовом dlg.exec(). В качестве ответа возвращается код завершения работы окна. Единственный интересный для нас случай: QDialog::Accepted. Для него мы делаем то же самое, что и в слоте onApplied().

Не забудьте убедиться, что родительский виджет становится неактивным, когда модальное диалоговое окно отображается.

Немодальное диалоговое окно

Добавим реализацию слота onNonModalDemo() для вызова нашего диалогового окна в немодальном режиме:

void MainWidget::onNonModalDemo() {
    if( !m_nonModalDlg ) {
        m_nonModalDlg = new DemoDialog( this );
        connect( m_nonModalDlg, SIGNAL( applied() ), SLOT( onApplied() ) );
        connect( m_nonModalDlg, SIGNAL( accepted() ), SLOT( onApplied() ) );
    }

    m_nonModalDlg->show();
}

Следует заметить, что для немодального диалогового окна мы завели отдельное поле класса в виджете. Это необходимо, поскольку немодальное окно не блокирует выполнение слота, поэтому локальная переменная оказалась бы уничтожена.

Для обработки ответа QDialog::Accepted мы делаем привязку к сигналу диалогового окна accepted(). Есть аналогичный сигнал для отрицательного ответа rejected(). А также для нашего собственного кода результата (на случай вызова слота done( int ) ): finished( int ).

Важным отличием является то, что отображаем диалог мы не с помощью exec(), а путем вызова show().

Освобождать указатель на немодальное диалоговое окно нам не нужно. Это произойдет автоматически при вызове деструктора родительского виджета. В этот же момент диалоговое окно закроется.

Если запустить немодальное диалоговое окно, то родительский виджет остается доступен. Например, мы можем открыть после этого еще и модальное диалоговое окно. Заметим, что в этом случае блокируется и родительский виджет, и немодальное диалоговое окно.

Выводы

Крайне не рекомендую применять диалоговые окна для вывода результатов выполнения операций или сообщений об ошибках (хотя для обработки серьезных сбоев можно и подумать). В этом случае они только мешают и раздражают пользователей. Их лучше всего использовать для вынесения второстепенных функций приложения, которые просто не умещаются на форме главного виджета. Все остальное зависит от конкретных задач и целевой аудитории.

Похожие публикации