32、拓展知识---------Model/View模型视图
拓展知识
工作中为什么使用自定义模型
使用 QStandardItemModel 虽然方便,但当你的数据量较大或者需要更精细控制数据行为时,性能和灵活性可能会受到限制。通过创建自定义模型,你可以:
●优化性能:仅实现你需要的方法,减少不必要的开销。
●更好地控制数据:定义专门的数据结构,更符合业务需求。
●扩展功能:实现特殊的数据处理或数据验证逻辑。
自定义模型注意事项
在 Qt 中,如果你有一个自定义的模型,并且需要手动管理数据的插入和删除,那么你确实需要在相应的方法中调用 beginInsertRows 和 endInsertRows 以插入新行,调用 beginRemoveRows 和 endRemoveRows 以删除行。这些函数的作用是通知视图(如 QTableView 或 QListView)即将发生的数据变化,确保视图能够正确更新。
为什么系统模型不需要手动调用这些方法?
当使用 Qt 提供的系统模型(如 QStringListModel、QStandardItemModel 等)时,这些模型已经实现了内部的管理机制。它们在添加、删除或更新数据时会自动调用 beginInsertRows、endInsertRows、beginRemoveRows 和 endRemoveRows。因此,用户不需要手动干预,只需调用相应的方法(如 insertRow()、removeRow() 等),系统模型会自动处理这些通知。
在 Qt 中,自定义模型类时,通常需要继承 QAbstractItemModel 类,并实现一些纯虚函数,以定义数据模型的行为。根据 Qt 的文档,子类化模型需要实现的函数可以分为以下几类:
自定义模型需要实现的核心函数
当你创建一个自定义模型,通常会继承自 QAbstractTableModel 或 QAbstractListModel。对于表格数据(如姓名、年龄、职业),QAbstractTableModel 是更合适的选择。以下是创建自定义模型时通常需要实现的核心函数:
1 必须实现的纯虚函数
这些函数是 QAbstractTableModel 的纯虚函数,必须在自定义模型中实现:
rowCount: 返回模型中的行数。columnCount: 返回模型中的列数。data: 根据给定的QModelIndex和角色(例如显示、编辑)返回对应的数据。
2 推荐实现的函数
为了使模型支持编辑、插入、删除等功能,还应实现以下函数:
setData: 允许模型的数据被修改。flags: 设置每个单元格的属性(如是否可编辑)。headerData: 设置行/列的头部标签。insertRows: 实现插入新行的逻辑。removeRows: 实现删除行的逻辑。
3 可选实现的函数
根据需要,特别是当数据结构发生大规模变化时,可以实现:
resetModel(使用beginResetModel和endResetModel): 当模型的数据发生重大变化时使用,如整体更换数据源。
自定义模型实现案例
我们可以通过自定义模型,将之前的信息表重新实现
1 创建Person.h用来存储用户信息
class Person
{
public:
Person();
QString _name;
int _age;
QString _profession;
};
Person.cpp中实现构造函数
Person::Person():_name(""),_age(0),_profession("")
{
}
Person::Person(QString name, int age, QString profession)
:_name(name),_age(age),_profession(profession)
{
}
2 创建自定义模型类
因为我们是用QTableView展示数据,那配套的数据是QAbstractTableModel(表格数据)最为合适,所以我们定义的模型类要继承这个模型类
我们右键QT项目选择新建模型类PeopleModel, 先定义PeopleModel类并声明成员函数
class PeopleModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit PeopleModel(QObject* parent = nullptr);
//必须实现的纯虚函数
//返回行数
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
//返回列数
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
// 获取数据
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
// 设置数据
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
// 返回索引项对应的标记
Qt::ItemFlags flags(const QModelIndex &index) const override;
// 返回头部各列数据
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
//插入和删除行会调用如下函数
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
//重置整个模型数据
void resetData(const QList<Person> &newPeople);
private:
QList<Person> _people;
};
3 实现PeopleModel构造函数
PeopleModel::PeopleModel(QObject *parent)
:QAbstractTableModel(parent)
{
// 初始化一些示例数据
_people.append(Person{"张三", 25, "工程师"});
_people.append(Person{"李四", 30, "设计师"});
_people.append(Person{"王五", 28, "教师"});
_people.append(Person{"赵六", 22, "学生"});
}
4 返回行数和列数
int PeopleModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
//返回QList中元素的个数
return _people.count();
}
int PeopleModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
//返回列数
return 3;
}
5 返回index对应的表格模型数据
//返回index对应的数据
QVariant PeopleModel::data(const QModelIndex &index, int role) const
{
//先判断索引是否有效
if(!index.isValid()){
return QVariant();
}
//判断行号是不是超过最大行,因为index的row是从0开始的
if(index.row() >= _people.count() || index.row() < 0){
return QVariant();
}
//获取索引对应的行号,再根据行号获取对应人物数据
auto & person = _people[index.row()];
// 如果role不是表现角色并且不是可编辑的,就直接返回无效数据
if(role != Qt::DisplayRole && role != Qt::EditRole){
return QVariant();
}
//判断列数
auto const & column = index.column();
//第0列则返回用户名字
if(column == 0){
return person._name;
}
//第1列则返回用户年龄
if(column == 1){
return person._age;
}
//第2列则返回用户专业
if(column == 2){
return person._profession;
}
//都不满足则返回空
return QVariant();
}
6 设置用户数据
//设置用户数据
bool PeopleModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
//如果索引无效,或者角色不可编辑则直接返回false,表示无需设置
if(!index.isValid() || role != Qt::EditRole){
return false;
}
//进一步判断index.row是否越界
if(index.row() >= _people.count() || index.row() < 0){
return false;
}
auto & person = _people[index.row()];
//最后判断列数设置对应字段
const auto & column = index.column();
//列数索引大于等于3则直接返回
if(column >= 3){
return false;
}
//设置名字
if(column == 0){
person._name = value.toString();
//通知tableview视图刷新
//三个参数分别表示和要刷新区域的左上角的索引,右下角的索引,以及刷新的角色列表
emit dataChanged(index,index,{role});
return true;
}
//设置年龄
if(column == 1){
person._age = value.toInt();
//通知tableview视图刷新
//三个参数分别表示和要刷新区域的左上角的索引,右下角的索引,以及刷新的角色列表
emit dataChanged(index,index,{role});
return true;
}
//设置专业
if(column == 2){
person._profession = value.toString();
//通知tableview视图刷新
//三个参数分别表示和要刷新区域的左上角的索引,右下角的索引,以及刷新的角色列表
emit dataChanged(index,index,{role});
return true;
}
return false;
}
7 返回项的角色
//返回项的标记
Qt::ItemFlags PeopleModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
}
如果索引无效,则返回NoItemFlags表示无效的项,否则返回可编辑,可被选择以及有效标记。
8 返回表头数据
根据表头返回某个列的名字
QVariant PeopleModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal) {
switch (section) {
case 0:
return tr("姓名");
case 1:
return tr("年龄");
case 2:
return tr("职业");
default:
return QVariant();
}
}else if (orientation == Qt::Vertical){
return QString("Row %1").arg(section + 1);
}
return QVariant();
}
9 实现插入接口
insertRows会在model调用insertRow的时候触发,所以更新模型数据的时候要实现
bool PeopleModel::insertRows(int row, int count, const QModelIndex &parent)
{
Q_UNUSED(parent);
if (row < 0 || row > _people.size())
return false;
beginInsertRows(QModelIndex(), row, row + count - 1);
for (int i = 0; i < count; ++i) {
_people.insert(row, Person{"", 0, ""});
}
endInsertRows();
return true;
}
10 实现删除接口
removeRows会在model调用removeRow的时候触发,所以更新模型的时候要实现
bool PeopleModel::removeRows(int row, int count, const QModelIndex &parent)
{
Q_UNUSED(parent);
if (row < 0 || (row + count) > _people.size())
return false;
beginRemoveRows(QModelIndex(), row, row + count - 1);
for (int i = 0; i < count; ++i) {
_people.removeAt(row);
}
endRemoveRows();
return true;
}
11 重置数据
更新模型数据的时候会调用此函数
void PeopleModel::resetData(const QList<Person> &newPeople)
{
beginResetModel();
_people = newPeople;
endResetModel();
}
到此模型更新完毕,为了方便演示,我们将原来的模型改为我们自定义的模型
12 更改模型为自定义模型
在MainWindow的声明中将model改为PeopleModel*类型
PeopleModel* model;
然后将MainWindow的构造函数中构造这个自定义模型 , 并将之前对模型数据的初始化删除
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
tableView(new QTableView(this)),
model(new PeopleModel(this)),
insertButton(new QPushButton("插入行",this)),
deleteButton(new QPushButton("删除行",this)){
}
13 新增重置按钮
MainWindow声明处增加重置按钮,添加新的槽函数
//重置数据按钮
QPushButton *resetButton;
private slots:
//重置数据
void resetModelData();
MainWindow的构造函数中添加重置按钮
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
tableView(new QTableView(this)),
model(new PeopleModel(this)),
insertButton(new QPushButton("插入行",this)),
deleteButton(new QPushButton("删除行",this)),
resetButton(new QPushButton("重置数据", this)){
//...
}
并且MainWindow构造函数中将重置按钮放入布局,且链接信号和槽
//添加重置按钮
buttonLayout->addWidget(resetButton);
connect(resetButton, &QPushButton::clicked, this, &MainWindow::resetModelData);
14 重新实现槽函数
我们希望在insertRow()调用后让新插入的行的第一个单元格处于编辑状态
删除和之前没区别,获取选中的行然后删除,从后往前删
最后是重置数据直接调用模型的resetData就可以了。
void MainWindow::insertRow()
{
// 在末尾插入一行
int row = model->rowCount();
if (model->insertRow(row)) {
// 自动选择新插入的行并开始编辑第一个单元格(姓名)
QModelIndex index = model->index(row, 0);
tableView->setCurrentIndex(index);
tableView->edit(index);
} else {
QMessageBox::warning(this, "插入失败", "无法插入新行!");
}
}
//删除行
void MainWindow::deleteRow()
{
// 获取选中的行
QModelIndexList selected = tableView->selectionModel()->selectedRows();
if (selected.isEmpty()) {
QMessageBox::information(this, "提示", "请先选择要删除的行!");
return;
}
// 删除从后往前
QList<int> rows;
for(int i = selected.size()-1; i >= 0; --i){
rows.append(selected[i].row());
}
std::sort(rows.begin(), rows.end(), std::greater<int>());
for(int i = 0; i < rows.size(); i++){
if(!model->removeRow(rows[i])){
QMessageBox::warning(this, "删除失败", "无法删除所选择的行!");
}
}
}
void MainWindow::resetModelData()
{
// 示例:重置模型数据为一个新的列表
QList<Person> newPeople = {
{"孙七", 35, "产品经理"},
{"周八", 27, "测试工程师"},
{"吴九", 29, "销售经理"}
};
model->resetData(newPeople);
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)