Модальное диалоговое окно блокирует родительский виджет. Блокировка снимается только при закрытии диалогового окна. Такое поведение оказывается проще при реализации, поскольку оно ограничивает свободу пользователя и снижает количество возможных проблем. Однако пользователи могут счесть это недостатком.
Немодальное диалоговое окно обладает в точности противоположным поведением по сравнению с модальным. Пользователь может взаимодействовать и с родительским виджетом, и с диалоговым окном, переключаясь между ними. Управлять подобной логикой взаимодействия сложнее. Но в замен пользователь получает свободу, которой ему могло не хватать в модальном варианте.
Выбор типа диалогового окна основывается на здравом смысле и относится к этапу юзабилити-тестирования. Мы этого вопроса касаться не станем.
Класс диалогового окна
Реализуем простой класс диалогового окна. Именно его мы попробуем вызывать в модальном и немодальном режимах.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
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().
Модальное диалоговое окно
Реализуем виджет, который будет использовать наше диалоговое окно:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
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() для вызова нашего диалогового окна в немодальном режиме:
1 2 3 4 5 6 7 8 9 |
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().
Освобождать указатель на немодальное диалоговое окно нам не нужно. Это произойдет автоматически при вызове деструктора родительского виджета. В этот же момент диалоговое окно закроется.
Если запустить немодальное диалоговое окно, то родительский виджет остается доступен. Например, мы можем открыть после этого еще и модальное диалоговое окно. Заметим, что в этом случае блокируется и родительский виджет, и немодальное диалоговое окно.
Выводы
Крайне не рекомендую применять диалоговые окна для вывода результатов выполнения операций или сообщений об ошибках (хотя для обработки серьезных сбоев можно и подумать). В этом случае они только мешают и раздражают пользователей. Их лучше всего использовать для вынесения второстепенных функций приложения, которые просто не умещаются на форме главного виджета. Все остальное зависит от конкретных задач и целевой аудитории.