QT开发(三十八)——Model/View框架编程

一、自定义模型

1自定义只读模型

    QAbstractItemModel为自定义模型提供了一个足够灵活的接口,能够支持数据源的层次结构,能够对数据进行增删改操作,还能够支持拖放。QT提供了 QAbstarctListModel和QAbstractTableModel两个类来简化非层次数据模型的开发,适合于结合列表和表格使用。

    自定义模型需要考虑模型管理的的数据结构适合的视图的显示方式。如果模型的数据仅仅用于列表或表格的显示,那么可以使用QAbstractListModel或者 QAbstractTableModel。但如果模型的数据具有层次结构,并且必须向用户显示出层次结构,只能选择QAbstractItemModel。不管底层数据结构是如何组织的,都要直接考虑适应于标准的 QAbstractItemModel的接口,可以让更多视图能够轻松访问到模型。

    自定义一个货币汇率表模型CurrencyModel

    通常,货币的汇率都是相对于某个基准货币(如美元)的汇率。

    CurrencyModel底层使用QMap数据结构进行存储,QString类型的键是货币名字,double类型的值是这种货币相对美元的汇率。

    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 double > &map);
     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 double > currencyMap;
};
  
#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 double > &map)
  {
      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 double > data;
     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();
}

wKiom1hw9uPjL69GAAB4Qwchh1k245.png

2、自定义可编辑模型

    如果允许用户修改数据,则应该提供可编辑的模型。可编辑模型与只读模型非常相似,至少在显示数据方面几乎是完全一样的,所不同的是可编辑模型需要提供用户编辑数据后,应当如何将数据保存到实际存储值中。

    可编辑模型需要增加

    Qt::ItemFlags QAbstractItemModel::flags(const QModelIndex &index) const;

    bool QAbstractItemModel::setData(const QModelIndex &index, const QVariant &value,int role = Qt::EditRole);

    在QTModel/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数据项,每个数据项可以根据数据角色存储多个数据,每一个数据项能够对数据状态进行控制(如可编辑、可选)。

wKioL1hw9wjxD6zmAACajaEvq28283.png

QVariant是变体类型,常用于设计返回类型可变的函数。

工程中常用模型设计如下:

A、解析数据源中的数据(数据库、串口、网络)

B、将解析后的数据存入QStandarItem数据项

C、根据数据间的关系在QStandardItemModel对象中组织数据项

D、选择合适的视图显示数据

wKioL1hw9yXx9sV6AABgZNGDLTE184.png

实例:

在文件中以行的形式存储考试成绩信息(ID,Name,Score),开发GUI程序显示文件中的信息(计算平均成绩,查找最好成绩和最差成绩,刷新显示和删除)。

    系统架构图:

wKiom1hw90Owk_lqAACUSEDT_u8111.png

    架构图用于定义模块功能。

    核心类图:

wKioL1hw916BdnrwAABIw5Wg0GU635.png

类图用于定义具体功能的接口。

每个核心类用于实现架构图中每一层的具体功能。

DataSource类设计

设计数据源、读取数据

对数据进行解析,生成数据对象

wKiom1hw936Tb7n3AAA081iuFT0611.png

DataScore类设计

封装数据源中的一组完整数据

提供返回具体数据值的接口函数

wKioL1hw95-SzpsFAAAyTShgDFE702.png

DataModel类设计

使用QStandardItemModel标准模型类作为类成员

DataScore类对象作为单位组织数据

wKioL1hw97mzYRxXAABEoDM0EUo876.png

    DataAppUI界面类设计

wKiom1hw99TCEGImAAA6Fa7f920531.png

    右键菜单的实现:

    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 m_data;
     bool  parse(QString line, DataScore& info);
public :
     explicit  DataSource(QObject* parent = 0);
     bool  setDataSource( const  QString source);
     QList fetchData();
     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 DataSource::fetchData()
{
     QList ret = m_data;
     m_data.clear();
     return  ret;
}
  
int  DataSource::count()  const
{