Qt 实现自定义Delegate(详细教程)
一、自定义Delegate核心概述
Qt中的Delegate(委托)是MVC(Model-View-Controller)架构中"视图-模型"之间的桥梁,主要负责数据的渲染(绘制)和编辑交互。默认情况下,Qt提供了QItemDelegate(Qt4)和QStyledItemDelegate(Qt5及以上,推荐使用),但当默认样式、编辑控件无法满足需求(如自定义进度条、复选框、下拉框样式,或自定义编辑逻辑)时,就需要实现自定义Delegate。
核心作用:
-
控制视图中每一个item的绘制样式(如文字颜色、背景、图标、自定义控件);
-
提供自定义的编辑控件(如替换默认的QLineEdit为QSpinBox、QComboBox等);
-
处理编辑过程中的交互逻辑(如输入验证、编辑状态切换)。
关键说明:自定义Delegate需继承QStyledItemDelegate(推荐)或QItemDelegate,重写核心虚函数,无需修改Model和View的代码,实现解耦,灵活性极高。
二、自定义Delegate核心虚函数(必重写/可选重写)
QStyledItemDelegate的核心虚函数分为"绘制相关"和"编辑相关"两大类,根据需求选择性重写,以下是最常用的函数:
2.1 绘制相关(控制item显示样式)
- paint():最核心的绘制函数,负责绘制item的全部内容(文字、背景、自定义控件等),所有自定义样式都需在此实现。
函数原型:void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
参数说明:
- painter:绘图工具,用于绘制各种图形、文字;
- option:item的样式选项(如item的矩形区域、状态:选中/未选中/编辑中、字体、颜色等);
- index:当前item的模型索引,通过index.data()获取模型中的数据。
- sizeHint():设置item的默认大小(宽高),若不重写,将使用View的默认大小。
函数原型:QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
2.2 编辑相关(控制item编辑逻辑)
- createEditor():创建自定义编辑控件(如QSpinBox、QComboBox),当item进入编辑状态时调用。
函数原型:QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
- setEditorData():将模型中的数据(index.data())设置到编辑控件中(初始化编辑控件)。
函数原型:void setEditorData(QWidget *editor, const QModelIndex &index) const override;
- setModelData():将编辑控件中修改后的数据,同步回模型中(保存编辑结果)。
函数原型:void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
- updateEditorGeometry():设置编辑控件的位置和大小(与item的矩形区域对齐),若不重写,编辑控件可能位置错乱。
函数原型:void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
三、自定义Delegate完整实现实例(实战)
以下实例实现一个"自定义进度条Delegate",用于在QTableView中显示进度(0-100),支持编辑(通过QSpinBox修改进度值),同时自定义进度条颜色、文字显示。
3.1 头文件(CustomProgressDelegate.h)
cpp
#ifndef CUSTOMPROGRESSDELEGATE_H
#define CUSTOMPROGRESSDELEGATE_H
#include <QStyledItemDelegate>
#include <QPainter>
#include <QSpinBox>
class CustomProgressDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit CustomProgressDelegate(QObject *parent = nullptr);
// 重写绘制函数:绘制进度条和文字
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
// 重写item大小:设置进度条高度
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
// 重写编辑相关函数:使用QSpinBox编辑进度值
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 // CUSTOMPROGRESSDELEGATE_H
3.2 源文件(CustomProgressDelegate.cpp)
cpp
#include "CustomProgressDelegate.h"
CustomProgressDelegate::CustomProgressDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
// 绘制进度条和文字
void CustomProgressDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
// 1. 获取模型中的进度值(确保是int类型,0-100)
int progress = index.data(Qt::DisplayRole).toInt();
progress = qBound(0, progress, 100); // 限制进度在0-100之间
// 2. 获取item的矩形区域(用于绘制进度条)
QRect rect = option.rect;
rect.adjust(2, 2, -2, -2); // 向内缩进2px,避免与item边框重叠
// 3. 绘制背景(未填充部分)
painter->save(); // 保存画笔状态,避免影响后续绘制
QPen pen(Qt::lightGray, 1);
painter->setPen(pen);
painter->setBrush(Qt::white);
painter->drawRect(rect); // 绘制白色背景矩形
// 4. 绘制进度条(填充部分)
QRect progressRect = rect;
progressRect.setWidth(rect.width() * progress / 100); // 进度宽度按比例计算
// 根据进度设置不同颜色(0-30红色,31-70黄色,71-100绿色)
if (progress <= 30) {
painter->setBrush(QColor(255, 100, 100));
} else if (progress <= 70) {
painter->setBrush(QColor(255, 200, 100));
} else {
painter->setBrush(QColor(100, 255, 100));
}
painter->drawRect(progressRect); // 绘制进度填充部分
// 5. 绘制进度文字(居中显示)
painter->setPen(Qt::black);
painter->setFont(option.font);
QString text = QString("%1%").arg(progress);
QTextOption textOption(Qt::AlignCenter);
painter->drawText(rect, text, textOption); // 文字居中显示在进度条上
// 6. 绘制选中状态(当item被选中时,绘制边框)
if (option.state & QStyle::State_Selected) {
painter->setPen(QPen(Qt::blue, 2));
painter->setBrush(Qt::NoBrush);
painter->drawRect(option.rect); // 绘制选中边框
}
painter->restore(); // 恢复画笔状态
}
// 设置item大小:高度25px,宽度随View自适应
QSize CustomProgressDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(option);
Q_UNUSED(index);
return QSize(option.rect.width(), 25); // 固定高度25px
}
// 创建编辑控件:QSpinBox(用于修改进度值)
QWidget *CustomProgressDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(option);
Q_UNUSED(index);
QSpinBox *spinBox = new QSpinBox(parent);
spinBox->setRange(0, 100); // 进度范围0-100
spinBox->setAlignment(Qt::AlignCenter); // 文字居中
return spinBox;
}
// 将模型数据设置到编辑控件(初始化SpinBox)
void CustomProgressDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
int progress = index.data(Qt::DisplayRole).toInt();
QSpinBox *spinBox = qobject_cast<QSpinBox*>(editor);
if (spinBox) {
spinBox->setValue(progress);
}
}
// 将编辑控件的数据同步回模型
void CustomProgressDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
QSpinBox *spinBox = qobject_cast<QSpinBox*>(editor);
if (spinBox) {
int progress = spinBox->value();
model->setData(index, progress, Qt::DisplayRole); // 同步到模型
}
}
// 设置编辑控件的位置和大小(与item对齐)
void CustomProgressDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
editor->setGeometry(option.rect); // 编辑控件与item矩形完全重合
}
3.3 使用自定义Delegate(主函数/窗口代码)
将自定义Delegate设置到QTableView中,结合QStandardItemModel使用,示例如下:
cpp
#include <QApplication>
#include <QTableView>
#include <QStandardItemModel>
#include "CustomProgressDelegate.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 1. 创建模型(QStandardItemModel)
QStandardItemModel *model = new QStandardItemModel(5, 2); // 5行2列
model->setHorizontalHeaderItem(0, new QStandardItem("任务名称"));
model->setHorizontalHeaderItem(1, new QStandardItem("完成进度"));
// 2. 给模型设置测试数据
QStringList tasks = {"任务1", "任务2", "任务3", "任务4", "任务5"};
QList<int> progresses = {25, 50, 75, 90, 30};
for (int i = 0; i < 5; ++i) {
model->setItem(i, 0, new QStandardItem(tasks[i]));
model->setItem(i, 1, new QStandardItem(QString::number(progresses[i])));
}
// 3. 创建视图(QTableView)
QTableView *view = new QTableView;
view->setModel(model);
view->horizontalHeader()->setStretchLastSection(true); // 最后一列自适应宽度
// 4. 设置自定义Delegate(仅对第2列生效,即进度列)
CustomProgressDelegate *delegate = new CustomProgressDelegate;
view->setItemDelegateForColumn(1, delegate); // 第2列使用自定义Delegate
// 5. 显示视图
view->show();
return a.exec();
}
四、关键注意事项
-
继承选择:Qt5及以上优先继承QStyledItemDelegate,它支持Qt的样式表(QSS),与QStyle融合更好;QItemDelegate不支持样式表,仅兼容Qt4代码。
-
画笔状态:在paint()函数中,使用painter->save()和painter->restore()保存/恢复画笔状态,避免绘制时相互干扰(如颜色、字体错乱)。
-
数据类型:通过index.data()获取模型数据时,需明确数据类型(如Qt::DisplayRole、Qt::UserRole),避免类型转换错误。
-
编辑控件释放:createEditor()返回的控件,Qt会自动管理生命周期,无需手动delete,避免内存泄漏。
-
多列适配:若需给不同列设置不同Delegate,使用setItemDelegateForColumn();若给所有列设置统一Delegate,使用setItemDelegate()。
-
样式表兼容:若使用QSS设置View样式,自定义Delegate的paint()函数需考虑与QSS的兼容性,避免绘制内容与样式表冲突。
五、扩展场景(常见自定义需求)
-
自定义复选框:重写paint()绘制自定义样式的复选框,重写editorEvent()处理点击事件(无需创建编辑控件)。
-
自定义下拉框:在createEditor()中返回QComboBox,设置下拉选项,实现下拉选择编辑。
-
文字样式自定义:在paint()中设置不同的字体、颜色、对齐方式,甚至绘制带图标的文字。
-
复杂控件嵌套:在paint()中绘制多个控件(如进度条+按钮),通过editorEvent()处理控件交互。
六、常见问题排查
-
问题1:item不显示自定义内容 → 检查paint()函数是否重写,是否正确获取index.data(),是否设置了正确的绘制区域。
-
问题2:编辑控件不显示 → 检查createEditor()是否返回有效控件,updateEditorGeometry()是否设置了正确的位置。
-
问题3:编辑后数据不保存 → 检查setModelData()是否正确将编辑控件的数据同步到模型,是否使用了正确的角色(如Qt::DisplayRole)。
-
问题4:样式错乱 → 检查画笔状态是否正确保存/恢复,是否与QSS冲突,sizeHint()是否设置合理。