许可向导示例

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

Screenshot of the License Wizard example

大多数向导都有线性结构,页面1后面是页面2,依此类推,直到最后一页。《简单向导》示例展示了如何创建这样的向导。

有些向导更复杂,因为它们允许用户根据提供的信息选择不同的遍历路径。许可向导示例说明了这一点。它提供了五个向导页面;根据所选选项,用户可以到达不同的页面。

The License Wizard pages

该示例包含以下类

  • LicenseWizard 继承自 QWizard 并实现了一个非线性五页向导,它引导用户通过选择许可协议的过程。
  • IntroPageEvaluatePageRegisterPageDetailsPageConclusionPageQWizardPage 子类,它们实现了向导页面。

许可向导类

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 限于构造函数和一个枚举。枚举定义了与各个页面相关联的 IDs

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

对于此示例,ID 是任意的。唯一的约束是它们必须是唯一的,并且与 -1 不同。IDs 允许我们引用页面。

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_MAC
    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 以显示一个 帮助 按钮,该按钮连接到我们的 showHelp() 槽。我们还为具有标题的页面设置了 LogoPixmap(即 EvaluatePageRegisterPageDetailsPage)。

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() 中,我们显示适用于当前页面的帮助文本。如果用户在同一个页面上连续两次单击 帮助,我们说:“对不起,我已经给出了我能给出的所有帮助。也许你应该尝试向人类提问?”

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。我们设置了一个 标题 和一个 水印Pixmap。因为没有设置任何 副标题,所以我们确保在此页面上不显示页眉。(在Windows中,通常在第一页和最后一页显示水印Pixmap,在其他页面上显示页眉。)

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., [email protected])."));

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

首先,我们设置页面的 标题副标题

然后我们创建子小部件,为它们创建相关联的 向导字段,并将它们放入布局中。字段名称旁边有一个星号(*)。这使得它们成为 必填字段,即必须在用户按下 下一步 按钮(在macOS上为 继续)之前填写。可以通过 QWizardPage::field() 从任何其他页面访问字段的值。

重置页面相当于清除两个文本字段。

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

下一页始终是 ConclusionPage

ConclusionPage 类

RegisterPageDetailsPageEvaluatePage 非常相似。让我们直接去看 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;
};

这次,我们除了重写 QWizardPage::initializePage() 和 QWidget::setVisible(),还重写了 nextId()。我们还声明了一个私有槽: 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 出现时,我们想在向导中显示一个 打印 按钮。实现这一目标的一种方法是通过重写 QWidget::setVisible()

项目示例 @ code.qt.io

也请参阅QWizard简单向导示例

© 2024 Qt 公司有限公司。本文件中包含的文档贡献的版权归其各自的所有者所有。本文件中提供的文档受 GNU自由文档许可1.3版 的条款约束,该许可由自由软件基金会发布。Qt及其相关标志是芬兰和/或其他国家的Qt公司在全球的商标。所有其他商标都是其所有者的财产。