1、需求分析
在QStandardItemModel + QItemSelectionModel + QTableView组件的基础下实现了一个类似如下的表格:
为了防止人为输入数据导致错误,能否实现下面类似这种输入方式(对每个item项进行控制):
![]() | ![]() |
![]() | ![]() |
也就是修改生日时候弹出一个日历控件,岗位可以有一个下拉列表来选择,性别一样还带图标,薪资使用spinBox来控制。这就涉及到了自定义代理了。
2、自定义代理的概念
官方概念:指通过继承QStyledItemDelegate
或QAbstractItemDelegate
类来创建自定义的项渲染和编辑方式。
个人理解就是:
步骤2:将tableView的item设置为自定义组件,代码一般是:ui->tableView->setItemDelegateForColumn(1, &genderDelegate); 这里的genderDelegate是自定义类的对象,这个类里面会创建一个QComboBox组件的指针。这句代码也很容易理解就是给下标1的列(性别)设置一个代理,代理对象就是我们自定义类创建的一个实例。
步骤1:就是从Model中获取数据,然后用这数据设置自己的代理对象。这个通常改写父类的setEditorData方法实现。
步骤1+2 :就能实现从Model数据传给代理然后,tableView再以代理组件方式进行显示了。
步骤3:用户输入数据了,会改变代理的数据。
步骤4:把代理组件中用户输入的数据传递给Model,这样就能保证Model和TableView进行同步了。这个通常改写setModelData方法实现。
步骤3+4:就能实现从用户数据传给代理,然后代理再传给Model,达到同步数据的效果。
总结就是:代理组件就是一个中间介,其目的是为了控制显示效果,和对数据格式进行控制限定等。
3、一个简单代理组件的代码实现
先看效果:
代码如下:
//qsalarydelegate.h (自定义类的头文件)
#ifndef QSALARYDELEGATE_H
#define QSALARYDELEGATE_H
#include <QStyledItemDelegate>
//继承QStyledItemDelegate
class QSalaryDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
QSalaryDelegate(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;
};
#endif // QSALARYDELEGATE_H
//qsalarydelegate.cpp
#include "qsalarydelegate.h"
#include <QSpinBox>
QSalaryDelegate::QSalaryDelegate(QObject *parent):QStyledItemDelegate(parent)
{
}
QWidget *QSalaryDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
//用来创建一个代理组件给薪资一列使用,并返回其指针
QSpinBox *spinBox = new QSpinBox(parent);
//给这个组件设置进行一些设置
spinBox->setMaximum(100000); //最大值
spinBox->setMinimum(8000); //最小值
spinBox->setSingleStep(100); //单步长
return spinBox; //返回组件指针
}
void QSalaryDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
//把model的数据读取到组件上,然后再通过组件和tableView的item进行关联显示
//这里是把model的读取到组件上
/*
代码解释:
1、model()->data(index)返回的是一个QVariant类型,它是Qt中的一个万能容器类,它可以存储多种不同类型的值。
不同的Model可能需要返回不同类型的数据,QVariant提供了一个统一的返回类型,可以进行类型转换得到数据。
2、index.model()获取与该索引关联的数据模型。
3、spinBox可以直接设置int,所以提取到的数据直接转为int。
*/
int value = index.model()->data(index).toInt();
/*
代码解释:
1、QWidget *editor类型是无法设置组件的数据的,因为editor
2、static_cast<QSpinBox*>(editor)是将QWidget *类型转为QSpinBox *类型,一般来说是子类转父类,
这里可以从父类转到子类是因为上面返回的editer指针是指向子类(QSpinBox *)的,所以可以转换。
*/
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
//设置组件值,指针传递所以可以更改对象的值
spinBox->setValue(value);
}
void QSalaryDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
//这里是把用户输入到spinbox的值设置到model里面去
//获取spinbox的值,同样还是需要强转一次
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
int value = spinBox->value();
//设置model的数据
model->setData(index, value);
}
void QSalaryDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
//设置组件的位置
/*
将编辑器精确定位到:
位置:与表格/列表中的当前项相同的位置
大小:与表格/列表中的当前项相同的大小
*/
editor->setGeometry(option.rect);
}
//mainwindow.h
QSalaryDelegate salaryDelegate; //MainWindow类内定义一个QSalaryDelegate对象
//mainwindow.cpp
ui->tableView->setItemDelegateForColumn(4, &salaryDelegate); //自定义组件对象和视图的item关联
总结:自定义一个QWiget类,它继承QStyledItemDelegate,
然后分别在这个类里面实现构建一个组件指针,设置改组件指针格式、把Model的数据取到改组件指针中,把用户输入的数据通过改组件指针来设置Model,最后再设置一下组件的位置大小。这就是一个自定义代理组件的大概框架,然后其他一些复杂的东西,你就可以通过折腾你构建的组件指针来达到你想要的目的。
最后再加一句:QTableView默认代理组件是QLineEdit。
4、使用QComboBox组件作为代理组件实现下拉框和图标
上面代理组件简单的原因是没有给QSpinBox设置原始数据和其他图标之类的,但是总的步骤是一样的,只不过是增加了对自定义组件的数据增加了处理。
先看效果:
代码如下(提示如果有调试不出来的可以先清理所有项目再重新构建项目,再运行):
/********************qgenderdelegate.h文件**********************************/
#ifndef QGENDERDELEGATE_H
#define QGENDERDELEGATE_H
#include <QStyledItemDelegate>
#include <QStandardItemModel>
class QGenderDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
QGenderDelegate(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;
void paint(QPainter *painter,
const QStyleOptionViewItem &option, const QModelIndex &index) const override;
private:
};
#endif // QGENDERDELEGATE_H
/********************qgenderdelegate.cpp文件**********************************/
#include "qgenderdelegate.h"
#include <QComboBox>
#include <QPainter> // 提供绘图功能(QPainter类)
#include <QStyleOptionViewItem> // 提供绘图选项参数
#include <QPalette> // 提供颜色调色板
#include <QIcon> // 提供图标功能
#include <QRect> // 提供矩形区域定义
#include <QApplication>
QGenderDelegate::QGenderDelegate(QObject *parent):QStyledItemDelegate(parent)
{
}
QWidget *QGenderDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
//没有使用的参数,进行说明
Q_UNUSED(option);
Q_UNUSED(index);
//新建一个下拉框组件指针作为editor, 同时对其进行设置, 并返回
QComboBox *editor = new QComboBox(parent);
//给这个组件添加相应的item,这样你点击这个下拉框就能看到这两个item了
editor->addItem(QIcon(":/source/images/boy.png"), "男");
editor->addItem(QIcon(":/source/images/girl.png"), "女");
return editor;
}
void QGenderDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
//从model里面获取数据,再通过editor设置显示到tableView中
//这里的index是属于model的不是属于代理组件的
QString value = index.data(Qt::DecorationRole).toString();
//强行转换,获取下拉列表组件的指针
QComboBox *genderCombox = static_cast<QComboBox *>(editor);
//给这个组件设置从model获取的值
genderCombox->setCurrentText(value);
}
void QGenderDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
//从代理组件中获取数据设置到model中
QComboBox *genderCombox = static_cast<QComboBox *>(editor);
QString selectedText = genderCombox->currentText();
//保存到model中去
model->setData(index, selectedText, Qt::EditRole);
}
void QGenderDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(editor);
Q_UNUSED(index);
editor->setGeometry(option.rect);
}
/*
用户双击性别单元格
委托调用createEditor创建QComboBox
setEditorData被调用,将当前值"男"加载到下拉框
用户选择"女"
用户按Enter确认
setModelData被调用,将"女"保存到模型
模型发出dataChanged信号
视图调用paint()更新显示
*/
void QGenderDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
/*
这里算是一个新重点!!!
1、paint函数的作用是对自定义控件的渲染
需要的头文件:
#include <QPainter> // 提供绘图功能(QPainter类)
#include <QStyleOptionViewItem> // 提供绘图选项参数
#include <QPalette> // 提供颜色调色板
#include <QIcon> // 提供图标功能
#include <QRect> // 提供矩形区域定义
2、paint方法也是继承QStyledItemDelegate的方法。其原理是:用户双击性别单元格,
委托调用createEditor创建QComboBox,setEditorData被调用,将之前添加的"男"、"女"加载到
下拉框,假如用户选择"女"后按Enter确认(或者双击)后,setModelData会被调用,将"女"保存到model,
model发出dataChanged信号,视图收到这个信号后调用paint()更新显示。
*/
//获取model中获取存储的文本
QString text = index.data(Qt::EditRole).toString();
//根据text创建icon
QIcon icon = (text == "男") ? QIcon(":/source/images/boy.png"): QIcon(":/source/images/girl.png");
//绘制背景
//检查单元格是否被选中, 如果选中:使用高亮颜色填充背景, 如果未选中:使用基础颜色填充背景
if(option.state & QStyle::State_Selected){
painter->fillRect(option.rect, option.palette.highlight());
}
else{
painter->fillRect(option.rect, option.palette.base());
}
//绘制图标部分
//创建一个正方形区域 (高度和宽度相同),在这个区域内绘制图标 (自动处理图标的缩放和居中)
QRect iconRect = option.rect;
iconRect.setWidth(iconRect.height());
icon.paint(painter, iconRect);
//绘制文本部分
//文本区域从图标右侧开始,留4像素间距,根据选中状态设置文本颜色 (选中时用高亮文本色)
//垂直居中绘制文本
QRect textRect = option.rect;
textRect.setLeft(iconRect.right() + 8);
painter->setPen(option.state & QStyle::State_Selected ?
option.palette.highlightedText().color() :
option.palette.text().color());
painter->drawText(textRect, Qt::AlignVCenter, text);
}
/*************************mainwindow.h 文件*******************************/
QGenderDelegate genderDelegate; //给mainwindow类增加一个属性
/*************************mainwindow.cpp 文件*******************************/
ui->tableView->setItemDelegateForColumn(1, &genderDelegate); //进行关联
总结:这个自定义代理下拉框组件和上面的SpinBox组件构建方式还是一样的,只不过在它的基础上增加了一个对应的图标,这里面关键的部分是这个图标并不会加载到model和下拉组件中去,而是调用了point方法,point方法会根据当前model的值是"男"还是"女"选择相应的图标进行渲染。这上面还有一个小瑕疵,就是因为下拉框默认的顺序是"男"在第一列、"女"在第二列,所以在当前单元格为"女"的时候双击会直接变成"男"给人一种突兀感。这个问题想要修改的可自行探索。
5、使用QDateEdit组件作为代理组件
先看效果:
代码如下:
/************************qdateeditdelegate.h 文件******************************/
#ifndef QDATEEDITDELEGATE_H
#define QDATEEDITDELEGATE_H
#include <QStyledItemDelegate>
class QDateEditDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
QDateEditDelegate(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;
};
#endif // QDATEEDITDELEGATE_H
/************************qdateeditdelegate.cpp 文件******************************/
#include "qdateeditdelegate.h"
#include <QDateEdit>
QDateEditDelegate::QDateEditDelegate(QObject *parent):QStyledItemDelegate(parent)
{
}
QWidget *QDateEditDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QDateEdit *dateEdit = new QDateEdit(parent);
//设置日期范围
dateEdit->setMinimumDate(QDate(1900, 1, 1));
dateEdit->setMaximumDate(QDate(2029, 8, 5));
//开启日历控件
dateEdit->setCalendarPopup(true);
//给日期编辑组件设置为当前日期,数据来源于model
//这样表格里面是什么日期,双击修改的时候开始显示的也是这个日期增加用户体验
QString value = index.data().toString();
QDate date = QDate::fromString(value, "yyyy-M-dd");
dateEdit->setDate(date);
return dateEdit;
}
void QDateEditDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
//从model获取数据传给editor
QString value = index.data().toString();
QDate date = QDate::fromString(value, "yyyy-M-dd");
QDateEdit *dateEdit = static_cast<QDateEdit *>(editor);
dateEdit->setDate(date);
}
void QDateEditDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
//从editor获取数据给model
QDateEdit *dateEdit = static_cast<QDateEdit *>(editor);
QDate date = dateEdit->date();
//因为没有重写point所以model填入怎么格式的数据在tableView中就会怎么显示
QString value = date.toString("yyyy-M-dd");
model->setData(index, value);
}
void QDateEditDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry(option.rect);
}
/*************************mainwindow.h 文件*******************************/
QDateEditDelegate dateEditDelegate; //给mainwindow类增加一个属性
/*************************mainwindow.cpp 文件*******************************/
ui->tableView->setItemDelegateForColumn(2, &dateEditDelegate); //进行关联
以上就是为tableView的item增加一个自定义日历组件的代理了。