原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/267435

前面我们说了Qt提供的几个预定义model。但是,面对变化万千的需求,那几个model是远远不能满足我们的需要的。另外,对于Qt这种框架来说,model的选择首先要能满足绝大多数功能的需要,这就是说,可能这个model中的某些功能你永远也不会用到,但是还要带着它,这样做的后果就是效率不会很高。所以,我们还必须要能够自定义model。

 

在我们真正的完成自定义model之前,先来看看在Qt的model-view架构中的几个关键的概念。一个model中的每个数据元素都有一个model索引。这个索引指明这个数据位于model的位置,比如行、列等。这就是前面我们曾经说到过的QModelIndex。每个数据元素还要有一组属性值,称为角色(roles)。这个属性值并不是数据的内容,而是它的属性,比如说,这个数据是用来展示数据的,还是用于显示列头的?因此,这组属性值实际上是Qt的一个enum定义的,比较常见的有Qt::DisplayRole和Qt::EditRole,另外还有Qt::ToolTipRole, Qt::StatusTipRole, 和Qt::WhatsThisRole等。并且,还有一些属性是用来描述基本的展现属性的,比如Qt::FontRole, Qt::TextAlignmentRole, Qt::TextColorRole, Qt::BackgroundColorRole等。

 

对于list model而言,要定位其中的一个数据只需要有一个行号就可以了,这个行号可以通过QModelIndex::row()函数进行访问;对于table model而言,这种定位需要有两个值:行号和列号,这两个值可以通过QModelIndex::row()和QModelIndex::column()这两个函数访问到。另外,对于tree model而言,用于定位的可以是这个元素的父节点。实际上,不仅仅是tree model,并且list model和table model的元素也都有自己的父节点,只不过对于list model和table model,它们元素的父节点都是相同的,并且指向一个非法的QModelIndex。对于所有的model,这个父节点都可以通过QModelIndex::parent()函数访问到。这就是说,每个model的项都有自己的角色数据,0个、1个或多个子节点。既然每个元素都有自己的子元素,那么它们就可以通过递归的算法进行遍历,就像数据结构中树的遍历一样。关于父节点的描述,请看下面这张图(出自C++ GUI Programming with Qt4, 2nd Edition):

Qt学习之路(45): 自定义model之一_qt

构造函数中没有添加任何代码,只要调用父类的构造函数就可以了。然后我们重写了rowCount()和columnCount()这两个函数,用于返回model的行数和列数。由于我们使用一维的map记录数据,因此这里的行和列都是map的大小。然后我们看最复杂的data()函数。

 

Qt学习之路(45): 自定义model之一_qt_02QVariant CurrencyModel::data(const QModelIndex &index, int role) const 
Qt学习之路(45): 自定义model之一_qt_02
Qt学习之路(45): 自定义model之一_qt_02        if (!index.isValid()) 
Qt学习之路(45): 自定义model之一_qt_02                return QVariant(); 
Qt学习之路(45): 自定义model之一_qt_02 
Qt学习之路(45): 自定义model之一_qt_02        if (role == Qt::TextAlignmentRole) { 
Qt学习之路(45): 自定义model之一_qt_02                return int(Qt::AlignRight | Qt::AlignVCenter); 
Qt学习之路(45): 自定义model之一_qt_02        } else if (role == Qt::DisplayRole) { 
Qt学习之路(45): 自定义model之一_qt_02                QString rowCurrency = currencyAt(index.row()); 
Qt学习之路(45): 自定义model之一_qt_02                QString columnCurrency = currencyAt(index.column()); 
Qt学习之路(45): 自定义model之一_qt_02                if (currencyMap.value(rowCurrency) == 0.0) 
Qt学习之路(45): 自定义model之一_qt_02                        return "####"
Qt学习之路(45): 自定义model之一_qt_02                double amount = currencyMap.value(columnCurrency) / currencyMap.value(rowCurrency); 
Qt学习之路(45): 自定义model之一_qt_02                return QString("%1").arg(amount, 0, 'f', 4); 
Qt学习之路(45): 自定义model之一_qt_02        } 
Qt学习之路(45): 自定义model之一_qt_02        return QVariant(); 
Qt学习之路(45): 自定义model之一_qt_02}

 

