参考:
Qt 自定义 model 之一_w3cschool
https://www.w3cschool.cn/learnroadqt/xmvq1j4q.html
C++ GUI Programming with Qt 4, Second Edition
本地环境:
win10专业版,64位,Qt 5.12
理论基础
model-view架构
一个model中每个数据都有一个model索引(QModelIndex
),这个索引指明了这个数据在model中的位置,比如行、列等。每个数据还要有一组属性值,称为角色roles
。每个数据项都可以有多个角色,用于指定不同的数据操作。
比如Qt::DisplayRole
是其中一个用于获取显示文本的角色。
当视图需要展示模型中的数据时,会通过数据模型的data()函数来获取数据项的值。在调用data()函数时,可以通过第二个参数指定需要获取的数据项的角色。如果角色指定为Qt::DisplayRole
,那么返回的值是用于显示的文本。
对于list model,定位其中一个数据只要使用QModelIndex::row()
即可;对于table model,除了行号还需要QModelIndex::column()
;对于tree model,需要用到这个元素的父节点QModelIndex::parent()
。但对于所有model,都有自己的角色数据和索引,索引中也都有父节点(list和table model的元素的父节点都是非法的一个索引)。
自定义model
自定义model主要是为了只使用最相关的特性,抛弃不需要的,提升效率。同时可以做一些自定义的操作。
Qt提供了QAbstractListModel
和QAbstractTableModel
两类,前者是一维数据 model,后者是二维数据 model。如果数据很复杂,那么可以直接继承QAbstractItemModel
(是前两个的父类,继承自QObject
)。如果继承了QAbstractTableModel
,那可以只重写几个函数,就完成了model定义。
效果
下面是一个汇率对照表格。假设有5种货币,形成下图所示的表格时,并不需要储存5x5个数据(如果有更多种货币,数据量会变大)。这里设计一个新的模型,只存储一种货币与其他货币的汇率,剩余货币之间的转换让模型自动计算。
头文件:
#ifndef CURRENCYMODEL_H
#define CURRENCYMODEL_H
#include <QAbstractTableModel>
class CurrencyModel : public QAbstractTableModel {
public:
CurrencyModel(QObject *parent = 0);
void setCurrencyMap(const QMap<QString, double> &map);
// 返回行数
int rowCount(const QModelIndex &parent) const;
// 返回列数
int columnCount(const QModelIndex &parent) const;
// 返回单元格数据
// QVariant类似java里的Object,可以封装大多数Qt的数据类型
// 封装是因为table model里可能放很多种类型,取出的时候用QVariant::toInt()之类的提取就行
// 放入和取出的类型要一致
// QVariant也表示在不同的条件下,return的类型可以不一样,只要能被QVariant包裹即可
QVariant data(const QModelIndex &index, int role) const;
// 返回列名或者行名
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
private:
// 返回偏移量为offset的key
QString currencyAt(int offset) const;
QMap<QString, double> currencyMap;
};
#endif // CURRENCYMODEL_H
源文件:
#include "currencymodel.h"
CurrencyModel::CurrencyModel(QObject *parent)
: QAbstractTableModel(parent)
{
// 调用父类构造函数即可
}
int CurrencyModel::rowCount(const QModelIndex & parent) const
{
// 因为使用的是一维的map,所以返回个数就是行数、列数
return currencyMap.count();
}
int CurrencyModel::columnCount(const QModelIndex & parent) const
{
return currencyMap.count();
}
QVariant CurrencyModel::data(const QModelIndex &index, int role) const
{
// 如果index不合法就返回空的QVariant
if (!index.isValid())
return QVariant();
if (role == Qt::TextAlignmentRole) {
return int(Qt::AlignRight | Qt::AlignVCenter);
} else if (role == Qt::DisplayRole) {
QString rowCurrency = currencyAt(index.row());
QString columnCurrency = currencyAt(index.column());
if (currencyMap.value(rowCurrency) == 0.0)
return "####";
double amount = currencyMap.value(columnCurrency) / currencyMap.value(rowCurrency);
// arg: 第一个参数:要填充的变量
// 第二个参数:要填充的宽度。默认是0,表示不填充
// 第三个参数:使用浮点数格式插入
// 第四个参数:小数点后保留4位
return QString("%1").arg(amount, 0, 'f', 4);
}
return QVariant();
}
QVariant CurrencyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
return currencyAt(section);
}
void CurrencyModel::setCurrencyMap(const QMap<QString, double> &map)
{
beginResetModel();
currencyMap = map;
// 此函数在qt5中已经作废,需要使用begin和end通知视图刷新数据
//reset();
endResetModel();
}
QString CurrencyModel::currencyAt(int offset) const
{
return (currencyMap.begin() + offset).key();
}
main中:
QMap<QString, double> data;
data["NOK"] = 1.0000;
data["NZD"] = 0.2254;
data["SEK"] = 1.1991;
data["SGD"] = 0.2592;
data["USD"] = 0.1534;
CurrencyModel *model = new CurrencyModel;
model->setCurrencyMap(data);
QTableView *view = new QTableView;
view->setModel(model);
view->resize(400, 300);
view->show();