QT编程(19) : Qt 实现自定义delegate

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显示样式)

  1. paint():最核心的绘制函数,负责绘制item的全部内容(文字、背景、自定义控件等),所有自定义样式都需在此实现。

函数原型:void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;

参数说明:

复制代码
- painter:绘图工具,用于绘制各种图形、文字;

- option:item的样式选项(如item的矩形区域、状态:选中/未选中/编辑中、字体、颜色等);

- index:当前item的模型索引,通过index.data()获取模型中的数据。
  1. sizeHint():设置item的默认大小(宽高),若不重写,将使用View的默认大小。

函数原型:QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;

2.2 编辑相关(控制item编辑逻辑)

  1. createEditor():创建自定义编辑控件(如QSpinBox、QComboBox),当item进入编辑状态时调用。

函数原型:QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;

  1. setEditorData():将模型中的数据(index.data())设置到编辑控件中(初始化编辑控件)。

函数原型:void setEditorData(QWidget *editor, const QModelIndex &index) const override;

  1. setModelData():将编辑控件中修改后的数据,同步回模型中(保存编辑结果)。

函数原型:void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;

  1. 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的兼容性,避免绘制内容与样式表冲突。

五、扩展场景(常见自定义需求)

  1. 自定义复选框:重写paint()绘制自定义样式的复选框,重写editorEvent()处理点击事件(无需创建编辑控件)。

  2. 自定义下拉框:在createEditor()中返回QComboBox,设置下拉选项,实现下拉选择编辑。

  3. 文字样式自定义:在paint()中设置不同的字体、颜色、对齐方式,甚至绘制带图标的文字。

  4. 复杂控件嵌套:在paint()中绘制多个控件(如进度条+按钮),通过editorEvent()处理控件交互。

六、常见问题排查

  • 问题1:item不显示自定义内容 → 检查paint()函数是否重写,是否正确获取index.data(),是否设置了正确的绘制区域。

  • 问题2:编辑控件不显示 → 检查createEditor()是否返回有效控件,updateEditorGeometry()是否设置了正确的位置。

  • 问题3:编辑后数据不保存 → 检查setModelData()是否正确将编辑控件的数据同步到模型,是否使用了正确的角色(如Qt::DisplayRole)。

  • 问题4:样式错乱 → 检查画笔状态是否正确保存/恢复,是否与QSS冲突,sizeHint()是否设置合理。

相关推荐
AI科技星2 小时前
基于wr/c + h/c = 1的螺旋线矢量特性及应用分析
c语言·开发语言·人工智能·opencv·算法·计算机视觉·r语言
xiaomo22492 小时前
javaee-多线程进阶
java·开发语言
polaris06302 小时前
学生成绩管理系统(MySQL)
android·数据库·mysql
暮冬-  Gentle°2 小时前
用Python制作一个文字冒险游戏
jvm·数据库·python
无敌秋2 小时前
C++ public, private, protected类的继承
开发语言·c++
m0_579393662 小时前
C++代码混淆与保护
开发语言·c++·算法
qq_148115372 小时前
C++中的享元模式实战
开发语言·c++·算法
__Yvan2 小时前
Kotlin 的 ?.let{} ?: run{} 真的等价于 if-else 吗?
android·开发语言·前端·kotlin
左左右右左右摇晃2 小时前
Java并发——线程间的通信
java·开发语言