Viadeo Twitter Google Bookmarks ! Facebook Digg del.icio.us MySpace Yahoo MyWeb Blinklist Netvouz Reddit Simpy StumbleUpon Bookmarks Windows Live Favorites 
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Classes principales  ·  Annotées  ·  Classes groupées  ·  Modules  ·  Fonctions  · 

Complex Wizard Example

Files:

The Complex Wizard example shows how to implement complex wizards in Qt.

A wizard is a special type of input dialog that consists of a sequence of dialog pages. A wizard's purpose is to walk the user through a process step by step. Wizards are useful for complex or infrequently occurring tasks that users may find difficult to learn or do.

Screenshot of the Complex Wizard example

Most wizards have a linear structure, with step 1 followed by step 2 and so on until the last step. The Simple Wizard example shows how to create such wizards.

Some wizards are more complex in that they allow different traversal paths based on the information provided by the user. The Complex Wizard example illustrates this. It provides five wizard pages; depending on which options are selected, the user can reach different pages.

The Complex Wizard Pages

The example consists of the following classes:

  • ComplexWizard is a base class that provides the basic framework for complex wizards. You can use it in your own projects for implementing custom wizards.
  • WizardPage is the base class for each wizard page. Like ComplexWizard, you can use it in your own projects.
  • LicenseWizard inherits ComplexWizard and provides a five-page wizard that leads the user through the process of choosing a license agreement.
  • LicenseWizardPage is a WizardPage subclass and is the base class for each of the license wizard pages.
  • TitlePage, EvaluatePage, RegisterPage, DetailsPage, and FinishPage are LicenseWizardPage subclasses that implement the wizard pages.

WizardPage Class

The WizardPage class is the base class for the wizard pages. It inherits from QWidget and provides a few functions that are called by ComplexWizard. Here's the class definition:

    class WizardPage : public QWidget
    {
        Q_OBJECT

    public:
        WizardPage(QWidget *parent = 0);

        virtual void resetPage();
        virtual WizardPage *nextPage();
        virtual bool isLastPage();
        virtual bool isComplete();

    signals:
        void completeStateChanged();
    };

Subclasses can reimplement the virtual functions to refine the behavior of the class:

  • resetPage() resets all the page's fields to their default values.
  • nextPage() returns a pointer to the next wizard page.
  • isLastPage() returns true if this page is the last page; otherwise it returns false.
  • isComplete() returns true if the user has filled the page with enough information and is allowed to go to the next page (or to click Finish if this is the last page); otherwise it returns false.

Subclasses are also expected to emit the completeStateChanged() signal whenever the isComplete() state changes.

WizardPage provides default implementations of the virtual functions:

    WizardPage::WizardPage(QWidget *parent)
        : QWidget(parent)
    {
        hide();
    }

    void WizardPage::resetPage()
    {
    }

    WizardPage *WizardPage::nextPage()
    {
        return 0;
    }

    bool WizardPage::isLastPage()
    {
        return false;
    }

    bool WizardPage::isComplete()
    {
        return true;
    }

ComplexWizard Class Definition

ComplexWizard offers a Cancel, a Back, a Next, and a Finish button. It takes care of enabling and disabling the buttons as the user advances through the pages. For example, if the user is viewing the first page, Back and Finish are disabled. It communicates with the pages through the four virtual functions declared in WizardPage.

Here's the class definition:

    class ComplexWizard : public QDialog
    {
        Q_OBJECT

    public:
        ComplexWizard(QWidget *parent = 0);

        QList<WizardPage *> historyPages() const { return history; }

    protected:
        void setFirstPage(WizardPage *page);

    private slots:
        void backButtonClicked();
        void nextButtonClicked();
        void completeStateChanged();

    private:
        void switchPage(WizardPage *oldPage);

        QList<WizardPage *> history;
        QPushButton *cancelButton;
        QPushButton *backButton;
        QPushButton *nextButton;
        QPushButton *finishButton;
        QHBoxLayout *buttonLayout;
        QVBoxLayout *mainLayout;
    };

The ComplexWizard class inherits QDialog. Subclasses must call setFirstPage() in their constructor to get things started.

The historyPages() function returns the traversal path taken so far by the user. This may be used by subclasses to find out which pages have been visited and which pages have been skipped. The current page is always last in the history. When the user presses Back, the previous page in the history list is retrieved so that the user can adjust their input when required.

ComplexWizard Class Implementation

