IT Notes

QWizard: Создание Мастера в Qt

QWizard (называется Мастером или Визардом) позволяет легко создавать приложения, которые проводят пользователя по линейной последовательности страниц. Преимущество такого подхода заключается в том, что попасть на следующую страницу нельзя до тех пор, пока все предыдущие не окажутся корректно заполнены. Это упрощает программирование и дает пользователю лучше понять, что от него все же просят.

Займемся разработкой простого Мастера. В результате у нас получится следующее:

qwizard-demo-qt-application-sample-thumbnail

Мастер будет состоять из трех страниц:

  1. Начальной, где пользователь сможет ввести свое имя, а также выбрать путь дальнейшего движения по Мастеру;
  2. Промежуточной, которая зависит от данных, введенных пользователем на начальной странице;
  3. Финальной, которая обычно позволяет последний раз подумать, и принять окончательное решение о вступлении изменений в силу. Мы же просто попрощаемся с пользователем.

Подготовительный этап

Для осуществления навигации по Мастеру нам понадобятся идентификаторы страниц. Создадим заголовочный файл shared_defs.h:

#ifndef SHARED_DEFS_H
#define SHARED_DEFS_H

enum {
    INTRO_PAGE,
    MIDDLE_PAGE_PATH_1,
    MIDDLE_PAGE_PATH_2,
    FINAL_PAGE,
};

#endif // SHARED_DEFS_H

Назначение всех идентификаторов вполне понятно по названиям соответствующих констант, поэтому двигаемся дальше.

Начальная страница

Все страницы Мастера в Qt должны реализовывать класс QWizardPage. Создадим страницу IntroPage. Вот ее заголовочный файл intropage.h:

#ifndef INTROPAGE_H
#define INTROPAGE_H

#include <QWizardPage>

class QLineEdit;
class QRadioButton;
class QLabel;

class IntroPage : public QWizardPage {
    Q_OBJECT

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

protected:
    int nextId() const;
    bool validatePage();

private:
    QLineEdit* m_edit;
    QLabel* m_errorLbl;

    QRadioButton* m_rbtnPath1;
    QRadioButton* m_rbtnPath2;

};

#endif // INTROPAGE_H

Функции-члены nextId() и validatePage() перегружают виртуальные функции QWizardPage. Посмотрим на их реализацию в файле intropage.cpp:

#include "intropage.h"

#include <QLineEdit>
#include <QLabel>
#include <QRadioButton>
#include <QBoxLayout>

#include "shared_defs.h"

IntroPage::IntroPage( QWidget* parent )
    : QWizardPage( parent ) {

    setTitle( trUtf8( "Начальная страница" ) );
    setSubTitle( trUtf8( "Пожалуйста, представьтесь." ) );

    QBoxLayout* mainLayout = new QVBoxLayout;

    mainLayout->addWidget( m_edit = new QLineEdit );
    mainLayout->addWidget( m_errorLbl = new QLabel );
    mainLayout->addWidget( m_rbtnPath1 = new QRadioButton( trUtf8( "Путь 1" ) ) );
    mainLayout->addWidget( m_rbtnPath2 = new QRadioButton( trUtf8( "Путь 2" ) ) );
    mainLayout->addStretch( 1 );

    registerField( "userName*", m_edit );
    m_errorLbl->setStyleSheet( "color: red; font-size: 8pt;");

    m_rbtnPath1->setChecked( true );

    setLayout( mainLayout );
}

IntroPage::~IntroPage() {
}

int IntroPage::nextId() const {
    if( m_rbtnPath1->isChecked() ) {
        return MIDDLE_PAGE_PATH_1;
    }

    return MIDDLE_PAGE_PATH_2;
}

bool IntroPage::validatePage() {
    bool ok = 2 <= m_edit->text().trimmed().size();

    m_errorLbl->setText( ok ? "" : trUtf8( "Имя должно состоять хотя бы из двух символов" ) );
    m_edit->setFocus();

    return ok;
}

Конструктор IntroPage занимается инициализацией структуры страницы. В нем мы определяем заголовок и подзаголовок с помощью setTitle() и setSubTitle(). Затем компонуем содержимое страницы, состоящее из поля ввода QLineEdit, текстовой метки QLabel и двух радио-кнопок QRadioButton.

Наиболее интересным местом конструктора является строка:

registerField( "userName*", m_edit );

Таким образом мы регистрируем поле ввода, содержимое которого доступно на любой странице Мастера под текстовым идентификатором userName. При этом оно объявляется обязательным полем с помощью символа звездочки * в конце имени userName*. Это означает, что пользователь не сможет перейти на следующую страницу Мастера, если не заполнит это поле ввода. Такое ограничение достигается за счет того, что кнопка Далее окажется неактивной при пустом значении имени пользователя.

Передать значение с одной страницы Мастера на другую можно и без привязки к текстовому полю. Для этого существует функция-член setField(). В качестве первого параметра она принимает текстовый идентификатор, а в качестве второго - произвольный объект, обернутый в QVariant.

Функция-член nextId() возвращает числовой идентификатор следующей страницы. Значение определяется в соответствии с выбором той или иной радио-кнопки.

Проверка обязательного поля на пустое значение хорошо. Если оно удовлетворено, то кнопка Далее уже становится доступной для нажатия. Но этого не всегда достаточно. Дополнительную проверку позволяет выполнить функция-член validatePage(). Для текстовых полей можно было бы использовать QValidator, но мы упрощаем задачу, и лишь проверяем, чтобы имя было не короче двух символов. В случае нарушения условия на форме выводится сообщение об ошибке. Это происходит после нажатия на кнопку Далее:

