本页内容

许可证向导示例

“许可证向导”示例展示了如何在 Qt 中实现复杂的向导。

许可证向导的首个页面

大多数向导具有线性结构,即第 1 页之后是第 2 页,依此类推,直至最后一个页面。示例“简单向导”展示了如何创建此类向导。

有些向导则更为复杂,它们允许根据用户提供的信息选择不同的浏览路径。“许可证向导”示例就说明了这一点。它提供了多个向导页面;根据所选选项的不同,用户可以跳转到不同的页面。

复杂向导页面的流程图

该示例由以下类组成:

  • LicenseWizard 继承自 `QWizard `,并实现了一个非线性的五页向导,引导用户完成选择许可协议的过程。
  • IntroPage、EvaluatePage 、RegisterPage 、DetailsPage 和ConclusionPage 是QWizardPage 的子类,用于实现向导页面。

LicenseWizard 类

LicenseWizard 类继承自QWizard ,提供了一个包含五个页面的向导,引导用户完成对某款虚构软件产品的注册流程。以下是该类的定义:

class LicenseWizard : public QWizard
{
    Q_OBJECT

public:
    enum { Page_Intro, Page_Evaluate, Page_Register, Page_Details,
           Page_Conclusion };

    LicenseWizard(QWidget *parent = nullptr);

private slots:
    void showHelp();
};

该类的公共 API 仅限于一个构造函数和一个枚举。该枚举定义了与各个页面关联的 ID:

类名枚举值页面 ID
IntroPagePage_Intro0
EvaluatePagePage_Evaluate1
RegisterPagePage_Register2
DetailsPagePage_Details3
ConclusionPagePage_Conclusion4

在此示例中,ID 是任意设置的。唯一的限制是它们必须是唯一的,且不能等于 -1。ID 使我们能够引用页面。

LicenseWizard::LicenseWizard(QWidget *parent)
    : QWizard(parent)
{
    setPage(Page_Intro, new IntroPage);
    setPage(Page_Evaluate, new EvaluatePage);
    setPage(Page_Register, new RegisterPage);
    setPage(Page_Details, new DetailsPage);
    setPage(Page_Conclusion, new ConclusionPage);

    setStartId(Page_Intro);

在构造函数中,我们创建了五个页面,使用QWizard::setPage()将其插入向导,并将Page_Intro 设为第一页。

#ifndef Q_OS_MACOS
    setWizardStyle(ModernStyle);
#endif

我们在除 macOS 以外的所有平台上将样式设置为 `ModernStyle `,

    setOption(HaveHelpButton, true);
    setPixmap(QWizard::LogoPixmap, QPixmap(":/images/logo.png"));

    connect(this, &QWizard::helpRequested, this, &LicenseWizard::showHelp);

    setWindowTitle(tr("License Wizard"));
}

我们将QWizard 配置为显示Help 按钮,该按钮与我们的showHelp() 槽相关联。此外,我们还为所有带有页眉的页面(即EvaluatePage 、RegisterPage 和DetailsPage )设置了LogoPixmap 。

void LicenseWizard::showHelp()
{
    static QString lastHelpMessage;

    QString message;

    switch (currentId()) {
    case Page_Intro:
        message = tr("The decision you make here will affect which page you "
                     "get to see next.");
        break;
    ...
    default:
        message = tr("This help is likely not to be of any help.");
    }

    if (lastHelpMessage == message)
        message = tr("Sorry, I already gave what help I could. "
                     "Maybe you should try asking a human?");

    QMessageBox::information(this, tr("License Wizard Help"), message);

    lastHelpMessage = message;
}

在showHelp() 中,我们会显示与当前页面相匹配的帮助文本。如果用户针对同一页面两次点击Help ,系统会提示:“抱歉,我已提供力所能及的帮助。也许您应该试着向真人寻求帮助?”

IntroPage 类

这些页面在licensewizard.h 中定义,并在licensewizard.cpp 以及LicenseWizard 中实现。

以下是IntroPage 的定义和实现:

class IntroPage : public QWizardPage
{
    Q_OBJECT

public:
    IntroPage(QWidget *parent = nullptr);

