在QListView、QTableView或QTreeView 中显示数据时,各个item由委托绘制。此外,当用户开始编辑一个item时(例如,通过双击该item),代理会提供一个编辑器控件,该控件在进行编辑时放置在该item的顶部。提供展示(presentation)和编辑(edit)服务的组件称为委托。
委托是QAbstractItemDelegate 的子类,Qt提供的QStyledItemDelegate可以处理最常见的数据类型,如int和QString。
当我们想使用一个与众不同的编辑器,或者想把数据显示为图形,我们就可能需要使用委托。
在此示例中,我们将看到如何实现自定义委托来呈现和编辑“星级”数据类型,该数据类型可以存储诸如“5 星中的 2 颗”之类的值。
该示例由以下类组成:
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的虚函数,以提供自定义渲染和编辑
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);
}
}
每当视图需要重新绘制item时,都会调用paint()。如果 item 中存储的数据是StarRating,我们自己绘制它;否则,我们让QStyledItemDelegate为我们绘制。这确保了StarDelegate可以处理最常见的数据类型。
option.state & QStyle::State_Selected语句表示当星级评价的item处于被选中的状态时,会绘制被选中的高亮背景(和其他被选中的item一样)。
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);
}
当用户开始编辑item时会调用 createEditor() 函数。commitAndCloseEditor实现如下:
void StarDelegate::commitAndCloseEditor()
{
StarEditor *editor = qobject_cast<StarEditor *>(sender());
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);
}
}
编辑完成后,调用 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() 函数返回item的首选大小.
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);
}
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;
};
StarEditor::StarEditor(QWidget *parent)
: QWidget(parent)
{
setMouseTracking(true);
setAutoFillBackground(true);
}
我们打开QWidget的自动填充背景功能以获得不透明背景。(如果没有调用,视图的背景会穿透到编辑器中。)
void StarEditor::paintEvent(QPaintEvent *)
{
QPainter painter(this);
myStarRating.paint(&painter, rect(), palette(),
StarRating::EditMode::Editable);
}
void StarEditor::mouseMoveEvent(QMouseEvent *event)
{
const int star = starAtPosition(event->x());
if (star != myStarRating.starCount() && star != -1) {
myStarRating.setStarCount(star);
update();
}
QWidget::mouseMoveEvent(event);
}
我们调用 QWidget::update() 来强制重绘。
void StarEditor::mouseReleaseEvent(QMouseEvent *event)
{
emit editingFinished();
QWidget::mouseReleaseEvent(event);
}
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;
}
根据鼠标x轴坐标计算星级。后面的判断语句是当x坐标为负数,或者计算的星级大于最大星级时返回-1,此时StarEditor::mouseMoveEvent内只会调用QWidget::mouseMoveEvent。
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类表示评分星级,包含当前星级和最大星级,此外它还能绘制星星。
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);
}
设置星级,初始化用于绘制星星(starPolygon)和菱形(diamondPolygon)的多边形。
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();
}
其中PaintingScaleFactor是个常数,源码中为20,用于控制星星显示的大小。
QSize StarRating::sizeHint() const
{
return PaintingScaleFactor * QSize(myMaxStarCount, 1);
}
返回星星绘制区域的大小
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();
}
主函数创建了一个QTableWidget,设置委托为StarDelegate,并设置了编辑触发的方式
populateTableWidget填充表格,用于demo效果展示。
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);
}
}
如果想要自定义item的样式,可以通过视图的setItemDelegate()方法,使用自定义委托替换默认委托。通过创建一个继承自QStyledItemDelegate的类来编写自定义委托,对没有编辑功能的item,我们只需重写两个方法:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
。绘制itemQSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index)
。告知item的大小如果要提供item编辑能力,还需要重写以下方法:
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index)
。提供编辑器void setEditorData(QWidget *editor, const QModelIndex &index)
。初始化编辑器内数据void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index)
。将编辑器内数据保存到model中参考链接: