Reordering OK and Cancel

The various platforms supported by Qt have different guidelines for the order of buttons in dialogs. The most obvious issue is that the "OK" and "Cancel" buttons should be swapped on Mac OS X and GNOME, but there are several other differences as well. To ensure that dialogs created with Qt or Qt Designer look native on all window systems, Qt 4.2 introduces the QDialogButtonBox class that abstracts the button row usually located at the bottom or on the right side of dialogs.

The QDialogButtonBox Class

The QDialogButtonBox class is a QWidget subclass that you can populate with a set of buttons and insert in your dialog's layout. By default, QDialogButtonBox lays out its buttons horizontally, but this can be changed by passing Qt::Vertical to the constructor.

A horizontal button box A vertical button box

When adding buttons to a QDialogButtonBox, we also pass a "button role" that specifies how the button should be handled. For example:

    box->addButton(tr("OK"), QDialogButtonBox::AcceptRole);
    box->addButton(tr("Cancel"),QDialogButtonBox::RejectRole);
    box->addButton(tr("Apply"), QDialogButtonBox::ApplyRole);
    box->addButton(tr("Reset"), QDialogButtonBox::ResetRole);
    box->addButton(tr("Help"), QDialogButtonBox::HelpRole);

QDialogButtonBox uses the roles to order the buttons correctly with respect to the user's window system or desktop environment. The table below explains the available roles.

RoleDescriptionExamples
AcceptRoleAccepts the dialogOKOpenSave
RejectRoleRejects the dialogCancelClose
DestructiveRoleRisky way of closing the dialogDiscardDon't Save
ActionRolePerforms an action on the dialog without closing itFind NextMore Info
ResetRoleA "reset"-like actionResetRestore Defaults
ApplyRoleAn "apply"-like actionApplyTry
HelpRoleInvokes helpHelp
YesRolePositive answer to a yes/no questionYesYes to All
NoRoleNegative answer to a yes/no questionNoNo to All

As of Qt 4.2, the class supports four "layout styles": Windows, Mac OS X, KDE, and GNOME. The buttons are ordered according to the current layout style (specified by the active QStyle) and to their roles. If there are several buttons with the same role, the relative order in which they were inserted is preserved in the final dialog. Thus, on Windows, the buttons are organized as follows:

Windows button layout

(In Windows and Mac OS X styles, YesRole and NoRole are treated the same as AcceptRole and RejectRole, respectively.) On Mac OS X, the order is normally as follows:

Mac OS X button layout

Notice how the Help button is pushed to the far left, and how the dialog's accept button (e.g., OK) is pushed to the right. What the diagram doesn't show is that if there is more than one accept button, the extra ones are put to the left of the reject button, as specified in Apple Human Interface Guidelines.

Some buttons, such as OK, Cancel, and Help, occur over and over again in dialogs. For these, QDialogButtonBox provides convenience enum values that can be used instead of specifying a text and a role. For example:

    box->addButton(QDialogButtonBox::Ok);
    box->addButton(QDialogButtonBox::Cancel);
    box->addButton(QDialogButtonBox::Apply);
    box->addButton(QDialogButtonBox::Reset);
    box->addButton(QDialogButtonBox::Help);

An alternative to calling addButton() for every button is to set the standardButtons property (which also shows up in Qt Designer):

    box->setStandardButtons(QDialogButtonBox::Ok
                            | QDialogButtonBox::Cancel
                            | QDialogButtonBox::Apply
                            | QDialogButtonBox::Reset
                            | QDialogButtonBox::Help);

An Extended Message Box

In Qt 4.2, the QMessageBox class has been rewritten to use QDialogButtonBox to display its buttons. The API has also been revised so that it supports the same standard buttons and button roles as QDialogButtonBox. As a result, QMessageBox's buttons are now ordered correctly on the different platforms.

Messagebox-Win Messagebox-Mac

The new QMessageBox also features a more native look on Mac OS X, a more flexible API that caters for message boxes with more than three buttons, and more sensible word-wrapping behavior.

Example: A Mail Editor

In this section, we will review the source code of a MailEditor dialog class that uses QDialogButtonBox and QMessageBox. The screenshots below show the MailEditor dialog on Windows XP and Mac OS X.

Mail-Win

Mail-Mac

Let's start with the constructor:

    MailEditor::MailEditor(QWidget *parent)
        : QDialog(parent)
    {
        ...
    
        sendNowButton = new QPushButton(tr("Send Now"));
        ...
        saveDraftButton = new QPushButton(tr("Save Draft"));
        sendNowButton->setDefault(true);
    
         buttonBox = new QDialogButtonBox;
         buttonBox->addButton(sendNowButton,
                              QDialogButtonBox::AcceptRole);
         buttonBox->addButton(discardDraftButton,
                              QDialogButtonBox::DestructiveRole);
         buttonBox->addButton(sendLaterButton,
                              QDialogButtonBox::RejectRole);
         buttonBox->addButton(saveDraftButton,
                              QDialogButtonBox::ActionRole);
     
        QGridLayout *layout = new QGridLayout;
        ...
        layout->addWidget(buttonBox, 3, 0, 1, 2);
        setLayout(layout);
    }

We instantiate the QPushButtons as usual, but instead of inserting them in a QHBoxLayout, we put them in a QDialogButtonBox. The table below lists the buttons, the roles we chose for them, and the reasons for that choice.

ButtonRoleRationale
Send NowAcceptRoleThis button is the standard way of closing the dialog.
Discard DraftDestructiveRoleThis button closes the dialog and may result in data loss.
Send LaterRejectRoleThis button closes the dialog but does not send the email. It is the equivalent of pressing Esc.
Save DraftActionRoleThis button doesn't close the dialog.

In the sendNow() slot, we call QDialog::accept() to close the dialog with an "accepted" result:

    void MailEditor::sendNow()
    {
        ...
        accept();
    }

In discardDraft() and sendLater(), we call QDialog::reject() to close the dialog with a "rejected" result:

    void MailEditor::discardDraft()
    {
        ...
        reject();
    }
    
    void MailEditor::sendLater()
    {
        saveDraft();
        reject();
    }

The dialog's result value is accessible through QDialog::result(). Unlike the other three slots, the saveDraft() slot doesn't close the dialog:

    void MailEditor::saveDraft()
    {
        ...
        textEdit->document()->setModified(false);
    }

If the user closes the dialog while there are unsaved changes, we show a message box:

    void MailEditor::closeEvent(QCloseEvent *event)
    {
        if (textEdit->document()->isModified()) {
            int r = QMessageBox::warning(this,
                        tr("Mail Editor"),
                        tr("Do you want to save the draft "
                           "before closing?"),
                         QMessageBox::Save
                         | QMessageBox::DontSave
                         | QMessageBox::Cancel);
            if (r == QMessageBox::Save) {
                saveDraft();
                event->accept();
            } else if (r == QMessageBox::DontSave) {
                event->accept();
            } else {
                event->ignore();
            }
        }
    }

Notice that we give meaningful labels to the message box's buttons, namely Save, Don't Save, and Cancel. For the user, this is much less error-prone than the traditional Yes and No choice.


This document is licensed under the Creative Commons Attribution-Share Alike 2.5 license.

Copyright © 2006 Trolltech Trademarks