First the constructor:

    ComplexWizard::ComplexWizard(QWidget *parent)
        : QDialog(parent)
    {
        cancelButton = new QPushButton(tr("Cancel"));
        backButton = new QPushButton(tr("< &Back"));
        nextButton = new QPushButton(tr("Next >"));
        finishButton = new QPushButton(tr("&Finish"));

        connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
        connect(backButton, SIGNAL(clicked()), this, SLOT(backButtonClicked()));
        connect(nextButton, SIGNAL(clicked()), this, SLOT(nextButtonClicked()));
        connect(finishButton, SIGNAL(clicked()), this, SLOT(accept()));

        buttonLayout = new QHBoxLayout;
        buttonLayout->addStretch(1);
        buttonLayout->addWidget(cancelButton);
        buttonLayout->addWidget(backButton);
        buttonLayout->addWidget(nextButton);
        buttonLayout->addWidget(finishButton);

        mainLayout = new QVBoxLayout;
        mainLayout->addLayout(buttonLayout);
        setLayout(mainLayout);
    }

We start by creating the Cancel, Back, Next, and Finish buttons and connecting their clicked() signals to SimpleWizard slots. The accept() and reject() slots are inherited from QDialog; they close the dialog and set the dialog's return code to QDialog::Accepted or QDialog::Rejected. The backButtonClicked() and nextButtonClicked() are defined in ComplexWizard.

    void ComplexWizard::setFirstPage(WizardPage *page)
    {
        page->resetPage();
        history.append(page);
        switchPage(0);
    }

The setFirstPage() function is called by subclasses with the page that should be shown first. We call resetPage() to make sure that its fields are correctly initialized, we append it to the history, and we call switchPage() to make the current page visible (the last page in the history). The switchPage() function takes a pointer to the previously current page as argument; here, we pass a null pointer.

    void ComplexWizard::backButtonClicked()
    {
        WizardPage *oldPage = history.takeLast();
        oldPage->resetPage();
        switchPage(oldPage);
    }

When the user clicks Back, we remove the current page from the history and clear all its fields. We call switchPage() to make the last page in the history visible. The argument to switchPage() is a pointer to the previously current page, not the new current page.

    void ComplexWizard::nextButtonClicked()
    {
        WizardPage *oldPage = history.last();
        WizardPage *newPage = oldPage->nextPage();
        newPage->resetPage();
        history.append(newPage);
        switchPage(oldPage);
    }

When the user clicks Next, we call nextPage() on the current page to obtain the next page. We initialize the next page's fields, we append it to the history, and we call switchPage() to make it visible.

    void ComplexWizard::completeStateChanged()
    {
        WizardPage *currentPage = history.last();
        if (currentPage->isLastPage())
            finishButton->setEnabled(currentPage->isComplete());
        else
            nextButton->setEnabled(currentPage->isComplete());
    }