data()函数返回单元格的数据。它有两个参数:第一个是QModelIndex,也就是单元格的位置;第二个是role,也就是这个数据的角色。这个函数的返回值是QVariant。至此,我们还是第一次见到这个类型。这个类型相当于是Java里面的Object,它把绝大多数Qt提供的数据类型都封装起来,起到一个数据类型“擦除”的作用。比如我们的table单元格可以是string,也可以是int,也可以是一个颜色值,那么这么多类型怎么返回呢?于是,Qt提供了这个QVariant类型,你可以把这很多类型都存放进去,到需要使用的时候使用一系列的to函数取出来即可。比如你把int包装成一个QVariant,使用的时候要用QVariant::toInt()重新取出来。这里需要注意的是,QVariant类型的放入和取出必须是相对应的,你放入一个int就必须按int取出,不能用toString(), Qt不会帮你自动转换。或许你会问,Qt不是提供了一个QObject类型吗?为什么不像Java一样都用Object呢?关于这一点豆子也没有官方文档,不过可以猜测一下。和Java不同,C++的面向对象体系不是单根的,C++对象并不是都继承于某一个类,因此,如果你要实现一个这种功能的类,做到“类型擦除”,就必须用一个类包含所有的数据类型。就相当于设计一个能放进所有形状的盒子,你才能把各种各样的形状放进去。这样的话这个类就会变得异常庞大。对于Qt,QObject类是大多数类继承的类,理应越小越好,因此就把这个功能抽取出来,形成了一个新类。这也只是豆子的猜测,大家不必往心里去:-)

 

好了,下面看这个类的内容。首先判断传入的index是不是合法,如果不合法直接return一个空白的QVariant。然后如果role是Qt::TextAlignmentRole,也就是文本的对象方式,那么就返回int(Qt::AlignRight | Qt::AlignVCenter);否则,role如果是Qt::DisplayRole,就按照我们前面所说的逻辑进行计算,然后按照字符串返回。这时候你就会发现,其实我们在if…else…里面返回的不是一种数据类型,if里面是int,而else里面是QString,这就是QVariant的作用了,也正是“类型擦除”的意思。

 

剩下的三个函数就很简单了:headerData()返回列名或者行名;setCurrencyMap()用于设置底层的数据源;currencyAt()返回偏移量为offset的键值。

 

至于调用就很简单了:

Qt学习之路(45): 自定义model之一_qt_02CurrencyTable::CurrencyTable() 
Qt学习之路(45): 自定义model之一_qt_02
Qt学习之路(45): 自定义model之一_qt_02        QMap<QString, double> data; 
Qt学习之路(45): 自定义model之一_qt_02        data["NOK"] = 1.0000; 
Qt学习之路(45): 自定义model之一_qt_02        data["NZD"] = 0.2254; 
Qt学习之路(45): 自定义model之一_qt_02        data["SEK"] = 1.1991; 
Qt学习之路(45): 自定义model之一_qt_02        data["SGD"] = 0.2592; 
Qt学习之路(45): 自定义model之一_qt_02        data["USD"] = 0.1534; 
Qt学习之路(45): 自定义model之一_qt_02 
Qt学习之路(45): 自定义model之一_qt_02        CurrencyModel *model = new CurrencyModel; 
Qt学习之路(45): 自定义model之一_qt_02        model->setCurrencyMap(data); 
Qt学习之路(45): 自定义model之一_qt_02 
Qt学习之路(45): 自定义model之一_qt_02        QTableView *view = new QTableView(this); 
Qt学习之路(45): 自定义model之一_qt_02        view->setModel(model); 
Qt学习之路(45): 自定义model之一_qt_02        view->resize(400, 300); 
Qt学习之路(45): 自定义model之一_qt_02}

 

好了,最后让我们来看一下最终结果吧!

 

<img title="2010-1-17" 22-06-01"="" border="0" alt="2010-1-17" src="https://s4.51cto.com/attachment/201001/17/448512_1263745286hudd.png" height="336" ?410?="" style="padding: 0px; margin: 0px; vertical-align: top; border: 0px;">

 

注意,这一章中的代码不是完整的代码,缺少view的头文件,不过这只是一个空白的文件。你也可以直接把view的代码放到main()函数里面运行。

本文出自 “豆子空间” 博客,请务必保留此出处http://devbean.blog.51cto.com/448512/267435