Back in Form with QFormLayout

by Jasmin Blanchette

Qt 4.4 introduces a new layout manager class, QFormLayout, that can be used to create two-column forms with labels on the left and editors on the right. For such forms, QFormLayout provides several advantages over QGridLayout, including platform-awareness and a more relevant API. In this article, we will see how it works.

Getting Started

QFormLayout started its life as a Qtopia class, where its raison d'être was to support row wrapping to accommodate the small displays of most mobile devices. Nonetheless, the need for a two-column form layout was felt on the desktop as well, where different platforms specify a somewhat different look and feel---compare the typical Windows dialog with the equally typical Mac OS X dialog alongside it:

A dialog in Windows

A dialog in Mac OS X

To us developers, QFormLayout is good news for another reason. Have you ever found yourself writing code like the following?

    nameLabel = new QLabel(tr("&Name:"));
    nameLabel->setBuddy(nameLineEdit);
 
    emailLabel = new QLabel(tr("&Name:"));
    emailLabel->setBuddy(emailLineEdit);
 
    ageLabel = new QLabel(tr("&Name:"));
    ageLabel->setBuddy(ageSpinBox);
 
    QGridLayout *gridLayout = new QGridLayout;
    gridLayout->addWidget(nameLabel, 0, 0);
    gridLayout->addWidget(nameLineEdit, 0, 1);
    gridLayout->addWidget(emailLabel, 1, 0);
    gridLayout->addWidget(emailLineEdit, 1, 1);
    gridLayout->addWidget(ageLabel, 2, 0);
    gridLayout->addWidget(ageSpinBox, 2, 1);
    setLayout(gridLayout);

Instead of the 14 code lines above (plus 3 blank lines), you can now write the 5 lines below:

    QFormLayout *formLayout = new QFormLayout;
    formLayout->addRow(tr("&Name:"), nameLineEdit);
    formLayout->addRow(tr("&Email:"), emailLineEdit);
    formLayout->addRow(tr("&Age:"), ageSpinBox);
    setLayout(formLayout);

In the QFormLayout version, each call to addRow() creates a QLabel behind the scenes and sets its buddy.

Conceptually, a QFormLayout consists of rows. Rows are created using addRow() or insertRow(). A row consists of a label and a field, both of which can be arbitrary QWidgets. If a QString is passed for the label, a QLabel is created, as we saw in the preceding example. It is also possible to add items that span both columns by calling addRow(QWidget~*).

QFormLayout provides the following properties that can be used to customize its look and feel:

  • Label alignment: Whether the labels in the left column are left or right aligned. Windows and GNOME specify left alignment for labels, whereas Mac OS X and KDE specify right alignment.
  • Row wrap policy: QFormLayout supports three row-wrapping policies:~wrap long rows, wrap all rows, and don't wrap rows. The "don't wrap rows" policy is the default on all desktop platforms, where screen space is abundant.
  • Field growth policy: On Windows, the field widgets typically expand to take all the space available, whereas on Mac OS X, they tend to stay at a fixed size.
  • Form alignment: On Mac OS X, the form should be horizontally centered in the window if there is extra space available.
For all of these, QFormLayout queries the QStyle to obtain default values but lets you override them if you need to.

Example: A Bug Report Form

We will now look at a complete example that uses QFormLayout to lay out its widgets. The dialog is a bug report form shown below on four different platforms:

The dialog in Windows XP

The dialog in Max OS X

The dialog in KDE

The dialog in GNOME

Here is the class definition:

    class BugForm : public QDialog
    {
        Q_OBJECT
 
    public:
        BugForm(QWidget *parent = 0);
 
    private slots:
        void reset();
 
    private:
        QLineEdit *nameEdit;
        QLineEdit *companyEdit;
        QLineEdit *phoneEdit;
        QLineEdit *emailEdit;
        QLineEdit *problemEdit;
        QTextEdit *summaryEdit;
        QComboBox *reproducibilityCombo;
        QDialogButtonBox *buttonBox;
    };

So far, nothing special. Here's the first half of the constructor:

    BugForm::BugForm(QWidget *parent)
        : QDialog(parent)
    {
        nameEdit = new QLineEdit;
        companyEdit = new QLineEdit;
        phoneEdit = new QLineEdit;
        emailEdit = new QLineEdit;
        problemEdit = new QLineEdit;
        summaryEdit = new QTextEdit;
 
        reproducibilityCombo = new QComboBox;
        reproducibilityCombo->addItem(tr("Always"));
        reproducibilityCombo->addItem(tr("Sometimes"));
        reproducibilityCombo->addItem(tr("Rarely"));
 
        buttonBox = new QDialogButtonBox;
        buttonBox->addButton(tr("Submit Bug Report"),
                             QDialogButtonBox::AcceptRole);
        buttonBox->addButton(tr("Don't Submit"),
                             QDialogButtonBox::RejectRole);
        buttonBox->addButton(QDialogButtonBox::Reset);
 
        connect(buttonBox, SIGNAL(accepted()),
                this, SLOT(accept()));
        connect(buttonBox, SIGNAL(rejected()),
                this, SLOT(reject()));
        connect(buttonBox->button(QDialogButtonBox::Reset),
                SIGNAL(clicked()), this, SLOT(reset()));

We create some editor widgets for the form, as well as a QDialogButtonBox with three buttons.

      QFormLayout *layout = new QFormLayout;
        layout->addRow(tr("&Name:"), nameEdit);
        layout->addRow(tr("&Company:"), companyEdit);
        layout->addRow(tr("&Phone:"), phoneEdit);
        layout->addRow(tr("&Email:"), emailEdit);
        layout->addRow(tr("Problem &Title:"), problemEdit);
        layout->addRow(tr("&Summary Information:"),
                       summaryEdit);
        layout->addRow(tr("&Reproducibility:"),
                       reproducibilityCombo);
 
        QVBoxLayout *mainLayout = new QVBoxLayout;
        mainLayout->addLayout(layout);
        mainLayout->addWidget(buttonBox);
        setLayout(mainLayout);
 
        setWindowTitle(tr("Report Bug"));
    }

We create a QFormLayout and populate it with rows, one row per editor widget. Then we insert the QFormLayout in a QVBoxLayout that also contains the QDialogButtonBox.

Finally, the Reset button is connected to the reset() slot, which resets all the editors.

The bug report form presented above can also be developed in Qt Designer, by dragging a QFormLayout onto the form and then the child widgets into it, much like we would do for a QGridLayout.

The form layout shown in Qt Designer

If you want to port existing .ui files to QFormLayout, you can simply edit the .ui file and replace QGridLayout with QFormLayout, since both have the same serialization format.

Conclusion

Qt is good at providing the fundamental mechanisms necessary to develop powerful cross-platform applications. What we see now is that it is also gradually expanding to provide higher-level APIs that encapsulate platform -specific idioms. An example of this is QDialogButtonBox, which was introduced in Qt 4.2 as a higher-level abstraction for a QHBoxLayout containing buttons, taking care of ordering them according to the platform's guidelines. QFormLayout follows in this tradition, and you can expect more classes like it in future Qt releases.

Обсудить на форуме...