Qt委托(Delegate)类技术详解
Qt 委托(Delegate)类概述
Qt的Delegate类用于在视图组件(如QListView、QTableView、QTreeView)中控制数据的显示和编辑方式。Delegate允许开发者自定义单元格的外观、编辑器创建和数据处理逻辑,常见子类包括QStyledItemDelegate和QItemDelegate。
主要负责:
- 控制数据项的显示方式
- 提供数据项的编辑功能
- 管理模型与视图之间的数据交互
委托类继承关系:
核心功能与使用场景
-
数据展示定制
重写paint()
方法可自定义单元格的绘制逻辑,例如显示进度条、图标组合或特殊格式文本。 -
数据编辑控制
通过createEditor()
、setEditorData()
和setModelData()
实现编辑器的创建、数据同步与回写。 -
交互增强
在editorEvent()
中处理鼠标/键盘事件,支持复杂交互如双击触发二级对话框。
常用子类对比
QStyledItemDelegate
- 推荐使用的默认委托,支持样式表(QSS)。
- 自动处理标准数据类型(如颜色、字体)的编辑控件。
- 示例代码(简单的星级评分委托):
#include <QStyledItemDelegate>
#include <QPainter>
class StarDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit StarDelegate(QObject *parent = nullptr)
: QStyledItemDelegate(parent) {}
// 重写绘制方法
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override {
if (index.data().canConvert<int>()) {
int rating = index.data().toInt();
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
// 绘制背景
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
}
// 绘制星星
QRect starRect = option.rect.adjusted(2, 2, -2, -2);
starRect.setWidth(starRect.height()); // 保持正方形
for (int i = 0; i < 5; ++i) {
if (i < rating) {
painter->setBrush(Qt::yellow);
painter->setPen(Qt::black);
} else {
painter->setBrush(Qt::NoBrush);
painter->setPen(Qt::gray);
}
painter->drawPolygon(createStar(starRect));
starRect.translate(starRect.width() + 2, 0);
}
painter->restore();
} else {
QStyledItemDelegate::paint(painter, option, index);
}
}
// 重写大小计算
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const override {
return QSize(5 * 20, 20); // 5颗星,每颗20px宽
}
private:
// 创建五角星形状
QPolygonF createStar(const QRect &rect) const {
QPolygonF star;
qreal radius = rect.height() / 2;
qreal centerX = rect.left() + radius;
qreal centerY = rect.top() + radius;
for (int i = 0; i < 5; ++i) {
qreal angle = 2 * M_PI * i / 5 - M_PI / 2;
star << QPointF(centerX + radius * cos(angle),
centerY + radius * sin(angle));
angle += M_PI / 5;
star << QPointF(centerX + radius * 0.5 * cos(angle),
centerY + radius * 0.5 * sin(angle));
}
return star;
}
};
QItemDelegate
- 旧版实现,不依赖样式系统。
- 需要手动处理更多绘图细节,适用于特殊兼容需求。
实现自定义委托的关键步骤
1. 继承QStyledItemDelegate
创建子类并重写必要方法:
class StarRatingDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
void paint(QPainter *painter, /*...*/) const override;
QSize sizeHint(/*...*/) const override;
QWidget *createEditor(/*...*/) const override;
};
2. 实现绘图逻辑
在paint()
中利用QPainter绘制自定义内容:
void StarRatingDelegate::paint(QPainter *painter, /*...*/) const {
int stars = index.data().toInt();
QRect rect = option.rect.adjusted(2, 2, -2, -2);
painter->drawRect(rect);
painter->fillRect(rect, QColor(stars * 50, 0, 0)); // 示例:根据值变色
}
3. 创建编辑器控件
返回适合数据类型的编辑组件(如QSpinBox、QComboBox):
QWidget *createEditor(QWidget *parent, /*...*/) const override {
QSpinBox *editor = new QSpinBox(parent);
editor->setRange(0, 5);
return editor;
}
4. 数据同步处理
双向绑定模型与编辑器数据:
void setEditorData(QWidget *editor, /*...*/) const override {
QSpinBox *box = static_cast<QSpinBox*>(editor);
box->setValue(index.data().toInt());
}
void setModelData(QWidget *editor, /*...*/) const override {
QSpinBox *box = static_cast<QSpinBox*>(editor);
model->setData(index, box->value());
}
高级应用技巧
动态样式调整
通过initStyleOption()
修改基础样式选项,再调用父类绘制:
void paint(QPainter *painter, /*...*/) const override {
QStyleOptionViewItem opt = option;
if (index.row() % 2 == 0)
opt.font.setBold(true);
QStyledItemDelegate::paint(paint, opt, index);
}
编辑器事件过滤
在eventFilter()
中捕获编辑器特定事件(如回车确认编辑)。
性能优化
对于大型数据集,避免在paint()
中进行复杂计算,优先使用模型角色(如Qt::DecorationRole)预处理数据。
条件委托
class ConditionalDelegate : public QStyledItemDelegate {
public:
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const override {
if (index.column() == 0) { // 第一列使用QLineEdit
return new QLineEdit(parent);
}
else if (index.data().canConvert<double>()) { // 数值使用QSpinBox
QSpinBox *editor = new QSpinBox(parent);
editor->setRange(0, 100);
return editor;
}
return QStyledItemDelegate::createEditor(parent, option, index);
}
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const override {
if (index.column() == 0 && qobject_cast<QLineEdit*>(editor)) {
model->setData(index, static_cast<QLineEdit*>(editor)->text());
}
else if (qobject_cast<QSpinBox*>(editor)) {
model->setData(index, static_cast<QSpinBox*>(editor)->value());
}
else {
QStyledItemDelegate::setModelData(editor, model, index);
}
}
};
委托开发的最佳实践
1、性能优化:
- 在paint()方法中保存和恢复painter状态
- 避免在paint()中创建临时对象
- 对于复杂绘制,考虑缓存绘制结果
2、可访问性:
- 确保委托支持高DPI显示
- 考虑颜色对比度以满足可访问性要求
3、一致性:
- 继承QStyledItemDelegate而不是QAbstractItemDelegate
- 使用QStyle绘制标准UI元素
4、编辑体验:
- 在编辑器中使用合适的验证器
- 提供清晰的编辑反馈
委托开发实战演示
1、效果展示
2、开发流程
a、新建工程
b、添加MStarDelegate
类
.hpp
#ifndef MSTARDELEGATE_H
#define MSTARDELEGATE_H
#include <QObject>
#include <QStyledItemDelegate>
#include <QPainter>
#include <QSpinBox>
class MStarDelegate: public QStyledItemDelegate
{
Q_OBJECT
public:
MStarDelegate(QObject *parent = nullptr);
void paint(QPainter *painter, 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;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
};
#endif // MSTARDELEGATE_H
.cpp
#include "mstardelegate.h"
MStarDelegate::MStarDelegate(QObject *parent):
QStyledItemDelegate(parent)
{
}
void MStarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QPointF vertex[5]; /* 五角星 Size=20,20 */
for (int i = 0; i < 5; i++)
{
vertex[i].setX(+10 * sinf(i * 144.0f * (3.1415926535f / 180.0f)));
vertex[i].setY(-10 * cosf(i * 144.0f * (3.1415926535f / 180.0f)));
}
int many = index.data(Qt::DisplayRole).toInt();
painter->setPen(Qt::NoPen);
painter->setBrush(QColor(255, 170, 0));
QSize iconSize = option.decorationSize;
QRect thisRect = option.rect;
int y = thisRect.y() + thisRect.height() / 2;
for (int i = 0; i < many; i++)
{
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
qreal left = 2 + iconSize.width() * 0.5 + (2 + iconSize.width()) * i;
painter->translate(left, y);
painter->scale(iconSize.width() / 20.0, iconSize.height() / 20.0);
painter->drawPolygon(vertex, 5, Qt::WindingFill);
painter->restore();
}
// if (option.state & QStyle::State_Selected)
// {
// painter->fillRect(option.rect, option.palette.highlight());
// }
}
QWidget *MStarDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSpinBox* cbBox = new QSpinBox(parent);
cbBox->setRange(0, 5);
cbBox->setSuffix(u8"星");
return cbBox;
}
void MStarDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QSpinBox* cbWidget = dynamic_cast<QSpinBox*>(editor);
cbWidget->setValue(index.data(Qt::DisplayRole).toInt());
}
void MStarDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
int level = dynamic_cast<QSpinBox*>(editor)->value();
model->setData(index, level, Qt::DisplayRole);
}
void MStarDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry(option.rect);
}
c、工程结构
d、放置TableView
e、设置委托
this->ui->tableView->setItemDelegateForColumn(0, new MStarDelegate(this->ui->tableView));
QStandardItemModel* model = new QStandardItemModel(this->ui->tableView);
model->setHorizontalHeaderLabels({ u8"点数", u8"状态" });
model->setVerticalHeaderLabels({ "哪吒", "孙悟空", "猪八戒", "唐僧" });
model->setColumnCount(2);
model->setRowCount(4);
for (int i = 0; i < 4; i++)
{
model->setItem(i, 0, new QStandardItem(QString::number(i)));
model->setItem(i, 1, new QStandardItem(QString("剩余%1点血").arg(i)));
}
this->ui->tableView->setModel(model);