自定义代理

同学们也注意到了,我们实现模型和视图,但是显示的内容都是文字化的,有时候我想实现特定的显示,比如在双击某个单元格后显示的是一个编辑器,有时候是一个下拉列表,那该怎么办呢?
我们可以通过自定义代理实现,前面我们提到了三要素,其中Delegate就是用来管理数据显示和编辑的。
QStyledItemDelegate
QAbstractItemDelegate类是所有代理类的基类,我们不用关注,我们要实现自己的代理,需要继承的是QStyledItemDelegate
QStyledItemDelegate 是视图组件使用的默认代理类,一般使用 QStyledItemDelegate 作为自定 义代理类的父类。要自定义一个代理类,必须重新实现 QStyledItemDelegate 中定义的 4 个虚函数。 这 4 个函数是由模型/视图系统自动调用的。
**1.函数 createEditor() **
函数 createEditor()可创建用于编辑模型数据的界面组件,称为代理编辑器,例如 QSpinBox 组件,或 QComboBox 组件。其函数原型定义如下:

QWidget *QStyledItemDelegate::createEditor(QWidget *parent,  const QStyleOptionViewItem &option, const QModelIndex &index)  

其中,parent 是要创建的组件的父组件,一般就是窗口对象;

option 是项的一些显示选项,是 QStyleOptionViewItem 类型的,包含字体、对齐方式、背景色等属性;

index 是项在数据模型中的 模型索引,通过 index->model()可以获取项所属数据模型的对象指针。

在 QTableView 视图组件上双击一个单元格使其进入编辑状态时,系统就会自动调用 createEditor()创建代理编辑器,例如创建 QSpinBox 组件,然后将其显示在单元格里。

2.函数 setEditorData()

函数 setEditorData()的功能是从数据模型获取项的某个角色(一般是 EditRole 角色)的数据, 然后将其设置为代理编辑器上显示的数据。

其函数原型定义如下:

void QStyledItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index)

设置了自定义代理后 单元格处于编辑状态的效果 ,参数 editor 就是前面用函数 createEditor()创建的代理编辑器。

通过 index->model()可以获取项 所属数据模型的对象指针,从而获取项的数据,然后将其显示在代理编辑器上。

3.函数 setModelData()

完成对当前单元格的编辑,例如输入焦点移到其他单元格时,系统会自动调用函数 setModelData(), 其功能是将代理编辑器里的输入数据保存到数据模型的项里。

其函数原型定义如下:

void QStyledItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,  const QModelIndex &index) 

其中,editor 是代理编辑器,model 是数据模型,index 是所编辑的项在模型中的模型索引。

4.函数 updateEditorGeometry()

视图组件在界面上显示代理编辑器时,需要调用 updateEditorGeometry()函数为组件设置合适 的大小,例如在一个单元格里显示一个 QSpinBox 代理编辑器时,一般将其设置为单元格的大小。

其函数原型定义如下:

 void QStyledItemDelegate::updateEditorGeometry(QWidget *editor,  const QStyleOptionViewItem &option, const QModelIndex &index)  

其中,变量 option->rect 是 QRect 类型,表示代理编辑器的建议大小。一般将代理编辑器大小设 置为建议大小即可,即用下面的一行代码:

 editor->setGeometry(option.rect);

定制化显示和编辑案例

样式要求

1 要求实现自定义的代理,完成姓名,年龄,以及职业的编辑。

2 双击姓名进行编辑,弹出编辑框

3 双击年龄进行编辑,弹出spinbox

4 双击职业进行编辑,弹出下拉框

如下图:

开发讲解

1 声明自定义的代理类CustomDelegate

class CustomDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    //构造函数
    CustomDelegate(QObject *parent = nullptr);
    //创建编辑器
    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;
};

构造函数的参数模仿基类QStyledItemDelegate添加了parent参数,另外声明了实现代理的必备的四个成员函数

2 实现构造函数

CustomDelegate::CustomDelegate(QObject *parent):QStyledItemDelegate (parent)
{

}

因为基类需要parent参数来构造,所以子类调用了基类的构造函数。

3 创建编辑器

实现createEditor函数,目的是为了在用户双击不同的单元格时,弹出不同的控件。

//实现双击的时候创建不同的控件
QWidget *CustomDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                                      const QModelIndex &index) const
{
    //姓名在第0列,年龄在第1列,职业在第2列
    //获取index对应的列数
    auto column = index.column();
    if(column == 0){
        //双击name列的时候创建一个QLineEdit
        QLineEdit * editor = new QLineEdit(parent);
        //设置最大长度
        editor->setMaxLength(50);
        return editor;
    }

    //如果年龄这一列被点击
    if(column == 1){
        //创建spinbox管理年龄
        QSpinBox* spinBox = new QSpinBox(parent);
        //设置合理的年龄范围
        spinBox->setRange(0,150);
        return spinBox;
    }

    //如果点击的是职业这一列
    if(column == 2){
        //创建下拉框comboBox
        QComboBox* comboBox = new QComboBox(parent);
        //添加职业选项
        comboBox->addItems({"工程师","设计师","教师","学生","法师"});
        return comboBox;
    }

    //条件都不满足,调用基类默认的处理方式
    return QStyledItemDelegate::createEditor(parent,option,index);
}

