QT开发(三十八)——Model/View框架编程
一、自定义模型
1、自定义只读模型
QAbstractItemModel为自定义模型提供了一个足够灵活的接口,能够支持数据源的层次结构,能够对数据进行增删改操作,还能够支持拖放。QT提供了 QAbstarctListModel和QAbstractTableModel两个类来简化非层次数据模型的开发,适合于结合列表和表格使用。
自定义模型需要考虑模型管理的的数据结构适合的视图的显示方式。如果模型的数据仅仅用于列表或表格的显示,那么可以使用QAbstractListModel或者 QAbstractTableModel。但如果模型的数据具有层次结构,并且必须向用户显示出层次结构,只能选择QAbstractItemModel。不管底层数据结构是如何组织的,都要直接考虑适应于标准的 QAbstractItemModel的接口,可以让更多视图能够轻松访问到模型。
自定义一个货币汇率表模型CurrencyModel
通常,货币的汇率都是相对于某个基准货币(如美元)的汇率。
CurrencyModel底层使用QMap
CurrencyModel.h文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#ifndef CURRENCYMODEL_H
#define CURRENCYMODEL_H
#include
class
CurrencyModel :
public
QAbstractTableModel
{
public
:
CurrencyModel(QObject *parent = 0);
void
setCurrencyMap(
const
QMap
int
rowCount(
const
QModelIndex &parent)
const
;
int
columnCount(
const
QModelIndex &parent)
const
;
QVariant data(
const
QModelIndex &index,
int
role)
const
;
QVariant headerData(
int
section, Qt::Orientation orientation,
int
role)
const
;
private
:
QString currencyAt(
int
offset)
const
;
QMap };
#endif // CURRENCYMODEL_H
|
CurrencyModel.cpp文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
#include "CurrencyModel.h"
#include
CurrencyModel::CurrencyModel(QObject *parent):QAbstractTableModel(parent)
{
}
QVariant CurrencyModel::data(
const
QModelIndex &index,
int
role)
const
{
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);
return
QString(
"%1"
).arg(amount, 0,
'f'
, 4);
}
return
QVariant();
}
int
CurrencyModel::rowCount(
const
QModelIndex & parent)
const
{
return
currencyMap.count();
}
int
CurrencyModel::columnCount(
const
QModelIndex & parent)
const
{
return
currencyMap.count();
}
QVariant CurrencyModel::headerData(
int
section, Qt::Orientation,
int
role)
const
{
if
(role != Qt::DisplayRole)
{
return
QVariant();
}
return
currencyAt(section);
}
QString CurrencyModel::currencyAt(
int
offset)
const
{
return
(currencyMap.begin() + offset).key();
}
void
CurrencyModel::setCurrencyMap(
const
QMap
{
beginResetModel();
currencyMap = map;
endResetModel();
}
|
Main.cpp文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#include "CurrencyModel.h"
#include
#include
#include
int
main(
int
argc,
char
*argv[])
{
QApplication a(argc, argv);
QMap
data[
"USD"
] = 1.0000;
data[
"CNY"
] = 0.1628;
data[
"GBP"
] = 1.5361;
data[
"EUR"
] = 1.2992;
data[
"HKD"
] = 0.1289;
QTableView view;
CurrencyModel *model =
new
CurrencyModel(&view);
model->setCurrencyMap(data);
view.setModel(model);
view.resize(400, 300);
view.show();
return
a.exec();
}
|
2、自定义可编辑模型
如果允许用户修改数据,则应该提供可编辑的模型。可编辑模型与只读模型非常相似,至少在显示数据方面几乎是完全一样的,所不同的是可编辑模型需要提供用户编辑数据后,应当如何将数据保存到实际存储值中。
可编辑模型需要增加
Qt::ItemFlags QAbstractItemModel::flags(const QModelIndex &index) const;
bool QAbstractItemModel::setData(const QModelIndex &index, const QVariant &value,int role = Qt::EditRole);
在QT的Model/View模型中,使用委托delegate来实现数据的编辑。在实际创建编辑器前,委托需要检测数据项是不是允许编辑。模型必须让委托知道数据项是否可编辑,可以通过flags()函数返回模型中每个数据项的标记flag来实现。当行和列的索引不一致的时候,允许修改数据项。
1
2
3
4
5
6
7
8
9
|
Qt::ItemFlags CurrencyModel::flags(
const
QModelIndex &index)
const
{
Qt::ItemFlags flags = QAbstractItemModel::flags(index);
if
(index.row() != index.column())
{
flags |= Qt::ItemIsEditable;
}
return
flags;
}
|
告诉Qt如何将委托获得的用户输入的新的数据保存到模型中。
1
2
3
4
5
6
7
8
9
10
11
12
|
bool
CurrencyModel::setData(
const
QModelIndex &index,
const
QVariant &value,
int
role)
{
if
(index.isValid() && index.row() != index.column() && role == Qt::EditRole)
{
QString columnCurrency = headerData(index.column(), Qt::Horizontal, Qt::DisplayRole).toString();
QString rowCurrency = headerData(index.row(), Qt::Vertical, Qt::DisplayRole).toString();
currencyMap.insert(columnCurrency, value.toDouble() * currencyMap.value(rowCurrency));
emit dataChanged(index, index);
return
true
;
}
return
false
;
}
|
增加data()函数的可编辑属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
QVariant CurrencyModel::data(
const
QModelIndex &index,
int
role)
const
{
if
(!index.isValid())
{
return
QVariant();
}
if
(role == Qt::TextAlignmentRole)
{
return
int
(Qt::AlignRight | Qt::AlignVCenter);
}
else
if
(role == Qt::DisplayRole || role == Qt::EditRole)
{
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);
return
QString(
"%1"
).arg(amount, 0,
'f'
, 4);
}
return
QVariant();
}
|
3、自定义模型应用实例
QStandardItemModel是一个通用的模型类,能够以任意的方式组织数据(如、线性、非线性),数据组织的基本单位为QStandardItem数据项,每个数据项可以根据数据角色存储多个数据,每一个数据项能够对数据状态进行控制(如可编辑、可选)。
QVariant是变体类型,常用于设计返回类型可变的函数。
工程中常用模型设计如下:
A、解析数据源中的数据(数据库、串口、网络)
B、将解析后的数据存入QStandarItem数据项
C、根据数据间的关系在QStandardItemModel对象中组织数据项
D、选择合适的视图显示数据
实例:
在文件中以行的形式存储考试成绩信息(ID,Name,Score),开发GUI程序显示文件中的信息(计算平均成绩,查找最好成绩和最差成绩,刷新显示和删除)。
系统架构图:
架构图用于定义模块功能。
核心类图:
类图用于定义具体功能的接口。
每个核心类用于实现架构图中每一层的具体功能。
DataSource类设计
设计数据源、读取数据
对数据进行解析,生成数据对象
DataScore类设计
封装数据源中的一组完整数据
提供返回具体数据值的接口函数
DataModel类设计
使用QStandardItemModel标准模型类作为类成员
以DataScore类对象作为单位组织数据
DataAppUI界面类设计
右键菜单的实现:
A、定义菜单QMenu对象
B、连接菜单中的QAction对象到槽函数
C、定义事件过滤器,处理ConetxtMenu事件
D、在当前鼠标的位置打开菜单对象
DataSource.h文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#ifndef DATASOURCE_H
#define DATASOURCE_H
#include
#include
#include "DataScore.h"
class
DataSource :
public
QObject
{
Q_OBJECT
private
:
QList
bool
parse(QString line, DataScore& info);
public
:
explicit
DataSource(QObject* parent = 0);
bool
setDataSource(
const
QString source);
QList
int
count()
const
;
};
#endif // DATASOURCE_H
|
DataSource.cpp文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
#include "DataSource.h"
#include
#include
#include
DataSource::DataSource(QObject *parent) : QObject(parent)
{
}
bool
DataSource::parse(QString line, DataScore& info)
{
bool
ret =
true
;
QStringList list = line.split(
","
, QString::SkipEmptyParts);
if
(list.count() == 3)
{
QString id = list[0].trimmed();
QString name = list[1].trimmed();
QString score = list[2].trimmed();
int
value = score.toInt(&ret);
if
(ret && (0 <= value) && (value <= 100))
{
info = DataScore(id, name, value);
}
else
{
ret =
false
;
}
}
else
{
ret =
false
;
}
return
ret;
}
bool
DataSource::setDataSource(
const
QString source)
{
bool
ret =
true
;
QFile file(source);
if
(file.open(QFile::ReadOnly | QFile::Text))
{
QTextStream in(&file);
while
(!in.atEnd())
{
DataScore score;
if
(parse(in.readLine(), score))
{
m_data.append(score);
}
}
file.close();
}
else
{
ret =
false
;
}
return
ret;
}
QList
{
QList
m_data.clear();
return
ret;
}
int
DataSource::count()
const
{
|