Home · Examples 


Star Delegate Example

Code:

The Star Delegate example shows how to create a delegate that can paint itself and that supports edition.

When displaying data in a QListView, QTableView, or QTreeView, the individual items are drawn by a delegate. Also, when the user starts editing an item (e.g., by double-clicking the item), the delegate provides an editor widget that is placed on top of the item while editing takes place.

Delegates are subclasses of QAbstractItemDelegate. Qt Jambi provides QItemDelegate, which inherits QAbstractItemDelegate and handles the most common data types (notably Integer and String). If we need to support custom data types, or want to customize the rendering or the editing for existing data types, we can subclass QAbstractItemDelegate or QItemDelegate. See Delegate Classes for more information about delegates, and Model/View Programming if you need a high-level introduction to Qt Jambi's model/view architecture (including delegates).

In this example, we will see how to implement a custom delegate to render and edit a "star rating" data type, which can stores values such as "1 out of 5 stars".

The example consists of the following classes:

StarDelegate Class Implementation

The StarDelegate consists of public methods reimplemented from
QItemDelegate to provide custom rendering and editing.

We provide the class with a constructor that takes a QWidget, which we use for instantiating the superclass. The native Qt code demands that all objects that inherit QObject has a parent; otherwise the program will fail.

The paint() method is reimplemented from QItemDelegate and is called whenever the view needs to repaint an item:

        public void paint(QPainter painter, QStyleOptionViewItem option, QModelIndex index)
        {
            Object data = index.data();

            if (data != null && data instanceof StarRating) {
                if (option.state().isSet(QStyle.StateFlag.State_Selected)) {
                    painter.fillRect(option.rect(), option.palette().highlight());
                }
                ((StarRating) data).paint(painter, option.rect(), option.palette(),
                                          StarRating.ReadOnly);
            } else
                super.paint(painter, option, index);
        }
The method is invoked once for each item, represented by a QModelIndex object from the model. If the data stored in the item is a StarRating, we paint it ourselves; otherwise, we let QItemDelegate paint it for us. This ensures that the StarDelegate can handle the most common data types.

In the case where the item is a StarRating, we draw the background if the item is selected, and we draw the item using StarRating.paint(), which we will review later.

Any kind of Object can be stored in a model, but when the item delegate encounters items it does not know how to paint, it will leave the view for that item empty.

The createEditor() method is called when the user starts editing an item:

        public QWidget createEditor(QWidget parent, QStyleOptionViewItem item,
                                    QModelIndex index)
        {
            Object data = index.data();

            if (data instanceof StarRating)
                return new StarEditor(parent, (StarRating) data);
            else
                return super.createEditor(parent, item, index);
        }
If the item is a StarRating, we create a StarEditor. The editor must have the parent; if not, it would be displayed as a top-level window.

The setEditorData() method is called when an editor is created to initialize it with data from the model:

        public void setEditorData(QWidget editor, QModelIndex index)
        {
            Object data = index.data();

            if (data instanceof StarRating)
                ((StarEditor) editor).setStarRating((StarRating) data);
            else
                super.setEditorData(editor, index);
        }
We simply call setStarRating() on the editor.

The setModelData() method is called when editing is finished, to commit data from the editor to the model:

        public void setModelData(QWidget editor, QAbstractItemModel model,
                                 QModelIndex index)
        {
            if (index.data() instanceof StarRating)
                model.setData(index, ((StarEditor) editor).starRating());
            else
                super.setModelData(editor, model, index);
        }
The editing is finished when the user clicks on the editor. Since we propagate the mouse click event (i.e., do not accept it), the view will close the editor for us when it receives the QMouseEvent.

The sizeHint() method returns an item's preferred size:

        public QSize sizeHint(QStyleOptionViewItem option, QModelIndex index)
        {
            Object data = index.data();

            if (data instanceof StarRating)
                return ((StarRating) data).sizeHint();
            else
                return super.sizeHint(option, index);
        }
We simply forward the call to StarRating.

StarEditor Class Implementation

The StarEditor is used by StarDelegate to edit items. The user edits a StarRating by moving the mouse over the editor. When the editing is finished the value of the star rating can be retrieved with getRating().

The protected methods are reimplemented from QWidget to handle mouse and paint events. The private method starAtPosition() is a helper method that returns the number of the star under the mouse pointer.

Let's start with the constructor:

        public StarEditor(QWidget parent, StarRating rating)
        {
            super(parent);

            starRating = rating;
            setMouseTracking(true);
            setAutoFillBackground(true);
        }