根据列号 (index.column()),返回不同类型的编辑器:

  • 姓名(第0列)QLineEdit,用于输入文本。可以设置最大长度以限制输入。
  • 年龄(第1列)QSpinBox,用于输入数值,设置合理的范围(如0-150)。
  • 职业(第2列)QComboBox,提供下拉菜单供选择预定义的职业。

4 将模型数据设置到控件

为了将模型数据设置到控件,我们需要使用setEditorData函数, 同样根据不同的列采用不同的设置方式

//将模型的数据设置到editor中
void CustomDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    //根据index获取模型,再根据模型获取数据,
    //因为我们想获取可编辑类型,所以传递Qt::EditRole
    QVariant value = index.model()->data(index, Qt::EditRole);
    //获取列数
    auto column = index.column();
    //根据列数判断,如果列数为0则向编辑器中设置姓名数据
    if(column == 0){
        //需要将eitor由QWidget*转化为QLineEdit*
        auto * lineEdit = qobject_cast<QLineEdit*>(editor);
        if(lineEdit){
            //将名字设置到lineEdit里
            lineEdit->setText(value.toString());
        }
        return;
    }

    //如果是年龄列,则将数据设置到spinBox中
    if(column == 1){
        //将editor转为QSpinBox
        QSpinBox *spinBox = qobject_cast<QSpinBox*>(editor);
        if (spinBox) {
            //设置年龄
            spinBox->setValue(value.toInt());
        }
        return;
    }

    //如果是职业,则从下拉框寻找对应文本,并设置索引
    if(column == 2){
        //转化为下拉框
        QComboBox* comboBox = qobject_cast<QComboBox*>(editor);
        if(comboBox){
            int idx = comboBox->findText(value.toString());
            if(idx >= 0){
                //如果找到了就设置索引
                comboBox->setCurrentIndex(idx);
            }
        }

        return;
    }


    //都不满足,调用基类默认的函数
    QStyledItemDelegate::setEditorData(editor, index);
    return;
}

将模型中的数据设置到编辑器中:

  • 姓名:将文本设置到 QLineEdit
  • 年龄:将数值设置到 QSpinBox
  • 职业:将当前文本设置到 QComboBox 中的对应项。

5 将修改写入模型中

我们编辑完数据后,需要将修改写入模型中,此时采用setModelData即可

//将控件内容设置到model中
void CustomDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    //获取列数
    auto column = index.column();
    //设置姓名改变到model中
    if(column == 0){
        auto * lineEdit = dynamic_cast<QLineEdit*>(editor);
        if(lineEdit){
            //设置当前文本到model中
            model->setData(index,lineEdit->text(),Qt::EditRole);
        }
        return;
    }

    //设置年龄改变到model中
    if(column == 1){
        auto * spinBox = dynamic_cast<QSpinBox*>(editor);
        if(spinBox){
            //设置spinbox的值到model中
            model->setData(index,spinBox->value(),Qt::EditRole);
        }
       return;
    }

    //设置职业改变到model中
    if(column == 2){
        auto * comboBox = dynamic_cast<QComboBox*>(editor);
        if(comboBox){
            //设置下拉框文本到model中
            model->setData(index,comboBox->currentText(),Qt::EditRole);
        }
        return;
    }

    //都不满足,调用基类默认的设置
    QStyledItemDelegate::setModelData(editor,model,index);
}

将编辑器中的数据保存回模型:

  • 姓名:从 QLineEdit 获取文本并设置到模型中。
  • 年龄:从 QSpinBox 获取数值并设置到模型中。
  • 职业:从 QComboBox 获取当前选中的文本并设置到模型中。

6 更新编辑框区域

我们需要更新编辑区域的大小,防止区域过大或者过小,采用updateEditorGeometry函数

void CustomDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    //设置编辑器几何区域,使其不超过单元格大小
    editor->setGeometry(option.rect);
}

7 将代理应用到特定的列

MainWindow 的构造函数中,需要实例化 CustomDelegate 并将其应用到 QTableView 的特定列。可以选择应用到整个视图,也可以仅应用到特定的列。

因为我们在代理中判断了不同列的处理,所以我们将代理直接应用到整个视图即可,在mainwindow中添加

// 创建自定义代理
auto * custom_delegate = new CustomDelegate(this);
// 设置代理
tableView->setItemDelegate(custom_delegate);

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