Qt | 如何创建一个新的模型

184 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

前言:

当要为一个已经存在的数据结构创建一个新的模型时,需要考虑使用那种类型的模型来为数据提供接口。如果数据结构可以表示为项目列表或者表格,那么可以子类化QAbstractListModel或者QAbstractTableModel,在这个的基础上创建属于自己的新模型。如果底层数据结构只能表示为具有层次的树结构,那么就需要子类化QAbstractItemModel。

创建一个只读模型:

创建一个基于QStringListModel类的、简单的、非层次结构的、只读的数据模型。

class StringListModel : public QAbstractListModel{}

该模型使用了一个QStringList作为内部的数据源,是因为QAbstractItemModel本身不存储数据,QAbstractItemModel仅仅提供一些接口来供视图访问数据。

在定义的模型类中,除了构造函数之外,只需要实现两个函数:rowCount()data()。这里还实现了headerData() 函数。

  • rowCount()返回模型的行数;
  • data()返回指定模型索引的数据项。
  • headerData()可以在树和表格视图的标头显示一些内容。

因为这里实现的模型时非层次结构的,所以不需要考虑父子关系。但是,如果模型时层次结构的,还需要实现index()和parent()函数。

函数实现:

//rowCont()函数
int StringListModel::rowCount(QModelIndex &parent){
    return stringList.count();
}

//data()函数
QVariant StringListModel::data(QModelIndex &index, int role){
    if(!index.isValid)
        return QVariant();
    if(index.row() == stringList.size())
        return QVariant();
    if(role == Qt::DiaplayRole)
        return stringList.at(index.row());
    else
        return QVariant();
}

当提供的索引是有效的,行号咋字符串列表的大小范围之内,而且需要的角色是支持的角色之一时,返回一个有效的QVariant。

//headerData()函数
QVariant StringListModel::headerData(int section, Qt::Qrientation orientation, int role){
    if(role != Qt::DsiplayRole)
        return QVariant();
    if(orientation == Qt::Horizontal)
        return QString("Column %1").arg(section);
    else
        return QString("Row %1").arg(section);
}
//这里实现了在标头中显示行号和列号

使用:

QStringList list;
list << "a" << "b" << "c";
StringListModel model(list);
//将模型展示在列表视图中
QListView listView;
listView.setModel(&model);
listView.show();
//将模型展示在表格视图中
QTableView tableView;
tableView.setModel(&model);
tableView.show();

添加编辑功能:

如果想为模型添加编辑功能,需要实现两个函数:flags()和setData()。flags()函数为模型中的每一个项目返回一个正确的标识来达到检测项目是否可编辑的目的。通过setData()函数为模型中的项目设置数据。

实现:

Qt::ItemFlags StringListModel::flags(QModelIndex &index){
    if(!index.isValid())
        return Qt::ItemIsEnabled;
    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}

bool StringListModel::setData(QModelIndex &index, QVariant &value, int role){
    if(index.isValid() && role == Qt::EditRole){
        stringList.replace(index.row(), value.toString());
        emit dataChanged(index, index);
        return true;
    }
    return false;
}

当数据被设置后,模型必须发送dataChanged()信号通知视图,让视图知道数据已经改变了。

插入和删除行:

如果想在模型中实现插入和删除行的功能,需要重新实现insertRows()和removeRows()函数。

实现:

  • 因为模型中的行对应着列表中的字符串,所以添加行时指定添加空字符串。
  • 父索引是用来决定在模型的什么位置添加行的。
  • 模型首先要调用beginInsertRows()函数来告知其他组件指定的行将要发生改变,这个函数指定了将要插入的第一个、最后一个新行的行号,以及它们的父项的模型索引。
  • 当改变玩字符串以后,需要调用endInsertRows()函数来完成操作,并告知其他组件该模型的大小发生了变化。
bool StringListModel::insetRows(int position, int rows, const QModelIndex &parent){
    beginInsertRows(QModelIndex(), position, positon += rows-1);
    for(int row = 0; row < rows; ++row){
        stringList.inset(position, "");
    }
    endInsertRows();
    return true;
}

删除行的操作与插入行的操作类似。

bool StringListModel::removeRows(int position, int rows, const QModelIndext &parent){
    beginRemoveRows(QModelIndex().position, position, position+rows-1);
    for(int row = 0; row < rows; ++row){
        stringList.removeAt(positon);
    }
    endRemoveRows();
    return true;
}

调用:

model.insertRows(3, 2);//在模型第3个数据项后添加2个空数据项
model.removeRows(0, 1);//删除模型的第一个数据项