Модальное диалоговое окно блокирует родительский виджет. Блокировка снимается только при закрытии диалогового окна. Такое поведение оказывается проще при реализации, поскольку оно ограничивает свободу пользователя и снижает количество возможных проблем. Однако пользователи могут счесть это недостатком.
Немодальное диалоговое окно обладает в точности противоположным поведением по сравнению с модальным. Пользователь может взаимодействовать и с родительским виджетом, и с диалоговым окном, переключаясь между ними. Управлять подобной логикой взаимодействия сложнее. Но в замен пользователь получает свободу, которой ему могло не хватать в модальном варианте.
Выбор типа диалогового окна основывается на здравом смысле и относится к этапу юзабилити-тестирования. Мы этого вопроса касаться не станем. Рассмотрим лишь технику создания и тех, и других диалоговых окон с помощью 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();
}
На нашем диалоговом окне мы разместили одно поле ввода и три кнопки: 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() );
}
}
Виджет содержит текстовое поле и две кнопки. Первая предназначена для вызова модального диалогового окна, а вторая - для немодального. Сосредоточимся на первой.
Модальное диалоговое окно обеспечивает блокировку вызывающей функции с помощью вызова 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()
.
Освобождать указатель на немодальное диалоговое окно нам не нужно. Это произойдет автоматически при вызове деструктора родительского виджета. В этот же момент диалоговое окно закроется.
Если запустить немодальное диалоговое окно, то родительский виджет остается доступен. Например, мы можем открыть после этого еще и модальное диалоговое окно. Заметим, что в этом случае блокируется и родительский виджет, и немодальное диалоговое окно.
Крайне не рекомендую применять диалоговые окна для вывода результатов выполнения операций или сообщений об ошибках (хотя для обработки серьезных сбоев можно и подумать). В этом случае они только мешают и раздражают пользователей. Их лучше всего использовать для вынесения второстепенных функций приложения, которые просто не умещаются на форме главного виджета. Все остальное зависит от конкретных задач и целевой аудитории.