Qt5MV自定义模型与实例浅析
1. Model/View结构
这种结构,其实就是将界面组件与所编辑的数据分离开来,又通过数据源的方式连接起来,相当于解耦,视图层只关心显示和与用户交互,而数据层负责与实际的数据进行通信,并为视图组件提供数据接口
网上比较经典的图如下
是不是很清晰明了
关于MV的实例之前已经发过一期,这里就不再赘述,链接如下
2. 自定义模型
2.1 定义
实现自定义模型可以通过QAbstractItemModel类继承,也可以通过QAbstractListModel,QAbstractTableModel类继承实现列表模型或表格模型。
2.2 标准数据模型
Qt实现了4类标准数据模型供我们在不同的场景下使用:
- QStringListModel:存储字符串列表。
- QStandardItemModel:存储树状结构的任意数据。
- QFileSystemModel:存储本地文件系统上的文件和目录信息。
- QSqlQueryModel、QSqlRelationalTableModel、QSqlTableModel:存储关系型数据库中的数据。
如果使用情况和上述情况之一比较相似,则可以考虑继承对应的模型类,并重新实现少数虚函数。
2.3 抽象模型
抽象数据模型有3类:
- QAbstractItemModel:项模型,这是所有数据模型的基类。
- QAbstractListModel:列表模型,结合QListView使用最合适。
- QAbstractTableModel:表模型,结合QTableView使用最合适。
2.4 自定义模型实例
我们以继承QAbstractTableModel为例子,来实现一个自定义模型
如果我们继承一个类,就必须得实现它的全部的纯虚函数
对于这个抽象表格模型类,我们得继承下面这些纯虚函数
static string BlogAdress = "https://www.cnblogs.com/wanghongyang/";
virtual int rowCount(const QModelIndex &parent=QModelIndex()) const;
virtual int columnCount(const QModelIndex &parent=QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
接下来说一下这4个虚函数的作用
virtual int rowCount(const QModelIndex &parent=QModelIndex()) const;
返回给定父节点下的行数。当父节点有效时,意味着rowCount返回父节点的子节点数。
virtual int columnCount(const QModelIndex &parent=QModelIndex()) const;
与前面的对应,返回给定父节点的子节点的列数。
QVariant data(const QModelIndex &index, int role) const;
为索引引用的项返回存储在给定角色下的数据。
注意:如果你没有返回值,返回一个无效的QVariant而不是返回0。
这里我们说明一下,role是个什么东东,在这里我直接列出官方的文档
比较常用的有下面这些
这里提到了QVariant,那我们再简单的谈谈QVariant的用法:
QVariant 类用于封装数据成员的类型及取值等信息,该类类似于 C++共用体 union,一个QVariant 对象,一次只能保存一个单一类型的值。该类封装了 Qt 中常用的类型,对于QVariant 不支持的类型 ( 比如用户自定义类型 ) ,则需要使用Q_DECLARE_METATYPE( Type )宏进行注册
QVariant 拥有常用类型的单形参构造函数,因此可把这些常用类型转换为 QVariant 类型,同时 QVariant 还重载了赋值运算符,因此可把常用类型的值直接赋给 QVariant 对象。
注意:QVariant 没有 char 类型的构造函数,若使用 char 值会被转换为对应的 int 型
下面分情况讨论QVariant的使用
1) 支持的类型
对于QVariant支持的类型,可直接赋值,但是取值时,对于存入的是什么类型,取出也要为这个类型
QVariant var;
var.setValue(12);
int data=var.toInt();
或者
QVariant var=12;
int data=var.toInt();
2) 对于不支持的类型(自定义类型为例)
如自己定义的结构体。由于Qt都是基于元对象系统,故要在头文件里面要注册此类属于元类型。存储用到了QVariant QVariant::fromValue(const T &value) 或 void QVariant::setValue(const T &value)。获取用到了T QVariant::value() const,在这之前一般要bool QVariant::canConvert(int targetTypeId) const先用进行判断,是否可以转换。例子如下:
.h文件声明
struct MyClass{
int id;
QString name;
};
Q_DECLARE_METATYPE(MyClass)
.cpp文件定义
//存储数据
MyClass myClass;
myClass.id=0;
myClass.name=QString("LiMing");
data[0]=QString("ddd");
data[1]=123;
data[3]=QVariant::fromValue(myClass);
//获取数据
QString str=data.value(0).toString();
int val=data.value(1).toInt();
// 注意,先判断
if(data[3].canConvert<MyClass>())
{
MyClass myClass=data[3].value<MyClass>();
int id=myClass.id;
QString name=myClass.name;
}
说完了QVariant,我们继续说自定义模型最后一个虚函数
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
返回标头中指定方向的给定角色和区段的数据。对于水平标头,节号对应于列号。类似地,对于垂直标题,节号对应于行号。
2.5 具体实现
构造函数
ModelEx::ModelEx(QObject *parent) :
QAbstractTableModel(parent)
{
armyMap[1]=tr("空军");
armyMap[2]=tr("海军");
armyMap[3]=tr("陆军");
armyMap[4]=tr("海军陆战队");
weaponTypeMap[1]=tr("轰炸机");
weaponTypeMap[2]=tr("战斗机");
weaponTypeMap[3]=tr("航空母舰");
weaponTypeMap[4]=tr("驱逐舰");
weaponTypeMap[5]=tr("直升机");
weaponTypeMap[6]=tr("坦克");
weaponTypeMap[7]=tr("两栖攻击舰");
weaponTypeMap[8]=tr("两栖战车");
populateModel();
}
构造函数中,存放的是数据,下面是定义的私有成员变量
QVector<short> army;
QVector<short> weaponType;
QMap<short,QString> armyMap;
QMap<short,QString> weaponTypeMap;
QStringList weapon;
QStringList header;
下面是populateModel()函数
void ModelEx::populateModel()
{
header<<tr("军种")<<tr("种类")<<tr("武器");
army<<1<<2<<3<<4<<2<<4<<3<<1;
weaponType<<1<<3<<5<<7<<4<<8<<6<<2;
weapon<<tr("B-2")<<tr("尼米兹级")<<tr("阿帕奇")<<tr("黄蜂级")<<tr("阿利伯克级")<<tr("AAAV")<<tr("M1A1")<<tr("F-22");
}
简单来说,army和weapon就是将数字与具体的值关联起来而存储值的容器
然后看看我们重新实现的纯虚函数(重点)
int ModelEx::columnCount(const QModelIndex &parent) const
{
return 3;
}
因为模型的列固定为3,所以这里我们直接返回3
int ModelEx::rowCount(const QModelIndex &parent) const
{
return army.size();
}
模型的行数要根据数量的大小来定,所以返回size();
QVariant ModelEx::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
return QVariant();// 这里不能直接返回0
if(role==Qt::DisplayRole)
{
switch(index.column())
{
case 0:
return armyMap[army[index.row()]];
break;
case 1:
return weaponTypeMap[weaponType[index.row()]];
break;
case 2:
return weapon[index.row()];
default:
return QVariant();
}
}
return QVariant();
}
这个函数用来返回指定索引的数据,将数值映射为文字
其中 role==Qt::DisplayRole: 模型中的条目能够有不同的角色,这样可以在不同的情况下提供不同的数据。例如,Qt::DisplayRole用来存取视图中显示的文字,角色由枚举类Qt::ItemDataRole定义。
其中index.column()是用来选择是第几列,根据列的不同来选择不同的数据
QVariant ModelEx::headerData(int section, Qt::Orientation orientation, int role) const
{
if(role==Qt::DisplayRole&&orientation==Qt::Horizontal)
return header[section];
return QAbstractTableModel::headerData(section,orientation,role);
}
headerData()函数返回固定的表头数据,设置水平表头的标题
这里的orientation==Qt::Horizontal是设置为水平标题
return QAbstractTableModel::headerData(section,orientation,role);
这一行,是当条件不满足时,调用父类的headerData函数,来处理剩下的问题
2.6 运行结果
3. 总结
根据这篇博客,完整的梳理了一下,自定义模型需要干的事情,如果有错误的话,请在评论区进行说明,我会修改