We enable mouse tracking on the widget so we can follow the cursor even when the user doesn't hold down any mouse button. We also turn on QWidget's auto-fill background feature to obtain an opaque background. (Without the call, the view's background would shine through the editor.)

The paintEvent() method is reimplemented from QWidget:

        public void paintEvent(QPaintEvent event)
        {
            QPainter painter = new QPainter(this);
            starRating.paint(painter, rect(), palette(), StarRating.ReadWrite);
        }
We simply call StarRating.paint() to draw the stars, just like we did when implementing StarDelegate.
        public void mouseMoveEvent(QMouseEvent event)
        {
            int star = starAtPosition(event.x());

            if (star != starRating.getRating() && star > 0) {
                starRating.setRating(star);
                update();
            }
        }
In the mouse event handler, we call setRating() on the private data member starCount to reflect the current cursor position, and we call QWidget.update() to force a repaint.
        public int starAtPosition(int x)
        {
            int star = (x / (starRating.sizeHint().width()
                            / starRating.getMaxRating())) + 1;

            if (star <= 0 || star > starRating.getMaxRating())
                return -1;

            return star;
        }
The starAtPosition() method uses basic linear algebra to find out which star is under the cursor.

StarRating Class Implementation

The StarRating class represents a rating as a number of stars. In addition to holding the data, it is also capable of painting the stars on a
QPaintDevice, which in this example is either a view or an editor. The starCount member variable stores the current rating, and maxStarCount stores the highest possible rating (typically 5). The polygons used for drawing stars and diamonds are set up in a static block.

The constructor initializes starCount and maxStarCount:

        public StarRating(int rating, int maxRating)
        {
            setupPolygons();
            maxCount = maxRating;
            setRating(rating);
        }
The paint() method paints the stars in this StarRating object on a paint device:
        public void paint(QPainter painter, QRect rect, QPalette palette,
                      int mode)
        {
            painter.save();

            painter.setRenderHint(QPainter.RenderHint.Antialiasing, true);
            painter.setPen(Qt.PenStyle.NoPen);

            if (mode == ReadWrite)
                painter.setBrush(palette.highlight());
            else
                painter.setBrush(palette.text());

            int yOffset = (rect.height() - PaintingFactor) / 2;
            painter.translate(rect.x(), rect.y() + yOffset);
            painter.scale(PaintingFactor, PaintingFactor);

            for (int i = 0; i < maxCount; i++) {
                if (i < starCount)
                    painter.drawPolygon(starPolygon, Qt.FillRule.WindingFill);
                else
                    painter.drawPolygon(diamondPolygon, Qt.FillRule.WindingFill);

                painter.translate(1.0, 0.0);
            }

            painter.restore();
        }
We first set the pen and brush we will use for painting. The mode parameter can be either ReadWrite or ReadOnly. If mode is read and write, we use the Highlight color instead of the Foreground color to draw the stars.

Then we draw the stars. If we are in ReadWrite mode, we paint diamonds in place of stars if the rating is less than the highest rating.

The sizeHint() method returns the preferred size for an area to paint the stars on:

        public QSize sizeHint(QStyleOptionViewItem option, QModelIndex index)
        {
            Object data = index.data();

            if (data instanceof StarRating)
                return ((StarRating) data).sizeHint();
            else
                return super.sizeHint(option, index);
        }
The preferred size is just enough to paint the maximum number of stars. The method is called by both StarDelegate.sizeHint() and StarEditor.sizeHint().

StarWindow Class Implementation

StarWindow inherits
QWidget and displays a table that has a star delegate installed.

The table is set up in the createTable() method:

    public void createTable()
    {
        LinkedList<String> headers = new LinkedList<String>();

        table = new QTableWidget(4, 4);

        table.setItemDelegate(new Delegate(table));

        table.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked,
                              QAbstractItemView.EditTrigger.SelectedClicked);
        table.setSelectionBehavior(
            QAbstractItemView.SelectionBehavior.SelectRows);
The createTable() method creates a QTableWidget and sets a StarDelegate on it. We set...
    }
The rest of the method fills the table with data including star ratings. DoubleClicked and SelectedClicked are set as edit triggers, so that the editor is opened by a single click when the star rating item is selected.

Possible Extensions and Suggestions

There are many ways to customize Qt Jambi's
model/view framework. The approach used in this example is appropriate for most custom delegates and editors. Examples of possibilities not used by the star delegate and star editor are:


Copyright © 2009 Nokia Corporation and/or its subsidiary(-ies) Trademarks
Qt Jambi 4.5.2_01