https://doc.qt.io/qt-5/qtwidgets-itemviews-stardelegate-example.html
一、介绍
star委托示例展示了如何创建一个可以自己绘制并支持编辑的委托。
当在QListView、QTableView或QTreeView中显示数据时,单个项由委托绘制。此外,当用户开始编辑项(例如,双击项)时,委托提供一个编辑器小部件,在进行编辑时,该小部件被放置在项的顶部。
委托是QAbstractItemDelegate的子类。Qt提供了QStyledItemDelegate,它继承了QAbstractItemDelegate并处理最常见的数据类型(特别是int和QString)。如果我们需要支持自定义数据类型,或者想要自定义现有数据类型的呈现或编辑,我们可以子类化QAbstractItemDelegate或QStyledItemDelegate。如果你需要Qt的模型/视图体系结构(包括委托)的高级介绍,请参阅委托类以获得更多关于委托和模型/视图编程的信息。
在本例中,我们将看到如何实现自定义委托来呈现和编辑“星级评级”数据类型,它可以存储诸如“5颗星中的1颗”这样的值。
这个例子由以下类组成:
- StarRating:是自定义数据类型。它存储以星表示的评级,如“5星中的2星”或“6星中的5星”。
- starDelegate:继承了QStyledItemDelegate并提供了对StarRating的支持(除了QStyledItemDelegate已经处理的数据类型之外)。
- StarEditor继承了QWidget,StarDelegate使用它来让用户使用鼠标编辑星级评级。
为了显示StarDelegate的实际操作,我们将用一些数据填充一个QTableWidget,并在其上安装delegate。
二、StarDelegate类
2.1定义
以下是StarDelegate类的定义:
class StarDelegate : public QStyledItemDelegate {Q_OBJECTpublic:// 使用父类的构造函数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中重新实现的虚函数,以提供自定义呈现和编辑。
2.2 实现
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()绘制该项目,
稍后我们将回顾这一点。
由于Q_DECLARE_METATYPE()宏出现在starrate .h中,StartRatings可以存储在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);}
三、StarEditor类
3.1 定义
在实现StarDelegate时使用了StarEditor类。下面是类的定义:
class StarEditor : public QWidget {Q_OBJECTpublic: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()是一个辅助函数,它返回鼠标指针下的*号。
3.2 实现
让我们从构造函数开始:
StarEditor::StarEditor(QWidget* parent): QWidget(parent){setMouseTracking(true);setAutoFillBackground(true);}
我们在小部件上启用鼠标跟踪,这样即使用户没有按下任何鼠标按钮,我们也可以跟踪光标。我们还打开了QWidget的自动填充背景特性,以获得不透明的背景。(如果没有调用,视图的背景将通过编辑器发光。)
它的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->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()函数使用基本的线性代数来找出光标下的星星。
sizeHint():首选的大小刚好足够绘制最大数目的星星。
QSize StarEditor::sizeHint() const{return myStarRating.sizeHint();}
四、StarRating类
4.1 定义
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上绘制星星,在本例中,QPaintDevice是一个视图或编辑器。myStarCount成员变量存储当前评级,而myMaxStarCount存储可能的最高评级(通常为5)。
Q_DECLARE_METATYPE()宏使QVariant知道StarRating类型,使得在QVariant中存储StarRating值成为可能。
4.2 实现
构造函数初始化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();}
五、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被设置为编辑触发器,这样当星型评级项目被选中时,编辑器就会随着一次单击而打开。
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 },//! [1]{ "Three More Foxes", "Jazz", "Maynard Ferguson", 4 },{ "Sex Bomb", "Pop", "Tom Jones", 3 },{ "Barbie Girl", "Pop", "Aqua", 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;// 调用QVariant::fromValue将StarRating转换为QVariantitem3->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。
六、示例代码
stardelegate.h
#ifndef STARDELEGATE_H#define STARDELEGATE_H#include <QStyledItemDelegate>class StarDelegate : public QStyledItemDelegate {Q_OBJECTpublic: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();};#endif
stardelegate.cpp
#include "stardelegate.h"#include "stareditor.h"#include "starrating.h"#include <QtWidgets>// paint()函数从QStyledItemDelegate中重新实现,并在视图需要重绘一个项目时调用void StarDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,const QModelIndex& index) const{// 如果项目中的数据是StarRating,将自己绘制它;// 否则让QStyledItemDelegate去绘制它, 这样可以让StarDelegate处理常见的数据类型if (index.data().canConvert<StarRating>()) {StarRating starRating = qvariant_cast<StarRating>(index.data());// 如果该项目被选中, 我们将绘制背景, 并使用StarRating::paint()绘制该项目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);}}// 当用户开始编辑条目时,createEditor()函数被调用QWidget* StarDelegate::createEditor(QWidget* parent,const QStyleOptionViewItem& option,const QModelIndex& index) const{// 如果条目是StarRating,我们创建一个StarEditor,并绑定信号槽,以确保在编辑器关闭时更新模型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);}// 当用户完成编辑时,我们发出commitData()和closeEditor(),告诉模型有编辑过的数据,并通知视图不再需要编辑器void StarDelegate::commitAndCloseEditor(){StarEditor* editor = qobject_cast<StarEditor*>(sender());// 以下两个信号在QAbstractItemDelegate中声明emit commitData(editor);emit closeEditor(editor);}// 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);}
stareditor.h
#ifndef STAREDITOR_H#define STAREDITOR_H#include <QWidget>#include "starrating.h"// 在实现StarDelegate时使用了StarEditor类class StarEditor : public QWidget {Q_OBJECTpublic: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;};#endif
stareditor.cpp
#include "stareditor.h"#include "starrating.h"#include <QtWidgets>StarEditor::StarEditor(QWidget* parent): QWidget(parent){// 启用鼠标跟踪setMouseTracking(true);// QWidget的自动填充背景特性setAutoFillBackground(true);}// paint()用来绘制星星, 就像我们在实现StarDelegate时所做的那样void StarEditor::paintEvent(QPaintEvent*){QPainter painter(this);myStarRating.paint(&painter, rect(), palette(),StarRating::EditMode::Editable);}// 通过调用StarRating类的setStarCount()来反映当前光标的位置, 并调用QWidget::update()来强制重绘void StarEditor::mouseMoveEvent(QMouseEvent* event){const int star = starAtPosition(event->x());if (star != myStarRating.starCount() && star != -1) {myStarRating.setStarCount(star);update();}QWidget::mouseMoveEvent(event);}// 当用户释放鼠标按钮时, 发出editingFinished()信号void StarEditor::mouseReleaseEvent(QMouseEvent* event){emit editingFinished();QWidget::mouseReleaseEvent(event);}// starAtPosition()函数使用基本的线性代数来找出光标下的星星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;}// 首选的大小刚好足够绘制最大数目的星星QSize StarEditor::sizeHint() const{return myStarRating.sizeHint();}
starrating.h
#ifndef STARRATING_H#define STARRATING_H#include <QPainter>#include <QPolygonF>#include <QSize>// 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;// 存储可能的最高评级(通常为5)int myMaxStarCount;};// Q_DECLARE_METATYPE()宏使QVariant知道StarRating类型,使得在QVariant中存储StarRating值成为可能。Q_DECLARE_METATYPE(StarRating)#endif
starrating.cpp
#include "starrating.h"#include <QtWidgets>#include <cmath>constexpr int PaintingScaleFactor = 20;// 构造函数初始化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);}// 首选的大小刚好足够绘制最大数目的星星QSize StarRating::sizeHint() const{return PaintingScaleFactor * QSize(myMaxStarCount, 1);}// 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();}
main.cpp
// star委托示例展示了如何创建一个可以自己绘制并支持编辑的委托。#include "stardelegate.h"#include "stareditor.h"#include "starrating.h"#include <QApplication>#include <QTableWidget>// 用数据填充QTableWidgetvoid 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 },//! [1]{ "Three More Foxes", "Jazz", "Maynard Ferguson", 4 },{ "Sex Bomb", "Pop", "Tom Jones", 3 },{ "Barbie Girl", "Pop", "Aqua", 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;// 调用QVariant::fromValue将StarRating转换为QVariantitem3->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);}}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();}
七、示例结果

