星号代理示例
星号代理示例展示了如何创建一个可以自行绘制且支持编辑的代理。
在QListView、QTableView或QTreeView中显示数据时,单个项目是由代理绘制的。此外,当用户开始编辑项目(例如通过双击项目)时,代理提供编辑小部件,并在编辑期间将其放置在项目上方。
代理是QAbstractItemDelegate的子类。Qt提供了继承自QAbstractItemDelegate的QStyledItemDelegate,它处理最常见的数据类型(特别是int
和QString)。如果我们需要支持自定义数据类型,或者想要定制现有数据类型的渲染或编辑,我们可以继承QAbstractItemDelegate或QStyledItemDelegate。有关代理的更多信息,请参阅代理类,如果您需要关于Qt的模型/视图架构(包括代理)的高级介绍,请参阅模型/视图编程。
在本示例中,我们将看到如何实现一个自定义代理以渲染和编辑“星号评分”数据类型,该数据类型可以存储诸如“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()
绘制项目,我们将在后面讨论这个函数。
由于出现在 starrating.h
中的宏 Q_DECLARE_METATYPE(),StartRating
可以存储在 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); }
我们在小部件上启用 鼠标跟踪,这样我们可以在用户不按任何鼠标按钮时跟踪光标。我们还打开 QWidget 的 自动填充背景 功能以获得不透明的背景。(如果不调用这个函数,视图的背景将通过编辑器透过来。)
重新实现了从 QWidget 的 paintEvent() 函数
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()
使用基本的线性代数来确定鼠标指针下面的哪颗星星。
星评级类定义
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()
宏使得类型 StarRating
被认识为 QVariant,使得能够在 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 枚举提供的更多的编辑触发器。例如,在星星代理示例中,用鼠标悬停在项上可能是一种弹出编辑器的方式。
- 通过重新实现 QAbstractItemDelegate::editorEvent(),可以在代理中直接实现编辑器,而不是创建一个单独的 QWidget 子类。
© 2024 The Qt Company Ltd。本文件中包含的文档贡献均属于各自所有者的版权。提供的文档依据自由软件基金会发布的GNU自由文档许可协议版本1.3进行许可。Qt及其相关标志是芬兰及全球其他国家的The Qt Company Ltd.的商标。所有其他商标均属于各自所有者。