qwizard-demo-error

Если все условия выполнены, то после нажатия на кнопку Далее мы попадаем на промежуточную страницу. Реализуем ее.

Промежуточная страница

Заголовочный файл middlepage.h:

#ifndef MIDDLEPAGE_H
#define MIDDLEPAGE_H

#include <QWizardPage>

class QLabel;

class MiddlePage : public QWizardPage {
public:
    MiddlePage( const QString& subTitle, QWidget* parent = 0 );
    ~MiddlePage();

protected:
    void initializePage();
    int nextId() const;

private:
    QLabel* m_lbl;
};

#endif // MIDDLEPAGE_H

Заметим несколько деталей:

  1. Конструктор MiddlePage в качестве первого входного параметра принимает строку подзаголовка. Это позволит нам различать две страницы, на которые можно попасть с первой путем выбора той или иной радио-кнопки;
  2. Помимо знакомой нам уже виртуальной функции nextId() перегружается initializePage(). Она вызывается каждый раз, когда Мастер попадает на страницу после нажатия кнопки Далее (но не Назад) на предыдущей странице.

Переходим к реализации:

#include "middlepage.h"

#include "shared_defs.h"

#include <QBoxLayout>
#include <QLabel>
#include <QVariant>

MiddlePage::MiddlePage( const QString& subTitle, QWidget* parent ) : QWizardPage( parent ) {
    setTitle( trUtf8( "Промежуточная страница" ) );
    setSubTitle( subTitle );
    setCommitPage( true );

    QBoxLayout* l = new QVBoxLayout;
    l->addWidget( m_lbl = new QLabel );

    setLayout( l );
}

MiddlePage::~MiddlePage() {
}

void MiddlePage::initializePage() {
    m_lbl->setText( trUtf8( "Здравствуйте, <strong>%1</strong>." ).arg( field( "userName" ).toString() ) );
}

int MiddlePage::nextId() const {
    return FINAL_PAGE;
}

Обратим внимание на вызов setCommitPage( true ) в конструкторе MiddlePage. После него страница становится "контрольной". Кнопка Далее на ней меняется на кнопку Подтвердить. После нажатия на эту кнопку вернуться назад уже нельзя. В нашем примере это не так полезно, но знать о такой возможности не помешает.

Функция initializePage() довольно проста, но раскрывает одну из основных возможностей QWizard - передачу значений между страницами. Получить строку с именем пользователя, зарегистрированную через идентификатор userName можно с помощью field( "userName" ). Это мы и делаем, формируя приветственную надпись.

В качестве следующей страницы мы выбрали финальную. Поэтому nextId() возвращает ее числовой идентификатор FINAL_PAGE.

Финальная страница

Определим страницу FinalPage в заголовочном файле finalpage.h:

#ifndef FINALPAGE_H
#define FINALPAGE_H

#include <QWizardPage>

class QLabel;

class FinalPage : public QWizardPage {
public:
    FinalPage( QWidget* parent = 0 );
    ~FinalPage();

protected:
    void initializePage();

private:
    QLabel* m_lbl;
};

#endif // FINALPAGE_H

А теперь реализация в finalpage.cpp:

#include "finalpage.h"

#include <QBoxLayout>
#include <QLabel>
#include <QVariant>

FinalPage::FinalPage( QWidget* parent ) : QWizardPage( parent ) {
    setTitle( trUtf8( "Финальная страница" ) );
    setSubTitle( trUtf8( "Работа с программой почти окончена." ) );

    QBoxLayout* l = new QVBoxLayout;
    l->addWidget( m_lbl = new QLabel );

    setLayout( l );
}

FinalPage::~FinalPage() {
}

void FinalPage::initializePage() {
    m_lbl->setText( trUtf8( "До свидания, <strong>%1</strong>." ).arg( field( "userName" ).toString() ) );
}

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

Компонуем приложение

Главный файл реализации main.cpp:

#include <QApplication>

#include <QWizard>

#include "shared_defs.h"
#include "intropage.h"
#include "middlepage.h"
#include "finalpage.h"

int main( int argc, char* argv[] ) {
    QApplication a( argc, argv );

    QWizard wizard;
    wizard.setPage( INTRO_PAGE, new IntroPage );
    wizard.setPage( MIDDLE_PAGE_PATH_1, new MiddlePage( QObject::trUtf8( "Вы выбрали путь 1." ) ) );
    wizard.setPage( MIDDLE_PAGE_PATH_2, new MiddlePage( QObject::trUtf8( "Вы выбрали путь 2." ) ) );
    wizard.setPage( FINAL_PAGE, new FinalPage );
    wizard.show();

    return a.exec();
}

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

Поздравляю! Приложение на основе QWizard готово!

Замечание

На самом деле, мы рассмотрели только самые базовые принципы работы с классом Мастера в Qt. Но этого материала уже достаточно для создания полнофункциональных программ. Продвинутое использование QWizard откладываем на будущее.

Исходники

 Скачать исходники с примером создания Мастера в Qt на основе QWizard

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

Комментарии

Клас, всё чётко и ясно, спасибо за статью!

Пожалуйста :)

а как создать визард с головной программы?

daxart:

а как создать визард с головной программы?

Имеется в виду, что визард должен появляться в виде диалогового окна? Попробуйте посмотреть эту статью: http://itnotesblog.ru/note.php?id=230. При этом держите в уме, что QWizard наследует QDialog.