Qt5MV自定义模型与实例浅析

1. Model/View结构

这种结构,其实就是将界面组件与所编辑的数据分离开来,又通过数据源的方式连接起来,相当于解耦,视图层只关心显示和与用户交互,而数据层负责与实际的数据进行通信,并为视图组件提供数据接口

网上比较经典的图如下

image-20210718224949224

是不是很清晰明了

关于MV的实例之前已经发过一期,这里就不再赘述,链接如下

Qt Model/view 小实例 文件目录浏览器

2. 自定义模型

2.1 定义

实现自定义模型可以通过QAbstractItemModel类继承,也可以通过QAbstractListModel,QAbstractTableModel类继承实现列表模型或表格模型。

2.2 标准数据模型

Qt实现了4类标准数据模型供我们在不同的场景下使用:

  1. QStringListModel:存储字符串列表
  2. QStandardItemModel:存储树状结构的任意数据
  3. QFileSystemModel:存储本地文件系统上的文件和目录信息
  4. QSqlQueryModel、QSqlRelationalTableModel、QSqlTableModel:存储关系型数据库中的数据

如果使用情况和上述情况之一比较相似,则可以考虑继承对应的模型类,并重新实现少数虚函数。

2.3 抽象模型

抽象数据模型有3类:

  1. QAbstractItemModel:项模型,这是所有数据模型的基类。
  2. QAbstractListModel:列表模型,结合QListView使用最合适。
  3. QAbstractTableModel:表模型,结合QTableView使用最合适。

2.4 自定义模型实例

我们以继承QAbstractTableModel为例子,来实现一个自定义模型

如果我们继承一个类,就必须得实现它的全部的纯虚函数

对于这个抽象表格模型类,我们得继承下面这些纯虚函数

static string BlogAdress = "//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是个什么东东,在这里我直接列出官方的文档

image-20210719204750094

比较常用的有下面这些

image-20210719212112702


这里提到了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 运行结果

image-20210719213204388

3. 总结

根据这篇博客,完整的梳理了一下,自定义模型需要干的事情,如果有错误的话,请在评论区进行说明,我会修改

博主博客://www.cnblogs.com/wanghongyang/