13.2.3.  Delegate Classes

[ fromfile: delegates.xml id: delegates ]

The delegate, shown in Figure 13.7, provides another level of indirection between the model and view, which increases the possibilities for customization.

Figure 13.7.  Model, View, and Delegate

Model, View, and Delegate

A delegate class, usually derived from QAbstractItemDelegate, adds several kinds of controller features to the Qt Model-View Framework. A delegate class can provide a factory method that enables view classes to create editors and virtual getters and setters for moving editor data to and from the model. It can also provide a virtual paint() method for custom display of items in the view. Delegates can be set up for an entire QAbstractItemView, or for just one of its columns.

Figure 13.8 shows a modified version of the StarDelegate example from $QTDIR/examples/modelview/stardelegate.

Figure 13.8.  Star Delegates

Star Delegates

In Example 13.5, we extended from QStyledItemDelegate. This is a concrete class that is used by default in Q(List|Table|Tree)Views. It provides a QLineEdit for editing QString properties, and other appropriate editors for the native style, when the type is a boolean, QDate, QTime, int, or double. The custom delegate shows the virtual methods one must override to get "stars" instead of a simple integer in the table for a custom StarRating value.

Example 13.5. src/modelview/playlists/stardelegate.h

#ifndef STARDELEGATE_H
#define STARDELEGATE_H

#include <QStyledItemDelegate>
#include <QStyleOptionViewItem>
class StarDelegate : public QStyledItemDelegate {
    Q_OBJECT
public:
    typedef QStyledItemDelegate SUPER;
    StarDelegate(QObject* parent=0) : SUPER(parent) {};
    QWidget* createEditor(QWidget* parent, 
                          const QStyleOptionViewItem& option,
                          const QModelIndex& index) const;
    void paint(QPainter* painter, 
               const QStyleOptionViewItem& option, 
               const QModelIndex& index) const;

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

#endif // STARDELEGATE_H


Delegates give full control over how the item in a view appears when displayed, via the paint() method, overridden in Example 13.6.

Example 13.6. src/modelview/playlists/stardelegate.cpp

[ . . . . ]

void StarDelegate::
    paint(QPainter* painter, 
          const QStyleOptionViewItem& option, 
          const QModelIndex& index) const {
    QString field = index.model()->headerData(index.column(),
                                           Qt::Horizontal).toString();
    if (field == "length") {
        QVariant var = index.data(Qt::DisplayRole);
        Q_ASSERT(var.canConvert(QVariant::Time));
        QTime time = var.toTime();
        QString str = time.toString("m:ss");
        painter->drawText(option.rect, str, QTextOption());
        // can't use drawDisplay with QStyledItemDelegate:
        // drawDisplay(painter, option, option.rect, str);
        return;
    }
    if (field != "rating") {
        SUPER::paint(painter, option, index);
        return;
    }
    QVariant variantData = index.data(Qt::DisplayRole);
    StarRating starRating = variantData.value<StarRating>();
    if (option.state & QStyle::State_Selected)
        painter->fillRect(option.rect, option.palette.highlight());
    starRating.paint(painter, option.rect, option.palette,
                     StarRating::ReadOnly);
}

In addition, delegates can determine what kind of widget shows up when the user triggers editing of an item. For this, you need to either override createEditor(), as shown in Example 13.7 or supply a custom QItemEditorFactory.

Example 13.7. src/modelview/playlists/stardelegate.cpp

[ . . . . ]

QWidget* StarDelegate::
    createEditor(QWidget* parent, 
                 const QStyleOptionViewItem& option,
                 const QModelIndex& index) const {
    QString field = index.model()->headerData(index.column(),
                                  Qt::Horizontal).toString();
    if (field == "rating")  {
            return new StarEditor(parent);
    }
    if (field == "length") {
        return new TimeDisplay(parent);
    }
    return SUPER::createEditor(parent, option, index);
}

[Important] What Is an Edit Trigger?

There are various ways to "trigger" the View into going into edit mode. On most desktop platforms, the F2 key is a "platform edit" key. On handhelds, it may be a gesture such as double-tap, or a special button. See the API docs for setEditTriggers().

When you trigger an edit request, you want to see an editor that initially has the value from the model for you to see or change. This is done by setEditorData(), as shown in Example 13.8.

Example 13.8. src/modelview/playlists/stardelegate.cpp

[ . . . . ]

void StarDelegate::
    setEditorData(QWidget* editor, 
                  const QModelIndex& index) const {
    QVariant val = index.data(Qt::EditRole);
    StarEditor* starEditor = qobject_cast<StarEditor*>(editor);     1
    if (starEditor != 0) {
        StarRating sr = qVariantValue<StarRating>(val);             2
        starEditor->setStarRating(sr);
        return;
    }
    TimeDisplay* timeDisplay = qobject_cast<TimeDisplay*>(editor);  3
    if (timeDisplay != 0) {
        QTime t = val.toTime();
        timeDisplay->setTime(t);
        return;
    }
    SUPER::setEditorData(editor, index);                            4
    return;
}

1

Dynamic type checking.

2

Extract user type value from QVariant.

3

Dynamic type checking.

4

Let base class handle other types.


When the user finishes editing, setModelData(), shown in Example 13.9, is called to put the data back into the QAbstractItemModel.

Example 13.9. src/modelview/playlists/stardelegate.cpp

[ . . . . ]

void StarDelegate::
    setModelData(QWidget* editor, QAbstractItemModel* model, 
                 const QModelIndex& index) const {
    StarEditor* starEditor = qobject_cast<StarEditor*>(editor);
    if (starEditor != 0) {
        StarRating r = starEditor->starRating();
        QVariant v;
        v.setValue<StarRating>(r);
        model->setData(index, v, Qt::EditRole);
        return;
    }
    TimeDisplay* td = qobject_cast<TimeDisplay*>(editor);
    if (td != 0) {
        QTime t = td->time();
        model->setData(index, QVariant(t));
        return;
    }
    SUPER::setModelData(editor, model, index);
    return;
}