9.7.  Designer Integration with Code

[ fromfile: designer-integration.xml id: designer-integration ]

Consider the ProductForm, a form for Product instances, shown in Figure 9.17.

Figure 9.17.  Product Form

Product Form


QFormLayout is a convenience widget that facilitates organizing QLabel and input widgets in a two-column format.

The ProductForm widget can accept data from the user for a new Product object. It can be used to display (read-only) or edit the values of a stored Product instance. The buttons can have text and roles that depend upon the usage mode. For example, in response to the user adding a new Product, clicking OK, should result in the values in the form being used to create a new Product instance. If instead the user clicks "Cancel", the values would be discarded. In edit mode, when the user clicks OK, the values in the form would replace the values in the already stored instance. In info mode, when the user clicks OK, the form should dismiss, and the Cancel button should be hidden.

While it is used, the ProductForm has a reference to the Product it is editing, as shown in Figure 9.18.

Figure 9.18.  Product and Its Form

Product and Its Form

Using Designer to Design the Form

The first step is to pick a base classname, (e.g., ProductForm), and write a header (ProductForm.h) and implementation file (ProductForm.cpp) for it, as usual. Next, create a form in Designer with the same base name (ProductForm.ui). Setting the objectName for the root object of this form (ProductForm) then enables uic to generate a header file for a corresponding Ui class (Ui_ProductForm).

Figure 9.19 shows how uic takes as input, the special XML file with a .ui extension produced by Designer, and generates a header file that you can #include in your code.

Figure 9.19.  Designer to Code

Designer to Code

Example 9.10 shows a small segment of the generated code. It defines both a Ui_ProductForm class and a Ui::ProductForm derived class. It is your choice which one to use in your C++ code. As you can see, there is a data member that points to each widget in the Designer form. The name of each member comes from the object name as set in the Designer form, giving you full control over member names in generated code.

Example 9.10. src/designer/productform/ui_ProductForm.h

[ . . . . ]
class Ui_ProductForm
{
public:
    QVBoxLayout *verticalLayout;
    QFormLayout *formLayout;
    QLabel *label;
    QDoubleSpinBox *priceSpinbox;
    QLabel *label_2;
    QLabel *label_3;
    QLineEdit *nameLineEdit;
    QLabel *label_4;
    QTextEdit *descriptionEdit;
    QDateEdit *dateEdit;
    QSpacerItem *verticalSpacer;
    QDialogButtonBox *buttonBox;

    void setupUi(QDialog *ProductForm)
[ . . . . ]
};
namespace Ui {
    class ProductForm: public Ui_ProductForm {};
} // namespace Ui
[ . . . . ]

<include src="src/designer/productform/ui_ProductForm.h" href="src/designer/productform/ui_ProductForm.h" id="ui-productform.h" mode="cpp"/>


[Important] What Is a Ui Class?

A Ui class is a class that contains only auto-generated code produced by the uic tool. It can be used as a base class, or as a data member of the custom form. Its members are initialized from the setupUi() method.

[Note] QDialog accept() and reject()

Designer initially connects a couple of signals from the QDialogButtonBox to the QDialog accept() and reject() slots. You can see this from the Signals and Slots Editor dockable. What this means is that as long as the underlying Product object is not changed until OK is pressed, the reject() base class behavior closes the dialog, as you probably want. You still might want to override accept(), but you do not need to connect any button's signal to it. The base class version also closes the dialog.

Approaches to Integration

Following are three approaches to integration of a Ui class with a custom QWidget-based form class.

  1. Aggregation as a pointer member

  2. Multiple (private) Inheritance

  3. Aggregation as an embedded object

Aggregation by pointer is the recommended (and default) approach because it has the advantage of making it possible to change the Ui file without causing binary breakage with the ProductForm header file. Example 9.11 shows aggregation by pointer member. ProductForm.h uses a forward class declaration instead of including the Ui_ProductForm.h directly.

Example 9.11. src/designer/delegation/productform.h

[ . . . . ]
#include <QDialog>
class Product;
class Ui_ProductForm;
class QWidget;
class QAbstractButton;
class ProductForm : public QDialog {
        Q_OBJECT
public:
    explicit ProductForm(Product* product = 0, QWidget* parent=0); 1
    void setModel(Product* model);

public slots:
    void accept();
    void commit();
    void update();

private:
    Ui_ProductForm *m_ui;
    Product* m_model;
};
[ . . . . ]

1

Mark explicit to avoid implicit conversions between pointers!

<include src="src/designer/delegation/productform.h" href="src/designer/delegation/productform.h" id="productform-delegation.h" mode="cpp"/>


Only the implementation file, shown in Example 9.12, depends on the uic-generated header file. This makes it easier to place this class into a library, for example.

Example 9.12. src/designer/delegation/productform.cpp

#include <QtGui>
#include "productform.h"
#include "ui_ProductForm.h"
#include "product.h"

ProductForm::ProductForm(Product* product, QWidget* parent)
: QDialog(parent), m_ui(new Ui::ProductForm),  m_model(product) {
    m_ui->setupUi(this); 1
    update();
}

void ProductForm::setModel(Product* p) {
    m_model =p;
}

void ProductForm::accept() {
    commit();
    QDialog::accept();  2
}

void ProductForm::commit() {
    if (m_model == 0) return;
    qDebug() << "commit()";
    m_model->setName(m_ui->nameLineEdit->text());
    QTextDocument* doc = m_ui->descriptionEdit->document();
    m_model->setDescription(doc->toPlainText());
    m_model->setDateAdded(m_ui->dateEdit->date());
    m_model->setPrice(m_ui->priceSpinbox->value());
}

void ProductForm::update() {
    if (m_model ==0) return;
    qDebug() << "update()";
    m_ui->nameLineEdit->setText(m_model->name());
    m_ui->priceSpinbox->setValue(m_model->price());
    m_ui->dateEdit->setDate(m_model->dateAdded());
    m_ui->descriptionEdit->setText(m_model->description());
}

1

Populate the UI object with valid instances with properties set from values in the .ui file.

2

Closes the dialog.

<include src="src/designer/delegation/productform.cpp" href="src/designer/delegation/productform.cpp" id="productform-delegation.cpp" mode="cpp"/>