The completeStateChanged() slot is called whenever the current page emits the completeStateChanged() signal. Depending on the value of isComplete() and isFinalPage(), we enable or disable the Next or the Finish button.

    void ComplexWizard::switchPage(WizardPage *oldPage)
    {
        if (oldPage) {
            oldPage->hide();
            mainLayout->removeWidget(oldPage);
            disconnect(oldPage, SIGNAL(completeStateChanged()),
                       this, SLOT(completeStateChanged()));
        }

The switchPage() function contains all the ugly logic for making a page the current page. We start by hiding the previously current page, remove it from the layout, and disconnect its completeStateChanged() signal.

Next, we add the new current page (the last page in the history) to the layout and we connect its completeStateChanged() to ComplexWizard's slot of the same name.

Then we must update the enabled state of the Back, Next, and Finish buttons. At the end, we call completeStateChanged() to reuse the logic we wrote earlier to enable or disable the Next or Finish buttons according to whether the current page is complete.

This completes the ComplexWizard class. The good news is that we can now use it to write any number of wizards, without having to worry about updating the state of the wizard buttons. The rest of the code implements a license wizard using ComplexWizard and WizardPage.

LicenseWizardPage Class

The LicenseWizardPage is a thin wrapper for WizardPage that provides a back pointer to the LicenseWizard:

    class LicenseWizardPage : public WizardPage
    {
    public:
        LicenseWizardPage(LicenseWizard *wizard)
            : WizardPage(wizard), wizard(wizard) {}

    protected:
        LicenseWizard *wizard;
    };

LicenseWizard Class

The LicenseWizard class derives from ComplexWizard and provides a five-page wizard that guides the user through the process of registering their copy of a fictitious software product. Here's the class definition:

    class LicenseWizard : public ComplexWizard
    {
    public:
        LicenseWizard(QWidget *parent = 0);

    private:
        TitlePage *titlePage;
        EvaluatePage *evaluatePage;
        RegisterPage *registerPage;
        DetailsPage *detailsPage;
        FinishPage *finishPage;

        friend class DetailsPage;
        friend class EvaluatePage;
        friend class FinishPage;
        friend class RegisterPage;
        friend class TitlePage;
    };

The class's public API is limited to a constructor. More interesting is the private API: It consists of a pointer to each page, and makes each page a friend of the class. This will make it possible for pages to refer to each other, as we will see shortly.

    LicenseWizard::LicenseWizard(QWidget *parent)
        : ComplexWizard(parent)
    {
        titlePage = new TitlePage(this);
        evaluatePage = new EvaluatePage(this);
        registerPage = new RegisterPage(this);
        detailsPage = new DetailsPage(this);
        finishPage = new FinishPage(this);

        setFirstPage(titlePage);

        setWindowTitle(tr("Complex Wizard"));
        resize(480, 200);
    }

In the constructor, we create the five pages and set titlePage to be the first page.

The Title-, Evaluate-, Register-, Details-, and FinishPage Classes

The pages are defined in licensewizard.h and implemented in licensewizard.cpp, together with LicenseWizard.

Here's the definition and implementation of TitlePage:

    class TitlePage : public LicenseWizardPage
    {
    public:
        TitlePage(LicenseWizard *wizard);

        void resetPage();
        WizardPage *nextPage();

    private:
        QLabel *topLabel;
        QRadioButton *registerRadioButton;
        QRadioButton *evaluateRadioButton;
    };

We reimplement the resetPage() and nextPage() functions from WizardPage. For the other two virtual functions (isLastPage() and isComplete()), the default implementations suffice ("not the last page, always complete").

    TitlePage::TitlePage(LicenseWizard *wizard)
        : LicenseWizardPage(wizard)
    {
        topLabel = new QLabel(tr("<center><font color=\"blue\" size=\"5\"><b><i>"
                                 "Super Product One</i></b></font></center>"));

        registerRadioButton = new QRadioButton(tr("&Register your copy"));
        evaluateRadioButton = new QRadioButton(tr("&Evaluate our product"));
        setFocusProxy(registerRadioButton);

        QVBoxLayout *layout = new QVBoxLayout;
        layout->addWidget(topLabel);
        layout->addSpacing(10);
        layout->addWidget(registerRadioButton);
        layout->addWidget(evaluateRadioButton);
        layout->addStretch(1);
        setLayout(layout);
    }

The TitlePage constructor takes a LicenseWizard and passes it on to LicenseWizardPage, which stores it in its wizard member.

The constructor sets up the page. The QWidget::setFocusProxy() call ensures that whenever ComplexWizard calls setFocus() on the page, the Register your copy button gets the focus.

    void TitlePage::resetPage()

Resetting the TitlePage consists in checking the Evaluate our product option.

    {
        registerRadioButton->setChecked(true);
    }

    WizardPage *TitlePage::nextPage()
    {
        if (evaluateRadioButton->isChecked())
            return wizard->evaluatePage;
        else
            return wizard->registerPage;
    }

The nextPage() function returns the EvaluatePage if the Evaluate our product option is checked; otherwise it returns the RegisterPage. We can access the wizard's pointers because we made all the wizard pages friends of the LicenseWizard class.

The EvaluatePage is slightly more involved:

    class EvaluatePage : public LicenseWizardPage
    {
    public:
        EvaluatePage(LicenseWizard *wizard);

        void resetPage();
        WizardPage *nextPage();
        bool isComplete();

    private:
        QLabel *topLabel;
        QLabel *nameLabel;
        QLabel *emailLabel;
        QLabel *bottomLabel;
        QLineEdit *nameLineEdit;
        QLineEdit *emailLineEdit;
    };

This time, we reimplement isComplete() in addition to resetPage() and nextPage(). Here's the constructor:

    EvaluatePage::EvaluatePage(LicenseWizard *wizard)
        : LicenseWizardPage(wizard)
    {
        topLabel = new QLabel(tr("<center><b>Evaluate Super Product One"
                                 "</b></center>"));

        nameLabel = new QLabel(tr("&Name:"));
        nameLineEdit = new QLineEdit;
        nameLabel->setBuddy(nameLineEdit);
        setFocusProxy(nameLineEdit);

        emailLabel = new QLabel(tr("&Email address:"));
        emailLineEdit = new QLineEdit;
        emailLabel->setBuddy(emailLineEdit);

        bottomLabel = new QLabel(tr("Please fill in both fields.\nThis will "
                                    "entitle you to a 30-day evaluation."));

        connect(nameLineEdit, SIGNAL(textChanged(QString)),
                this, SIGNAL(completeStateChanged()));
        connect(emailLineEdit, SIGNAL(textChanged(QString)),
                this, SIGNAL(completeStateChanged()));

        QGridLayout *layout = new QGridLayout;
        layout->addWidget(topLabel, 0, 0, 1, 2);
        layout->setRowMinimumHeight(1, 10);
        layout->addWidget(nameLabel, 2, 0);
        layout->addWidget(nameLineEdit, 2, 1);
        layout->addWidget(emailLabel, 3, 0);
        layout->addWidget(emailLineEdit, 3, 1);
        layout->setRowMinimumHeight(4, 10);
        layout->addWidget(bottomLabel, 5, 0, 1, 2);
        layout->setRowStretch(6, 1);
        setLayout(layout);
    }

The interesting part is the two QObject::connect() calls near the middle. Whenever the user types in some text in the Name or Email address fields, we emit the completeStateChanged() signal, even though the complete state might not actually have changed. It doesn't hurt anyway.

    void EvaluatePage::resetPage()
    {
        nameLineEdit->clear();
        emailLineEdit->clear();
    }

Resetting the page amounts to clearing the two text fields.

    WizardPage *EvaluatePage::nextPage()
    {
        return wizard->finishPage;
    }

The next page is always the FinishPage.

    bool EvaluatePage::isComplete()
    {
        return !nameLineEdit->text().isEmpty() && !emailLineEdit->text().isEmpty();
    }

The page is considered complete when both the Name and the Email address fields contain some text.

The RegisterPage and DetailsPage are very similar. Let's go directly to the FinishPage:

    class FinishPage : public LicenseWizardPage
    {
    public:
        FinishPage(LicenseWizard *wizard);

        void resetPage();
        bool isLastPage() { return true; }
        bool isComplete();

    private:
        QLabel *topLabel;
        QLabel *bottomLabel;
        QCheckBox *agreeCheckBox;
    };

This time, we reimplement isLastPage() to return true.

The resetPage() implementation is also interesting:

    void FinishPage::resetPage()
    {
        QString licenseText;

        if (wizard->historyPages().contains(wizard->evaluatePage)) {
            licenseText = tr("Evaluation License Agreement: "
                             "You can use this software for 30 days and make one "
                             "back up, but you are not allowed to distribute it.");
        } else if (wizard->historyPages().contains(wizard->detailsPage)) {
            licenseText = tr("First-Time License Agreement: "
                             "You can use this software subject to the license "
                             "you will receive by email.");
        } else {
            licenseText = tr("Upgrade License Agreement: "
                             "This software is licensed under the terms of your "
                             "current license.");
        }
        bottomLabel->setText(licenseText);
        agreeCheckBox->setChecked(false);
    }

We use historyPages() to determine the type of license agreement the user has chosen. If the user filled the EvaluatePage, the license text refers to an Evaluation License Agreement. If the user filled the DetailsPage, the license text is a First-Time License Agreement. If the user provided an upgrade key and skipped the DetailsPage, the license text is an Update License Agreement.

    bool FinishPage::isComplete()
    {
        return agreeCheckBox->isChecked();
    }

The FinishPage is complete when the user checks the I agree to the terms and conditions of the license option.

Publicité

Best Of

Actualités les plus lues

Semaine
Mois
Année
  1. « Quelque chose ne va vraiment pas avec les développeurs "modernes" », un développeur à "l'ancienne" critique la multiplication des bibliothèques 88
  2. Apercevoir la troisième dimension ou l'utilisation multithreadée d'OpenGL dans Qt, un article des Qt Quarterly traduit par Guillaume Belz 0
  3. Les développeurs ignorent-ils trop les failles découvertes dans leur code ? Prenez-vous en compte les remarques des autres ? 17
  4. Pourquoi les programmeurs sont-ils moins payés que les gestionnaires de programmes ? Manquent-ils de pouvoir de négociation ? 39
  5. Quelles nouveautés de C++11 Visual C++ doit-il rapidement intégrer ? Donnez-nous votre avis 10
  6. Adieu qmake, bienvenue qbs : Qt Building Suite, un outil déclaratif et extensible pour la compilation de projets Qt 17
  7. 2017 : un quinquennat pour une nouvelle version du C++ ? Possible, selon Herb Sutter 6
Page suivante

Le blog Digia au hasard

Logo

Créer des applications avec un style Metro avec Qt, exemples en QML et C++, un article de Digia Qt traduit par Thibaut Cuvelier

Le blog Digia est l'endroit privilégié pour la communication sur l'édition commerciale de Qt, où des réponses publiques sont apportées aux questions les plus posées au support. Lire l'article.

Communauté

Ressources

Liens utiles

Contact

  • Vous souhaitez rejoindre la rédaction ou proposer un tutoriel, une traduction, une question... ? Postez dans le forum Contribuez ou contactez-nous par MP ou par email (voir en bas de page).

Qt dans le magazine

Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.1
Copyright © 2012 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur ? Un bug ? Une redirection cassée ? Ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !
 
 
 
 
Partenaires

Hébergement Web