星形委托示例
星形委托示例展示了如何创建一个可自行绘制并支持编辑的委托。
在QListView 、QTableView 或QTreeView 中显示数据时,各个项目由委托绘制。此外,当用户开始编辑一个项目时(例如,双击该项目),委托会提供一个编辑器窗口小部件,在编辑过程中,该窗口小部件会被放置在项目的顶部。
委托是QAbstractItemDelegate 的子类。Qt XML 提供了QStyledItemDelegate ,它继承于QAbstractItemDelegate 并处理最常见的数据类型(主要是int
和QString )。如果我们需要支持自定义数据类型,或想自定义现有数据类型的渲染或编辑,我们可以子类化QAbstractItemDelegate 或QStyledItemDelegate 。有关委托的更多信息,请参阅委托类,如果您需要 Qt 的模型/视图架构(包括委托)的高级介绍,请参阅模型/视图编程。
在本示例中,我们将了解如何实现自定义委托,以呈现和编辑 "星级 "数据类型,该数据类型可存储 "1(满分 5 星)"等值。
该示例由以下类组成:
StarRating
是自定义数据类型。它存储以星级表示的评分,如 "2 分(满分 5 星)"或 "5 分(满分 6 星)"。StarDelegate
继承于 ,并为 提供支持(除了 已处理的数据类型外)。QStyledItemDelegateStarRating
QStyledItemDelegateStarEditor
StarDelegate
继承了 ,用于让用户使用鼠标编辑星级。QWidget
为了展示StarDelegate
的操作,我们将在QTableWidget 中填充一些数据,并在其上安装委托。
StarDelegate 类的定义
下面是StarDelegate
类的定义:
class StarDelegate : public QStyledItemDelegate { Q_OBJECT public: using QStyledItemDelegate::QStyledItemDelegate; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setEditorData(QWidget *editor, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; private slots: void commitAndCloseEditor(); };
所有公共函数都是从QStyledItemDelegate 重新实现的虚拟函数,以提供自定义渲染和编辑功能。
StarDelegate 类的实现
paint() 函数是从QStyledItemDelegate 中重新实现的,每当视图需要重新绘制一个项目时都会调用该函数:
void StarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.data().canConvert<StarRating>()) { StarRating starRating = qvariant_cast<StarRating>(index.data()); if (option.state & QStyle::State_Selected) painter->fillRect(option.rect, option.palette.highlight()); starRating.paint(painter, option.rect, option.palette, StarRating::EditMode::ReadOnly); } else { QStyledItemDelegate::paint(painter, option, index); }
每个项目都会调用一次该函数,该项目由模型中的QModelIndex 对象表示。如果项目中存储的数据是StarRating
,我们就自己绘制;否则,我们就让QStyledItemDelegate 代为绘制。这样可以确保StarDelegate
能够处理最常见的数据类型。
如果项目是StarRating
,如果项目被选中,我们将绘制背景,并使用StarRating::paint()
绘制项目,稍后我们将回顾这一点。
StartRating
由于starrating.h
中出现了Q_DECLARE_METATYPE() 宏,我们可以将 s 保存在QVariant 中。稍后将详细介绍。
当用户开始编辑项目时,createEditor() 函数将被调用:
QWidget *StarDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.data().canConvert<StarRating>()) { StarEditor *editor = new StarEditor(parent); connect(editor, &StarEditor::editingFinished, this, &StarDelegate::commitAndCloseEditor); return editor; } return QStyledItemDelegate::createEditor(parent, option, index); }
如果项目是StarRating
,我们将创建一个StarEditor
,并将其editingFinished()
信号连接到我们的commitAndCloseEditor()
插槽,这样我们就可以在编辑器关闭时更新模型。
下面是commitAndCloseEditor()
的实现:
void StarDelegate::commitAndCloseEditor() { StarEditor *editor = qobject_cast<StarEditor *>(sender()); emit commitData(editor); emit closeEditor(editor); }
当用户完成编辑时,我们会发出commitData() 和closeEditor() 信号(这两个信号都在QAbstractItemDelegate 中声明),告诉模型有已编辑的数据,并通知视图不再需要编辑器。
当创建编辑器时,我们会调用setEditorData() 函数,用模型中的数据对其进行初始化:
void StarDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { if (index.data().canConvert<StarRating>()) { StarRating starRating = qvariant_cast<StarRating>(index.data()); StarEditor *starEditor = qobject_cast<StarEditor *>(editor); starEditor->setStarRating(starRating); } else { QStyledItemDelegate::setEditorData(editor, index); } }
我们只需在编辑器上调用setStarRating()
即可。
编辑完成后,调用setModelData() 函数将编辑器中的数据提交到模型中:
void StarDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { if (index.data().canConvert<StarRating>()) { StarEditor *starEditor = qobject_cast<StarEditor *>(editor); model->setData(index, QVariant::fromValue(starEditor->starRating())); } else { QStyledItemDelegate::setModelData(editor, model, index); } }
sizeHint()
函数返回项目的首选尺寸:
QSize StarDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.data().canConvert<StarRating>()) { StarRating starRating = qvariant_cast<StarRating>(index.data()); return starRating.sizeHint(); } return QStyledItemDelegate::sizeHint(option, index); }
我们只需将调用转发到StarRating
。
StarEditor 类定义
在实现StarDelegate
时使用了StarEditor
类。下面是该类的定义:
class StarEditor : public QWidget { Q_OBJECT public: StarEditor(QWidget *parent = nullptr); QSize sizeHint() const override; void setStarRating(const StarRating &starRating) { myStarRating = starRating; } StarRating starRating() { return myStarRating; } signals: void editingFinished(); protected: void paintEvent(QPaintEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; private: int starAtPosition(int x) const; StarRating myStarRating; };
该类可让用户通过将鼠标移至编辑器上编辑StarRating
。当用户点击编辑器时,该类会发出editingFinished()
信号。
保护函数由QWidget 重新实现,用于处理鼠标和绘制事件。私有函数starAtPosition()
是一个辅助函数,用于返回鼠标指针下星星的编号。
星形编辑器类的实现
让我们从构造函数开始:
StarEditor::StarEditor(QWidget *parent) : QWidget(parent) { setMouseTracking(true); setAutoFillBackground(true); }
我们在部件上启用mouse tracking ,这样即使用户不按住任何鼠标按钮,我们也能跟踪光标。我们还打开了QWidget 的auto-fill background 功能,以获得不透明的背景。(如果不调用该功能,视图的背景就会透过编辑器发光)。
paintEvent() 函数由QWidget 重新实现:
void StarEditor::paintEvent(QPaintEvent *) { QPainter painter(this); myStarRating.paint(&painter, rect(), palette(), StarRating::EditMode::Editable); }
我们只需调用StarRating::paint()
来绘制星星,就像实现StarDelegate
时一样。
void StarEditor::mouseMoveEvent(QMouseEvent *event) { const int star = starAtPosition(event->position().toPoint().x()); if (star != myStarRating.starCount() && star != -1) { myStarRating.setStarCount(star); update(); } QWidget::mouseMoveEvent(event); }
在鼠标事件处理程序中,我们在私有数据成员myStarRating
上调用setStarCount()
来反映当前光标的位置,并调用QWidget::update() 来强制重绘。
void StarEditor::mouseReleaseEvent(QMouseEvent *event) { emit editingFinished(); QWidget::mouseReleaseEvent(event); }
当用户释放鼠标按钮时,我们只需发出editingFinished()
信号。
int StarEditor::starAtPosition(int x) const { const int star = (x / (myStarRating.sizeHint().width() / myStarRating.maxStarCount())) + 1; if (star <= 0 || star > myStarRating.maxStarCount()) return -1; return star; }
starAtPosition()
函数使用基本的线性代数来找出光标下的星星。
StarRating 类定义
class StarRating { public: enum class EditMode { Editable, ReadOnly }; explicit 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)
StarRating
类以星数表示评分。除了保存数据外,它还能在QPaintDevice 上绘制星星,在本例中, 就是视图或编辑器。myStarCount
成员变量存储当前评分,而myMaxStarCount
则存储可能的最高评分(通常为 5 分)。
Q_DECLARE_METATYPE()
宏使QVariant 知道StarRating
类型,从而可以在QVariant 中存储StarRating
值。
StarRating 类的实现
构造函数初始化myStarCount
和myMaxStarCount
,并设置用于绘制星星和钻石的多边形:
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 * std::cos(0.8 * i * 3.14), 0.5 + 0.5 * std::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); }
paint()
函数在绘画设备上绘制StarRating
对象中的星星:
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); painter->setBrush(mode == EditMode::Editable ? palette.highlight() : palette.windowText()); const 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 == EditMode::Editable) painter->drawPolygon(diamondPolygon, Qt::WindingFill); painter->translate(1.0, 0.0); } painter->restore(); }
我们首先要设置绘制时使用的笔和画笔。mode
参数可以是Editable
或ReadOnly
。如果mode
是可编辑的,我们就使用Highlight 颜色而不是WindowText 颜色来绘制星星。
然后绘制星星。如果是Edit
模式,如果评分小于最高评分,我们就用钻石代替星星。
sizeHint()
函数返回绘制星星区域的首选尺寸:
首选大小刚好能画出最大数量的星星。StarDelegate::sizeHint()
和StarEditor::sizeHint()
都会调用该函数。
main()
函数
下面是程序的main()
函数:
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); tableWidget.setHorizontalHeaderLabels({"Title", "Genre", "Artist", "Rating"}); populateTableWidget(&tableWidget); tableWidget.resizeColumnsToContents(); tableWidget.resize(500, 300); tableWidget.show(); return app.exec(); }
main()
函数创建了一个QTableWidget ,并在其上设置了一个StarDelegate
。DoubleClicked 和SelectedClicked 被设置为edit triggers ,这样在选择星级项目时,只需单击一下就能打开编辑器。
populateTableWidget()
函数将数据填入QTableWidget :
void populateTableWidget(QTableWidget *tableWidget) { static constexpr struct { const char *title; const char *genre; const char *artist; int rating; } staticData[] = { { "Mass in B-Minor", "Baroque", "J.S. Bach", 5 }, ... { nullptr, nullptr, nullptr, 0 } }; for (int row = 0; staticData[row].title != nullptr; ++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, QVariant::fromValue(StarRating(staticData[row].rating))); tableWidget->setItem(row, 0, item0); tableWidget->setItem(row, 1, item1); tableWidget->setItem(row, 2, item2); tableWidget->setItem(row, 3, item3); } }
注意调用QVariant::fromValue 将StarRating
转换为QVariant 。
可能的扩展和建议
定制 Qt模型/视图框架的方法有很多。本例中使用的方法适合大多数自定义委托和编辑器。星形委托和星形编辑器未使用的可能性举例如下:
- 可以通过调用QAbstractItemView::edit() 以编程方式打开编辑器,而不是依赖编辑触发器。除QAbstractItemView::EditTrigger 枚举提供的编辑触发器外,还可用于支持其他编辑触发器。例如,在 Star Delegate 的示例中,用鼠标悬停在一个项目上就可以弹出编辑器。
- 通过重新实现QAbstractItemDelegate::editorEvent() ,可以直接在委托中实现编辑器,而无需创建单独的QWidget 子类。
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.