Home | All Classes | Main Classes | Annotated | Grouped Classes | Functions

[Previous: Handling Selections] [Contents] [Next: Convenience Classes]

Delegate Classes

Concepts

Unlike the Model-View-Controller pattern, the model/view design does not include a completely separate component for managing interaction with the user. Generally, the view is responsible for the presentation of model data to the user, and for processing user input. To allow some flexibility in the way this input is obtained, the interaction is performed by delegates. These components provide input capabilities and are also responsible for rendering individual items in some views. The standard interface for controlling delegates is defined in the QAbstractItemDelegate class.

Delegates are expected to be able to render their contents themselves by implementing the paint() and sizeHint() functions. However, simple widget-based delegates can subclass QItemDelegate instead of QAbstractItemDelegate, and take advantage of the default implementations of those functions. Editors for delegates can be implemented in two ways, either by using widgets to manage the editing process, or by handling events directly. The first approach is covered later in this section.

Using an Existing Delegate

The standard views provided with Qt use instances of QItemDelegate to provide editing facilities. This default implementation of the delegate interface renders items in the usual style for each of the standard views: QListView, QTableView, and QTreeView. The delegate being used for a view is returned by the itemDelegate() function. The setItemDelegate() function allows you to install a custom delegate for a standard view, and it is necessary to use this function when setting the delegate for a custom view.

A Simple Delegate

The delegate implemented here uses a QSpinBox to provide editing facilities, and is mainly intended for use with models that display integers. Although we set up a custom integer-based table model for this purpose, the way that data in the model is accessed would also allow a string-based model to be used with only minor inconvenience when editing. We also construct a table view so that we can manipulate the model's data.

We subclass the delegate from QItemDelegate because we do not want to write custom display functions. However, we must still provide functions to manage the editor widget:

    class SpinBoxDelegate : public QItemDelegate
    {
        Q_OBJECT

    public:
        SpinBoxDelegate(QObject *parent = 0);

        QWidget *editor(QWidget *parent, const QStyleOptionViewItem &option,
                        const QModelIndex &index);

        void releaseEditor(QWidget *editor, const QModelIndex &index);
        void setEditorData(QWidget *editor, const QModelIndex &index) const;
        void setModelData(QWidget *editor, QAbstractItemModel *model,
                          const QModelIndex &index) const;

        void updateEditorGeometry(QWidget *editor,
            const QStyleOptionViewItem &option, const QModelIndex &index) const;

    private:
        QSpinBox *spinBox;
    };

Note that no editor widgets are set up when the delegate is constructed. We only construct an editor widget when it is needed.

Providing an Editor

In this example, when the table view needs to provide an editor, it asks the delegate to provide an editor widget that is appropriate for the item being modified. The editor() function is supplied with everything that the delegate needs to be able to set up a suitable widget.

    QWidget *SpinBoxDelegate::editor(QWidget *parent,
        const QStyleOptionViewItem & /* option */,
        const QModelIndex & /* index */)
    {
        if (spinBox)
            emit closeEditor(spinBox);

        spinBox = new QSpinBox(parent);
        spinBox->setMinimum(0);
        spinBox->setMaximum(100);
        spinBox->installEventFilter(this);

        return spinBox;
    }

We provide basic editing features with a spin box, checking only that we are allowed to provide an editor. If the editor is not the expected spin box, we cancel the editor process. We install the delegate's default event filter on the editor to ensure that it provides the standard editing shortcuts that users expect. Additional shortcuts can be added to the editor to allow more sophisticated behavior; these are discussed in the section on Editing Hints.

The view ensures that we set up the editor's data and geometry correctly by calling functions that we define later for this purpose. We can create different editors depending on say, the model index, for example if we have a column of integers and a column of strings we could return a QSpinBox or a QLineEdit depending on which column the item to be edited is in.