    int nextId() const override;

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

IntroPage::IntroPage(QWidget *parent)
    : QWizardPage(parent)
{
    setTitle(tr("Introduction"));
    setPixmap(QWizard::WatermarkPixmap, QPixmap(":/images/watermark.png"));

    topLabel = new QLabel(tr("This wizard will help you register your copy of "
                             "<i>Super Product One</i>&trade; or start "
                             "evaluating the product."));
    topLabel->setWordWrap(true);

    registerRadioButton = new QRadioButton(tr("&Register your copy"));
    evaluateRadioButton = new QRadioButton(tr("&Evaluate the product for 30 "
                                              "days"));
    registerRadioButton->setChecked(true);

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

页面继承自QWizardPage 。我们设置了title 和watermark pixmap 。通过不设置任何subTitle ,我们确保该页面不显示页眉。(在 Windows 系统中,向导通常会在第一页和最后一页显示水印位图,并在其他页面上显示页眉。)

int IntroPage::nextId() const
{
    if (evaluateRadioButton->isChecked()) {
        return LicenseWizard::Page_Evaluate;
    } else {
        return LicenseWizard::Page_Register;
    }
}

如果勾选了Evaluate the product for 30 days 选项,nextId() 函数将返回EvaluatePage 的ID;否则,它将返回RegisterPage 的ID。

EvaluatePage 类

EvaluatePage 的实现稍显复杂:

class EvaluatePage : public QWizardPage
{
    Q_OBJECT

public:
    EvaluatePage(QWidget *parent = nullptr);

    int nextId() const override;

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

EvaluatePage::EvaluatePage(QWidget *parent)
    : QWizardPage(parent)
{
    setTitle(tr("Evaluate <i>Super Product One</i>&trade;"));
    setSubTitle(tr("Please fill both fields. Make sure to provide a valid "
                   "email address (e.g., john.smith@example.com)."));

    nameLabel = new QLabel(tr("N&ame:"));
    nameLineEdit = new QLineEdit;
    ...
    registerField("evaluate.name*", nameLineEdit);
    registerField("evaluate.email*", emailLineEdit);
    ...
}

首先,我们设置页面的title 和subTitle 。

然后,我们创建子控件,创建与其关联的wizard fields ,并将它们放入布局中。字段的名称旁会带有星号(* ),这会使其成为必填字段(mandatory fields ),即用户必须先填写这些字段,才能点击“Next ”按钮(在 macOS 上为“Continue ”)。 可以通过QWizardPage::field() 从任何其他页面访问这些字段的值。

重置页面即清空这两个文本字段。

int EvaluatePage::nextId() const
{
    return LicenseWizard::Page_Conclusion;
}

下一页总是ConclusionPage 。

ConclusionPage 类

RegisterPage 和DetailsPage 与EvaluatePage 非常相似。让我们直接进入ConclusionPage :

class ConclusionPage : public QWizardPage
{
    Q_OBJECT

public:
    ConclusionPage(QWidget *parent = nullptr);

    void initializePage() override;
    int nextId() const override;
    void setVisible(bool visible) override;

private slots:
    void printButtonClicked();

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

这次,除了重新实现nextId() 之外,我们还重新实现了QWizardPage::initializePage() 和QWidget::setVisible()。此外,我们还声明了一个私有插槽:printButtonClicked() 。

int IntroPage::nextId() const
{
    if (evaluateRadioButton->isChecked()) {
        return LicenseWizard::Page_Evaluate;
    } else {
        return LicenseWizard::Page_Register;
    }
}

QWizardPage::nextId() 的默认实现会返回 ID 紧随其后的页面,若当前页面的 ID 已是最高值,则返回 -1。这种行为在此处本可正常工作,因为Page_Conclusion 等于 5,且不存在 ID 更高的页面;但为了避免依赖这种微妙的行为,我们重写了nextId() 使其返回 -1。

void ConclusionPage::initializePage()
{
    QString licenseText;

    if (wizard()->hasVisitedPage(LicenseWizard::Page_Evaluate)) {
        licenseText = tr("<u>Evaluation License Agreement:</u> "
                         "You can use this software for 30 days and make one "
                         "backup, but you are not allowed to distribute it.");
    } else if (wizard()->hasVisitedPage(LicenseWizard::Page_Details)) {
        const QString emailAddress = field("details.email").toString();
        licenseText = tr("<u>First-Time License Agreement:</u> "
                         "You can use this software subject to the license "
                         "you will receive by email sent to %1.").arg(emailAddress);
    } else {
        licenseText = tr("<u>Upgrade License Agreement:</u> "
                         "This software is licensed under the terms of your "
                         "current license.");
    }
    bottomLabel->setText(licenseText);
}

我们使用QWizard::hasVisitedPage() 来确定用户选择的许可协议类型。如果用户填写了EvaluatePage ,则许可文本为“评估版许可协议”;如果用户填写了DetailsPage ,则许可文本为“首次许可协议”;如果用户提供了升级密钥并跳过了DetailsPage ,则许可文本为“更新许可协议”。

void ConclusionPage::setVisible(bool visible)
{
    QWizardPage::setVisible(visible);

    if (visible) {
        wizard()->setButtonText(QWizard::CustomButton1, tr("&Print"));
        wizard()->setOption(QWizard::HaveCustomButton1, true);
        connect(wizard(), &QWizard::customButtonClicked,
                this, &ConclusionPage::printButtonClicked);
    } else {
        wizard()->setOption(QWizard::HaveCustomButton1, false);
        disconnect(wizard(), &QWizard::customButtonClicked,
                   this, &ConclusionPage::printButtonClicked);
    }
}

当ConclusionPage 页面打开时,我们希望在向导中显示一个Print 按钮。实现此功能的一种方法是重写QWidget::setVisible() 方法:

示例项目 @ code.qt.io

另请参阅 QWizard 和Trivial Wizard 示例。

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.