Star Delegate 示例
“星形委托”示例演示了如何创建一个能够自行绘制且支持编辑的委托。

在QListView 、QTableView 或QTreeView 中显示数据时,各个项目由一个委托对象进行绘制。此外,当用户开始编辑某个项目(例如,通过双击该项目)时,委托对象会提供一个编辑器控件,并在编辑过程中将其置于该项目之上。
委托类是QAbstractItemDelegate 的子类。Qt提供了QStyledItemDelegate ,该类继承自QAbstractItemDelegate ,并支持最常见的数据类型(特别是int 和QString )。 如果需要支持自定义数据类型,或者希望自定义现有数据类型的渲染或编辑行为,我们可以继承QAbstractItemDelegate 或QStyledItemDelegate类。有关委托的更多信息,请参阅《委托类》;若需了解 Qt XML 模型/视图架构(包括委托)的高级介绍,请参阅《模型/视图编程》。
在本示例中,我们将了解如何实现一个自定义委托来渲染和编辑“星级评分”数据类型,该类型可以存储诸如“5 星中的 1 星”之类的值。
该示例包含以下类:
StarRating是自定义数据类型。它存储以星级表示的评分,例如“5 星中的 2 星”或“6 星中的 5 星”。StarDelegate继承自QStyledItemDelegate ,并支持StarRating(除了QStyledItemDelegate 已处理的数据类型之外)。StarEditor继承自QWidget ,并被StarDelegate用于允许用户使用鼠标编辑星级评分。
为了演示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::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 上绘制星星,在本例中, 可以是视图(view)或编辑器(editor)。成员变量myStarCount 存储当前评分,而myMaxStarCount 存储最高可能评分(通常为5)。
宏Q_DECLARE_METATYPE() 使类型StarRating 被QVariant 识别,从而能够将StarRating 的值存储在QVariant 中。
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 枚举之外的其他编辑触发器。例如,在“星号委托”示例中,将鼠标悬停在项目上作为弹出编辑器的方式可能更合理。
- 通过重写QAbstractItemDelegate::editorEvent() 方法,可以在委托中直接实现编辑器,而无需创建一个单独的QWidget 子类。
© 2026 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.