Delegate 类
概念 与MVC模式不同,model/view结构没有用于与用户交互的完全独立的组件。一般来讲, view负责把数据展示给用户,也处理用户的输入。为了获得更多的灵性性,交互通过delegagte执行。它既提供输入功能又负责渲染view中的每个数据项。
使用Delegate的原因 Qt中当用到QTreeView和QTableView等用于显示item的视图时,你要编辑一个item用到的编辑工具可能是除了默认文字编辑lineEdit以外的工具,例如button,spinBox,甚至Slider,ProgressBar,也有可能是自定义的widget。所以Qt提供了一个委托类,用来处理View中的数据展示方式。
Delegate类的继承架构见下图,
自从Qt4.4,出现了两个delegate基类,QStyledItemDelegate vs. QItemDelegate。默认的delegate是QStyledItemDelegate,即你不自己写delegate的时候,默认那个lineEdit是来自QStyledItemDelegate。Qt Assistant建议用户如果自定义delegate或者用到了Qt style sheets的话,最好继承自QStyledItemDelegate,为什么呢?首先这两个类在绘制代理和为item提供编辑器上面是独立的,没什么联系,互不影响;不同的是QStyledItemDelegate使用当前style来绘制item(的代理),即如果程序设置了总体的风格(用QSS或其他定义方式),QStyledItemDelegate会使用这个风格设置。
先看看Qt Demos看了里面spinboxDelegat的例子:
1. 自定义的delegate继承自QItemDelegate。
2. 必须重载的一些函数:
(1) QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,const QModelIndex &index) const;
(2) void setEditorData(QWidget *editor, const QModelIndex &index) const;
(3) void setModelData(QWidget *editor, QAbstractItemModel *model,const QModelIndex &index) const;
(4) void updateEditorGeometry(QWidget *editor,const QStyleOptionViewItem &option, const QModelIndex &index) const;
3. createEditor创建自定义widget并返回之。
setEditorData是将model中当前有的数据设置到代理上。自己从model取出数据,自己setValue到editor上。
setModelData是将editor上的数据保存到Model中。
updateEditorGeometry就是将editor设置到一定位置,并且有一定大小,使这个editor看起来像是正好嵌入到格子里面一样。用的是option.rect。
4. closeEditor() signal 表明用户完成编辑数据,编辑控件可以销毁。
5. commitData() signal 必须在完成编辑数据之后,发送该信号,将会把新数据写回Model
6. paint() and sizeHint(), QitemDelegate默认继承了该方法,如果需要特殊风格绘制单元项中内容,还需重载这两个函数。
一、
SpinBoxDelegate例子是Qt Assistant中提供的一个非常优秀的例子,虽然讲的是继承于QItemDelegate的例子。但对于我们理解Delegate-委托这个概念,非常有帮助。
它重载了必须的几个函数:
(1) QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,const QModelIndex &index) const;
(2) void setEditorData(QWidget *editor, const QModelIndex &index) const;
(3) void setModelData(QWidget *editor, QAbstractItemModel *model,const QModelIndex &index) const;
(4) void updateEditorGeometry(QWidget *editor,const QStyleOptionViewItem &option, const QModelIndex &index) const;
下面把源码附上,并加上部分注释。附件有源码可以下载。
Main.cpp
- #include <QApplication>
- #include <QHeaderView>
- #include <QItemSelectionModel>
- #include <QStandardItemModel>
- #include <QTableView>
- #include "delegate.h"
- int main(int argc, char *argv[])
- {
- QApplication app(argc, argv);
- //构建一个4行,2列的项模型
- QStandardItemModel model(4, 2);
- //声明一个TableView
- QTableView tableView;
- //绑定模型
- tableView.setModel(&model);
- //声明一个委托
- SpinBoxDelegate delegate;
- //设定视图的委托
- tableView.setItemDelegate(&delegate);
- //ensuring that the view does not waste any of the space assigned to it for its header
- //最后一列全部填充View
- tableView.horizontalHeader()->setStretchLastSection(true);
- //初始化Model
- for (int row = 0; row < 4; ++row) {
- for (int column = 0; column < 2; ++column) {
- QModelIndex index = model.index(row, column, QModelIndex());
- model.setData(index, QVariant((row+1) * (column+1)));
- }
- }
- tableView.setWindowTitle(QObject::tr("Spin Box Delegate"));
- tableView.show();
- return app.exec();
- }
delegate.h
- #ifndef DELEGATE_H
- #define DELEGATE_H
- #include <QItemDelegate>
- #include <QModelIndex>
- #include <QObject>
- #include <QSize>
- #include <QSpinBox>
- class SpinBoxDelegate : public QItemDelegate
- {
- Q_OBJECT
- public:
- SpinBoxDelegate(QObject *parent = 0);
- //返回一个编辑控件,用来编辑指定项的数据
- QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
- const QModelIndex &index) const;
- //将Model中数据赋值到控件上
- 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;
- };
- #endif
delegate.cpp
- #include <QtGui>
- #include "delegate.h"
- SpinBoxDelegate::SpinBoxDelegate(QObject *parent)
- : QItemDelegate(parent)
- {
- }
- //返回一个编辑控件,用来编辑指定项的数据
- QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
- const QStyleOptionViewItem &/* option */,
- const QModelIndex &/* index */) const
- {
- //返回该QSpinBox控件
- QSpinBox *editor = new QSpinBox(parent);
- editor->setMinimum(0);
- editor->setMaximum(100);
- return editor;
- }
- //将Model中数据赋值到控件上
- void SpinBoxDelegate::setEditorData(QWidget *editor,
- const QModelIndex &index) const
- {
- //返回该索引的模型,继而返回该模型中此索引的编辑角色数据
- int value = index.model()->data(index, Qt::EditRole).toInt();
- //给控件赋值
- QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
- spinBox->setValue(value);
- }
- //设定模型数据,根据指定项中对应编辑控件的数据
- void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
- const QModelIndex &index) const
- {
- QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
- spinBox->interpretText();
- int value = spinBox->value();
- //设置模型的数据
- model->setData(index, value, Qt::EditRole);
- }
- //更新编辑框几何形状
- void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
- const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
- {
- //根据option,设置编辑框位置
- editor->setGeometry(option.rect);
- }
- 二、
-
trackeEditorDelegate例子是《 C++ GUI Programming with Qt 4》中自定义委托的标准例子。
和上一个SpinBox例子相比更完整:它多了自定义Editor、重载Paint()函数、提交数据信号commitData()、关闭控件信号closeEditor()
附件中有源码可以下载。
Main.cpp
- #include <QApplication>
- #include "trackeditor.h"
- int main(int argc, char *argv[])
- {
- QApplication app(argc, argv);
- QList<Track> tracks;
- tracks << Track("The Flying Dutchman: Overture", 630)
- << Track("The Flying Dutchman: Wie aus der Fern laengst "
- "vergangner Zeiten", 374)
- << Track("The Flying Dutchman: Steuermann, lass die Wacht",
- 152)
- << Track("Die Walkuere: Ride of the Valkyries", 286)
- << Track("Tannhaeuser: Freudig begruessen wir die edle "
- "Halle", 384)
- << Track("Tannhaeuser: Wie Todesahnung - O du mein holder "
- "Abendstern", 257)
- << Track("Lohengrin: Treulich gefuert ziehet dahnin", 294)
- << Track("Lohengrin: In fernem Land", 383)
- << Track("Die Meistersinger von Nuernberg: Overture", 543)
- << Track("Die Meistersinger von Nuernberg: Verachtet mir "
- "die Meister nicht", 200)
- << Track("Die Meistersinger von Nuernberg: Ehrt eure "
- "deutschen Meister", 112)
- << Track("Goetterdaemmerung: Funeral Music", 469)
- << Track("Tristan und Isolde: Mild und leise, wie er "
- "laechelt", 375);
- //自定义编辑控件
- TrackEditor editor(&tracks);
- editor.resize(600, 300);
- editor.show();
- return app.exec();
- }
TrackEditor.h
- #ifndef TRACKEDITOR_H
- #define TRACKEDITOR_H
- #include <QDialog>
- #include <QList>
- class QDialogButtonBox;
- class QTableWidget;
- class Track
- {
- public:
- Track(const QString &title = "", int duration = 0);
- QString title;
- int duration;
- };
- class TrackEditor : public QDialog
- {
- Q_OBJECT
- public:
- TrackEditor(QList<Track> *tracks, QWidget *parent = 0);
- void done(int result);
- private slots:
- void addTrack();
- private:
- QTableWidget *tableWidget;
- QDialogButtonBox *buttonBox;
- QList<Track> *tracks;
- };
- #endif
TrackEditor.cpp
- #include <QtGui>
- #include "trackdelegate.h"
- #include "trackeditor.h"
- Track::Track(const QString &title, int duration)
- {
- this->title = title;
- this->duration = duration;
- }
- TrackEditor::TrackEditor(QList<Track> *tracks, QWidget *parent)
- : QDialog(parent)
- {
- this->tracks = tracks;
- //视图控件
- tableWidget = new QTableWidget(tracks->count(), 2);
- //设置委托
- tableWidget->setItemDelegate(new TrackDelegate(1));
- //设置视图列头
- tableWidget->setHorizontalHeaderLabels(
- QStringList() << tr("Track") << tr("Duration"));
- //初始化视图内容
- for (int row = 0; row < tracks->count(); ++row) {
- Track track = tracks->at(row);
- QTableWidgetItem *item0 = new QTableWidgetItem(track.title);
- //设置第0列中所有项
- tableWidget->setItem(row, 0, item0);
- QTableWidgetItem *item1
- = new QTableWidgetItem(QString::number(track.duration));
- item1->setTextAlignment(Qt::AlignRight);
- //设置第1列所有项
- tableWidget->setItem(row, 1, item1);
- }
- //根据委托的SizeHint,重新设置视图大小
- tableWidget->resizeColumnToContents(0);
- buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
- | QDialogButtonBox::Cancel);
- QPushButton *addTrackButton = buttonBox->addButton(tr("&Add Track"),
- QDialogButtonBox::ActionRole);
- connect(addTrackButton, SIGNAL(clicked()), this, SLOT(addTrack()));
- connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
- connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
- QVBoxLayout *mainLayout = new QVBoxLayout;
- mainLayout->addWidget(tableWidget);
- mainLayout->addWidget(buttonBox);
- setLayout(mainLayout);
- setWindowTitle(tr("Track Editor"));
- }
- void TrackEditor::done(int result)
- {
- //ok
- if (result == QDialog::Accepted) {
- tracks->clear();
- for (int row = 0; row < tableWidget->rowCount(); ++row) {
- QString title = tableWidget->item(row, 0)->text();
- QTableWidgetItem *item = tableWidget->item(row, 1);
- int duration = item ? item->text().toInt() : 0;
- tracks->append(Track(title, duration));
- }
- }
- QDialog::done(result);
- }
- void TrackEditor::addTrack()
- {
- //在最后新插入一行
- tableWidget->insertRow(tableWidget->rowCount());
- }
TrackDelegate.h
- #ifndef TRACKDELEGATE_H
- #define TRACKDELEGATE_H
- #include <QItemDelegate>
- class TrackDelegate : public QItemDelegate
- {
- Q_OBJECT
- public:
- TrackDelegate(int durationColumn, QObject *parent = 0);
- //重新绘制
- void paint(QPainter *painter, const QStyleOptionViewItem &option,
- const QModelIndex &index) const;
- QWidget *createEditor(QWidget *parent,
- 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;
- private slots:
- void commitAndCloseEditor();
- private:
- int durationColumn;
- };
- #endif
TrackDelegate.cpp
- #include <QtGui>
- #include "trackdelegate.h"
- TrackDelegate::TrackDelegate(int durationColumn, QObject *parent)
- : QItemDelegate(parent)
- {
- this->durationColumn = durationColumn;
- }
- void TrackDelegate::paint(QPainter *painter,
- const QStyleOptionViewItem &option,
- const QModelIndex &index) const
- {
- //保存音轨的列
- if (index.column() == durationColumn) {
- //获得索引对应Model中的数据
- int secs = index.model()->data(index, Qt::DisplayRole).toInt();
- //设置时间格式字符串 分:秒
- QString text = QString("%1:%2")
- .arg(secs / 60, 2, 10, QChar('0'))
- .arg(secs % 60, 2, 10, QChar('0'));
- //获取项风格设置
- QStyleOptionViewItem myOption = option;
- myOption.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
- //绘制文本
- drawDisplay(painter, myOption, myOption.rect, text);
- //如果当前有焦点,就绘制一个焦点矩形,否则什么都不做
- drawFocus(painter, myOption, myOption.rect);
- } else{
- //否则默认
- QItemDelegate::paint(painter, option, index);
- }
- }
- QWidget *TrackDelegate::createEditor(QWidget *parent,
- const QStyleOptionViewItem &option,
- const QModelIndex &index) const
- {
- //音轨时间列
- if (index.column() == durationColumn) {
- //时间编辑控件
- QTimeEdit *timeEdit = new QTimeEdit(parent);
- //时间编辑控件文本格式
- timeEdit->setDisplayFormat("mm:ss");
- //如果编辑结束,激活提交和关闭槽
- connect(timeEdit, SIGNAL(editingFinished()),
- this, SLOT(commitAndCloseEditor()));
- return timeEdit;
- } else {
- //否则使用默认委托处理
- return QItemDelegate::createEditor(parent, option, index);
- }
- }
- //设置控件值
- void TrackDelegate::setEditorData(QWidget *editor,
- const QModelIndex &index) const
- {
- //音轨时间列
- if (index.column() == durationColumn) {
- //获得当前索引在Model中的值
- int secs = index.model()->data(index, Qt::DisplayRole).toInt();
- //设置时间控件的值
- QTimeEdit *timeEdit = qobject_cast<QTimeEdit *>(editor);
- timeEdit->setTime(QTime(0, secs / 60, secs % 60));
- } else {
- QItemDelegate::setEditorData(editor, index);
- }
- }
- //设置Model值
- void TrackDelegate::setModelData(QWidget *editor,
- QAbstractItemModel *model,
- const QModelIndex &index) const
- {
- //音轨列
- if (index.column() == durationColumn) {
- //获得时间控件值
- QTimeEdit *timeEdit = qobject_cast<QTimeEdit *>(editor);
- QTime time = timeEdit->time();
- int secs = (time.minute() * 60) + time.second();
- //设置模型值
- model->setData(index, secs);
- } else {
- //否则使用默认委托处理
- QItemDelegate::setModelData(editor, model, index);
- }
- }
- //自定义 提交和关闭 槽函数
- void TrackDelegate::commitAndCloseEditor()
- {
- QTimeEdit *editor = qobject_cast<QTimeEdit *>(sender());
- //提交该控件的值 否则模型中数据不更改
- emit commitData(editor);
- //关闭该控件 可以告知委托去代开下一个控件
- emit closeEditor(editor);
- }
三、
-
- 这节需要事件、绘图等基础知识,比较复杂。。。先收藏,后续学习。
Star Delegate Example
- itemviews/stardelegate/stardelegate.cpp
- itemviews/stardelegate/stardelegate.h
- itemviews/stardelegate/stareditor.cpp
- itemviews/stardelegate/stareditor.h
- itemviews/stardelegate/starrating.cpp
- itemviews/stardelegate/starrating.h
- itemviews/stardelegate/main.cpp
- itemviews/stardelegate/stardelegate.pro
- StarRating is the custom data type. It stores a rating expressed as stars, such as "2 out of 5 stars" or "5 out of 6 stars".
- StarDelegate inherits QItemDelegate and provides support for StarRating (in addition to the data types already handled byQItemDelegate).
- StarEditor inherits QWidget and is used by StarDelegate to let the user edit a star rating using the mouse.
- It is possible to open editors programmatically by calling QAbstractItemView::edit(), instead of relying on edit triggers. This could be use to support other edit triggers than those offered by the QAbstractItemView::EditTrigger enum. For example, in the Star Delegate example, hovering over an item with the mouse might make sense as a way to pop up an editor.
- By reimplementing QAbstractItemDelegate::editorEvent(), it is possible to implement the editor directly in the delegate, instead of creating a separate QWidget subclass.
The Star Delegate example shows how to create a delegate that can paint itself and that supports editing.
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 provides QItemDelegate, which inherits QAbstractItemDelegate and handles the most common data types (notably int and QString). 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'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 store values such as "1 out of 5 stars".
The example consists of the following classes:
To show the StarDelegate in action, we will fill a QTableWidget with some data and install the delegate on it.
StarDelegate Class Definition
Here's the definition of the StarDelegate class:
class StarDelegate : public QStyledItemDelegate { Q_OBJECT public: StarDelegate(QWidget *parent = 0) : QStyledItemDelegate(parent) {} void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; QWidget *createEditor(QWidget *parent, 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; private slots: void commitAndCloseEditor(); };
All public functions are reimplemented virtual functions from QItemDelegate to provide custom rendering and editing.
StarDelegate Class Implementation
The paint() function is reimplemented from QItemDelegate and is called whenever the view needs to repaint an item:
void StarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (qVariantCanConvert<StarRating>(index.data())) { StarRating starRating = qVariantValue<StarRating>(index.data()); if (option.state & QStyle::State_Selected) painter->fillRect(option.rect, option.palette.highlight()); starRating.paint(painter, option.rect, option.palette, StarRating::ReadOnly); } else { QStyledItemDelegate::paint(painter, option, index); }
The function is invoked once for each item, represented by a QModelIndex object from the model. If the data stored in the item is aStarRating, 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 usingStarRating::paint(), which we will review later.
StartRatings can be stored in a QVariant thanks to the Q_DECLARE_METATYPE() macro appearing in starrating.h. More on this later.
The createEditor() function is called when the user starts editing an item:
QWidget *StarDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (qVariantCanConvert<StarRating>(index.data())) { StarEditor *editor = new StarEditor(parent); connect(editor, SIGNAL(editingFinished()), this, SLOT(commitAndCloseEditor())); return editor; } else { return QStyledItemDelegate::createEditor(parent, option, index); } }
If the item is a StarRating, we create a StarEditor and connect its editingFinished() signal to our commitAndCloseEditor() slot, so we can update the model when the editor closes.
Here's the implementation of commitAndCloseEditor():
void StarDelegate::commitAndCloseEditor() { StarEditor *editor = qobject_cast<StarEditor *>(sender()); emit commitData(editor); emit closeEditor(editor); }
When the user is done editing, we emit commitData() and closeEditor() (both declared in QAbstractItemDelegate), to tell the model that there is edited data and to inform the view that the editor is no longer needed.
The setEditorData() function is called when an editor is created to initialize it with data from the model:
void StarDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { if (qVariantCanConvert<StarRating>(index.data())) { StarRating starRating = qVariantValue<StarRating>(index.data()); StarEditor *starEditor = qobject_cast<StarEditor *>(editor); starEditor->setStarRating(starRating); } else { QStyledItemDelegate::setEditorData(editor, index); } }
We simply call setStarRating() on the editor.
The setModelData() function is called when editing is finished, to commit data from the editor to the model:
void StarDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { if (qVariantCanConvert<StarRating>(index.data())) { StarEditor *starEditor = qobject_cast<StarEditor *>(editor); model->setData(index, qVariantFromValue(starEditor->starRating())); } else { QStyledItemDelegate::setModelData(editor, model, index); } }
The sizeHint() function returns an item's preferred size:
QSize StarDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { if (qVariantCanConvert<StarRating>(index.data())) { StarRating starRating = qVariantValue<StarRating>(index.data()); return starRating.sizeHint(); } else { return QStyledItemDelegate::sizeHint(option, index); } }
We simply forward the call to StarRating.
StarEditor Class Definition
The StarEditor class was used when implementing StarDelegate. Here's the class definition:
class StarEditor : public QWidget { Q_OBJECT public: StarEditor(QWidget *parent = 0); QSize sizeHint() const; void setStarRating(const StarRating &starRating) { myStarRating = starRating; } StarRating starRating() { return myStarRating; } signals: void editingFinished(); protected: void paintEvent(QPaintEvent *event); void mouseMoveEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); private: int starAtPosition(int x); StarRating myStarRating; };
The class lets the user edit a StarRating by moving the mouse over the editor. It emits the editingFinished() signal when the user clicks on the editor.
The protected functions are reimplemented from QWidget to handle mouse and paint events. The private function starAtPosition() is a helper function that returns the number of the star under the mouse pointer.
StarEditor Class Implementation
Let's start with the constructor:
StarEditor::StarEditor(QWidget *parent) : QWidget(parent) { 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() function is reimplemented from QWidget:
void StarEditor::paintEvent(QPaintEvent *) { QPainter painter(this); myStarRating.paint(&painter, rect(), this->palette(), StarRating::Editable); }
We simply call StarRating::paint() to draw the stars, just like we did when implementing StarDelegate.
void StarEditor::mouseMoveEvent(QMouseEvent *event) { int star = starAtPosition(event->x()); if (star != myStarRating.starCount() && star != -1) { myStarRating.setStarCount(star); update(); } }
In the mouse event handler, we call setStarCount() on the private data member myStarRating to reflect the current cursor position, and we call QWidget::update() to force a repaint.
void StarEditor::mouseReleaseEvent(QMouseEvent * /* event */) { emit editingFinished(); }
When the user releases a mouse button, we simply emit the editingFinished() signal.
int StarEditor::starAtPosition(int x) { int star = (x / (myStarRating.sizeHint().width() / myStarRating.maxStarCount())) + 1; if (star <= 0 || star > myStarRating.maxStarCount()) return -1; return star; }
The starAtPosition() function uses basic linear algebra to find out which star is under the cursor.
StarRating Class Definition
class StarRating { public: enum EditMode { Editable, ReadOnly }; StarRating(int starCount = 1, int maxStarCount = 5); void paint(QPainter *painter, const QRect &rect, const QPalette &palette, EditMode mode) const; QSize sizeHint() const; int starCount() const { return myStarCount; } int maxStarCount() const { return myMaxStarCount; } void setStarCount(int starCount) { myStarCount = starCount; } void setMaxStarCount(int maxStarCount) { myMaxStarCount = maxStarCount; } private: QPolygonF starPolygon; QPolygonF diamondPolygon; int myStarCount; int myMaxStarCount; }; Q_DECLARE_METATYPE(StarRating)
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 aQPaintDevice, which in this example is either a view or an editor. The myStarCount member variable stores the current rating, andmyMaxStarCount stores the highest possible rating (typically 5).
The Q_DECLARE_METATYPE() macro makes the type StarRating known to QVariant, making it possible to store StarRating values inQVariant.
StarRating Class Implementation
The constructor initializes myStarCount and myMaxStarCount, and sets up the polygons used to draw stars and diamonds:
StarRating::StarRating(int starCount, int maxStarCount) { myStarCount = starCount; myMaxStarCount = maxStarCount; starPolygon << QPointF(1.0, 0.5); for (int i = 1; i < 5; ++i) starPolygon << QPointF(0.5 + 0.5 * cos(0.8 * i * 3.14), 0.5 + 0.5 * sin(0.8 * i * 3.14)); diamondPolygon << QPointF(0.4, 0.5) << QPointF(0.5, 0.4) << QPointF(0.6, 0.5) << QPointF(0.5, 0.6) << QPointF(0.4, 0.5); }
The paint() function paints the stars in this StarRating object on a paint device:
void StarRating::paint(QPainter *painter, const QRect &rect, const QPalette &palette, EditMode mode) const { painter->save(); painter->setRenderHint(QPainter::Antialiasing, true); painter->setPen(Qt::NoPen); if (mode == Editable) { painter->setBrush(palette.highlight()); } else { painter->setBrush(palette.foreground()); } int yOffset = (rect.height() - PaintingScaleFactor) / 2; painter->translate(rect.x(), rect.y() + yOffset); painter->scale(PaintingScaleFactor, PaintingScaleFactor); for (int i = 0; i < myMaxStarCount; ++i) { if (i < myStarCount) { painter->drawPolygon(starPolygon, Qt::WindingFill); } else if (mode == Editable) { painter->drawPolygon(diamondPolygon, Qt::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 Editable or ReadOnly. If mode is editable, we use the Highlight color instead of the Foreground color to draw the stars.
Then we draw the stars. If we are in Edit mode, we paint diamonds in place of stars if the rating is less than the highest rating.
The sizeHint() function returns the preferred size for an area to paint the stars on:
QSize StarRating::sizeHint() const { return PaintingScaleFactor * QSize(myMaxStarCount, 1); }
The preferred size is just enough to paint the maximum number of stars. The function is called by both StarDelegate::sizeHint() andStarEditor::sizeHint().
The main() Function
Here's the program's main() function:
int main(int argc, char *argv[]) { QApplication app(argc, argv); QTableWidget tableWidget(4, 4); tableWidget.setItemDelegate(new StarDelegate); tableWidget.setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::SelectedClicked); tableWidget.setSelectionBehavior(QAbstractItemView::SelectRows); QStringList headerLabels; headerLabels << "Title" << "Genre" << "Artist" << "Rating"; tableWidget.setHorizontalHeaderLabels(headerLabels); populateTableWidget(&tableWidget); tableWidget.resizeColumnsToContents(); tableWidget.resize(500, 300); tableWidget.show(); return app.exec(); }
The main() function creates a QTableWidget and sets a StarDelegate on it. DoubleClicked and SelectedClicked are set as edit triggers, so that the editor is opened with a single click when the star rating item is selected.
The populateTableWidget() function fills the QTableWidget with data:
void populateTableWidget(QTableWidget *tableWidget) { static const struct { const char *title; const char *genre; const char *artist; int rating; } staticData[] = { { "Mass in B-Minor", "Baroque", "J.S. Bach", 5 }, ... { 0, 0, 0, 0 } }; for (int row = 0; staticData[row].title != 0; ++row) { QTableWidgetItem *item0 = new QTableWidgetItem(staticData[row].title); QTableWidgetItem *item1 = new QTableWidgetItem(staticData[row].genre); QTableWidgetItem *item2 = new QTableWidgetItem(staticData[row].artist); QTableWidgetItem *item3 = new QTableWidgetItem; item3->setData(0, qVariantFromValue(StarRating(staticData[row].rating))); tableWidget->setItem(row, 0, item0); tableWidget->setItem(row, 1, item1); tableWidget->setItem(row, 2, item2); tableWidget->setItem(row, 3, item3); } }
Notice the call to qVariantFromValue to convert a StarRating to a QVariant.
Possible Extensions and Suggestions
There are many ways to customize Qt'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:
- 这节需要事件、绘图等基础知识,比较复杂。。。先收藏,后续学习。