The delegate must provide a function to copy model data into the editor. In this example, we read the data stored in the display role, and set the value in the spin box accordingly.

    void SpinBoxDelegate::setEditorData(QWidget *editor,
                                        const QModelIndex &index) const
    {
        if (editor != spinBox)
            return;

        int value = index.model()->data(index,
            QAbstractItemModel::DisplayRole).toInt();

        spinBox->setValue(value);
    }

In this example, we know that the editor widget is a spin box, but we could have provided different editors for different types of data in the model, in which case we'd need to determine the type used and cast accordingly.

Submitting Data to the Model

When the user has finished editing the value in the spin box, the view asks the delegate to store the edited value in the model by calling the setModelData() function. Delegates can inform the view that they want to write data to the model by emitting the commitData() signal.

    void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                       const QModelIndex &index) const
    {
        if (editor != spinBox)
            return;

        spinBox->interpretText();
        int value = spinBox->value();

        model->setData(index, value);
    }

Since we only create one editor widget at a time, we can easily check whether the editor supplied is the spin box that we created, and update the model. We ensure that the spin box is up-to-date, and update the model with its value using the index specified.

The standard QItemDelegate class informs the view when it has finished editing by emitting the closeEditor() signal. The view calls the delegate's releaseEditor() function to ensure that the editor widget is closed:

    void SpinBoxDelegate::releaseEditor(QWidget *editor, const QModelIndex &index)
    {
        if (editor == spinBox) {
            QItemDelegate::releaseEditor(editor, index);
            spinBox = 0;
        }
    }

In this function, we check that the supplied editor is the spin box widget before removing its event filter and cleaning up. A new editor widget will be created by the delegate the next time the view requires one.

All the operations on data are performed through the interface provided by QAbstractItemModel. This makes the delegate mostly independent from the type of data it manipulates, but some assumptions must be made in order to use certain types of editor widgets. In this example, we have assumed that the model always contains integer values, but we can still use this delegate with different kinds of models because QVariant provides sensible default values for unexpected data.

Updating the Editor's Geometry

It is the responsibility of the delegate to manage the editor's geometry. The geometry must be set when the editor is created, and when the item's size or position in the view is changed. Fortunately, the view provides all the necessary geometry information inside a view option object.

    void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
        const QStyleOptionViewItem &option, const QModelIndex & /* index */) const
    {
        editor->setGeometry(option.rect);
    }

In this case, we just use the geometry information provided by the view option in the item rectangle. A delegate that renders items with several elements would not use the item rectangle directly. It would position the editor in relation to the other elements in the item.

Editing Hints

After editing, delegates should provide hints to the other components about the result of the editing process, and provide hints that will assist any subsequent editing operations. This is achieved by providing a suitable hint with the closeEditor() signal, and this is taken care of by the default QItemDelegate event filter which we installed on the spin box when it was constructed.

The behavior of the spin box could be adjusted to make it more user friendly. In the default event filter supplied by QItemDelegate, if the user hits Return to confirm their choice in the spin box, the delegate commits the value to the model and closes the spin box. We can change this behavior by installing our own event filter on the spin box, and provide editing hints that suit our needs; for example, we might emit closeEditor() with the EditNextItem hint to automatically start editing the next item in the view.

Another approach that does not require the use of an event filter is to provide our own editor widget, perhaps subclassing QSpinBox for convenience. This alternative approach would give us more control over how the editor widget behaves at the cost of writing additional code. It is usually easier to install an event filter in the delegate if you need to customize the behavior of standard Qt editor widgets.

Delegates do not have to emit these hints, but those that do not will be less integrated into applications, and will be less usable than those that emit hints to support common editing actions.

Comments

The way the delegate provides editor widgets in this example is quite simple: they are constructed on demand, and deleted when no longer needed. A more sophisticated approach might be used to provide user-friendly editing features, such as a record of previously submitted values.

[Previous: Handling Selections] [Contents] [Next: Convenience Classes]


Copyright © 2004 Trolltech Trademarks
Qt 4.0.0-b1