实现自定义模型(Implementing Custom Models)
2013-01-06 23:10
260 查看
转自:http://blog.csdn.net/iamdbl/article/details/3148135
Qt预定义的类已经提供了方便处理和显示数据的方式。然而,一些数据源不能直接使用这些定义好的模型,这就需要创建自定义的模型,优化对这些数据的处理。
在创建自定义模型之前,让我们首先回顾一下Qt的model/view结构的主要概念。在一个模型中每一个数据都有一个模型的索引(amodelindex)和一组属性,这些属性称为角色(roles),这些属性能够用任何类型的数据保存。在这一章的前几节最常用的角色为:Qt::DisplayRole和Qt::EditRole。其它角色有些是用来辅助性的数据,例如Qt::ToolTipRole,Qt::StatusTipRole和Qt::WhatsThisRole,还有一些是对显示属性的控制,如Qt::FontRole,Qt::TextAlignmentRole,Qt::TextColorRole,Qt::BackgroundColorRole。
Figure10.9.SchematicviewofQt'smodels
对一个列表模型(listmodel),用到的索引属性只有行号,QModelIndex::row()能够得到。对一个表格式模型(tablemodel),用到的索引属性为行号和列号,调用函数QModelIndex::row(),QModelIndex::column()得到。不论式列表模型还是表格模型,每一个项目(item)的父都是根项目(root),这个根项目用一个非法的QModelIndex得到。这一节的前两个例子说明怎么样创建一个自定义的表格模型。
一个树性模型(treemodel)和一个表格模型相似,但也略有不同。和表格模型一样,顶层父项目用一个非法的QModelIndex代替,但是每一个项目还能够有子项目,父项目能够用QModelIndex::parent()得到。每一个项目都有自己的角色数据,没有或者有多个子项目。由于项目能够把其它项目作为子项目,因此能够表示递归模型,最后一个例子说明了整个模型的使用。
本节的第一个例子是一个只读的模型,显示不同的货币对比。
Figure10.10.TheCurrenciesapplication
这个程序也可以使用一个简单的表格实现,但是我们想自定义一个模型对数据的存贮进行优化。如果我们想在一个表格中保存162中货币之间的比例关系,我们就需要保存162×162=26224个数据。如果使用下面的模型,我们只需要保存162个数据(每一种货币的对美元的比例)。
类CurrencyModel用一个标准的QTableView表示。模型中的数据类型为QMap<QString,double>;每一个key是一个货币代码,double型的数据表示为相对美元的比例。下面是一个代码段:
现在我们看一下类的定义,首先看头文件
我们选择QAbstractTableModel作为基类,因为QAbstractTableModel和我们的数据源最为匹配。Qt提供了一些基本的模型类,包括QAbstractListModel,QAbstractTableModel,QAbstractItemModel。QAbstractItemModel类用来支持广泛类型的模型,而QAbstractListModel和QAbstractTableModel类对一维数据和二维数据提供支持。
Figure10.11.Inheritancetreefortheabstractmodelclasses
对一个只读的表格模型,我我们必须重写三个函数rowCount(),columnCount()和data()。在这个例子中,我们还实现了headerData(),提供一个函数setCurrencyMap()来初始化数据。
在构造函数中,把父参数传递给基类。
对于本例中的表格模型,行数和列数为货币映射中的货币个数。父参数对表格模型来说没有意义,传递这个参数是因为rowCount()和columnCount()从QAbstractItemModel继承而来,基类是支持继承的。
函数data()返回任何一个项目中一个角色的数据值。这个项目由一个QModelIndex确定。对一个表格模型,需要知道的是行号和列号,通过函数row()和column()可以得到。
如果角色是Qt::TextAlignmentRole,返回一个适合显示数字的对齐方式。如果是显示角色Qt::DisplayRole,我们得到每一个货币关联的值,然后计算汇率。
我们把返回数值作为一个double处理,但是我们对数字的显示没有控制。因此,我们返回一个字符串,对它进行格式化处理。
函数headData()是视图(view)调用,显示水平方向和垂直方向的标题。参数section表示行号或者列号(根据方向可以确定)。由于在这个例子中行和列都显示货币代码,我们不需要关注方向,只要返回给定行号或者列号的货币代码就可以。
调用函数setCurrencyMap()能够改变货币种类。QAbstractItemModel::reset()通知视图,它们正在使用的模型数据是非法的,需要对可视部分数据进行更新。
函数currencyAt()返回给定偏移的键值(即货币代码),我们使用一个STL风格的遍历器得到对应的数据。
如上所述,我们发现创建一个只读的模型很简单,只需关注数据源的特点就可以,需要考虑数据在内存中的保存格式和读取的速度。下一个例子也是基于表格的,但是这里所有的数据都是用户输入的。
这个应用程序用来存贮任何两个城市之间的距离数据。和前面的例子一样,我们使用QTableWidget控件,每一个项目中存储每一个城市对的距离数值。然而,一个自定义的模型能够更加有效,因为一个城市A到城市B的距离和城市B到城市A的距离是一样的,因此,数据沿主对角线是镜像的。
为了说明自定义数据模型和普通表格的不同,我们假设有三个城市A,B,C,如果我们为每一个组合保存一个值,我们需要存储9个值。然而,一个自定义模型只需要三个值(A,B),(A,C),(B,C)。
Figure10.12.TheCitiesapplication
现在我们开始实现这个例子:
像在前一个例子中一样,我们必须重新实现一些函数,此外,我们必须重新实现函数setData()和flags(),让这个数据模型能够编辑,下面是它的类定义:
在这个模型中,我们使用两个数据结构:QStringList代表城市的名称,QVector<int>存储城市之间的距离值。
这个构造函数给基类传递了parent参数,其它什么都没有做。
我们把城市放在一个正方形的网格中,行数和列数就是城市的数量。
函数data()的实现与CurrencyModel里的实现相似。如果行列一样,此时两个城市是一样的,返回0,否则,它根据行数列数得到对应的城市,计算出距离。
函数headerData()返回行,列标题,在这个例子中表为正方形表,行数列数相等,这里我们返回对应的城市名称。
函数setData()在编辑表中项目时调用。只要模型的索引有效,两个城市不同,要修改的是数据的Qt::EditRole属性,函数就保存用户输入的距离值。
函数createIndex()生成指定行,列的模型索引。用这个函数我们得到与对角线对称的另一个项目的模型索引,这两个距离是相等地。我们用当前列数表示行数,用行数表示列数,就得到对应的项目。
如果我们改变了一个项目,就发送dataChanged()信号。由于一个项目改变后可能会引起不止一行一列改变,因此信号函数中有两个索引,由这两个索引表示一个由行列组成的矩形区域,一个代表变化的左上角项目索引,另一个代表变化右下角项目索引。同时对角位置的项目也改变了,也发出dataChanged()信号。函数返回值为true或者false,表示编辑是否成功。
函数flags()用来表示模型的项目能做的工作(如是否可以编辑)。抽象类QAbstractTableModel中这个函数的返回的是Qt::ItemIsSelectable和Qt::ItemIsEnabled。在CityModel中增加了Qt::ItemIsEditable。对角线上的值总是0,不能编辑。
如果重新设置的城市的列表,我们用新的列表代替原城市列表,重新计算距离数组distances,调用QAbstactItemModel::reset(),这个函数来通知这个模型的所有视图重新获取数据。
私有函数offsetOf()计算给定行,列的一个城市对的距离值在distances数组中的位置。例如,我们有四个城市A,B,C,D,如果用户改变了第3行第一列,在distances数组中的索引为3*(3-1)/2+1=4。如果用户改变了第一行第3列,调用qSwap()后,可以得到同样的偏移值。
Figure10.13.The
citiesand
distancesdatastructuresandthetablemodel
本节的最后一个例子是一个正则表达式的解析树模型。一个正则表达式包涵一个或者多个项(terms),项与项之间用“|”分开。因此表达式“alpha|bravo|Charlie”包含3个项。每一个项有包含多个因数(factors),例如bravo包含5个因数(每一个字母代表一个因数)。因数还可以进一步分解为原子(atom)和一个符号,如‘*’,‘+’,‘?’。一个正则表达式还可能包含表达式,解析可以是递归的
Figure10.14.TheRegexpParserapplication
RegExpParsery应用程序包含四个类:
·RegExpWindowis一个窗口,用户可以输入表达式,显示表达式的解析结构.
·RegExpParser,根据一个表达式生成一个解析树。
·RegExpModel用来表示解析树的一个树型模型。
·Node代表解析树中的一个项。
Node类定义如下:
每一个Node都有一个类型,一个字符串(可以为空),一个父节点(可以为空)和一个子节点列表(可以为空)。
在构造函数中初始化成员变量type和str。Node中所有的成员变量都是公有的,可以对type,string,parent,children直接操作。
函数qDeleteAll()遍历容器里所有的指针,然后进行删除。但是删除后它并不把指针设置为0,如果容器在析构函数以外使用,一般在语句的后面调用容器的clear()函数,清空所有的成员。
现在我们开始定义我的数据项(每一个数据项代表一个Node),首先我们创建一个模型:
这次我们需要一个有继承关系的模型,模型类没有从QAbstractTableModel继承,而是从QAbstractItemModel继承。我们必需要重新实现的函数基本一样,在这个类中我们必须重新实现的函数还有index()和parent()。为了设置模型的数据,我们调用setRootNode()函数,作为解析树的根节点。
在构造函数中,我们只是把根节点设置为0,把参数parent传递给基类。
在析构函数中我们删除根节点。如果根节点还有子节点,每一个子节点也会被Node的析构函数删除。
从新设置根节点前,我们删除原来的根节点(所有的子节点也一同删除)。然后设置新的根节点并调用call()函数,通知视图类刷新数据显示。
函数index()是QAbstractItemModel的重新实现。由模型类或者视图类需要得到一个子节点时调用(最顶层节点是一个无效的QModelIndex)。对于表格式和列表式的模型,QAbstractTableModel和QAbstractListModel的实现就是正确的,我们就不需要重新四实现这个函数。
在我们重新实现的index()函数中,如果没有根节点,我们返回一个无效的QModelIndex。如果有根节点,我们用行数,列数和子节点Node参数创建一个QModelIndex。对于有继承层次的模型,只知道行数,列数还不足以确定一个子节点。我们必须知道它的父节点。为了得到父节点,我们在QModelIndex中保存一个指向Node的指针。QModelIndex中除了可以保存一个行数,列数外,还可以保存一个void*指针或者一个int型数值。
子节点的Node*通过父节点的子节点列表得到。父节点由其QModelIndex索引和私有函数nodeFromIndex()得到:
函数nodeFromIndex()将给定索引的void*转换为一个Node*,如果给定的索引是无效的,就返回根节点rootNode,在模型类中,一个无效的模型索引指向的就是根节点。
对于一个给定的节点,行数就是它的子节点的个数。
列数在这个为2,第一列为节点的类型,第二列为节点的值。
通过一个子节点的QModelIndex得到父节点要比通过父节点得到子节点困难些。调用nodeFromIndex()可以得到父节点Node指针,但是要想得到父节点所在的行数,我们需要得到父节点的父节点,
在data()函数中,我们得到给定索引的Node*,利用这个指针得到数据。如果编辑方式不是Qt::DisplayRole,或者不能得到一个有效的指针,我们返回一个无效的QVariant类型的数据。如果是第0列,我们根据节点的类型设置显示的名称,如果是第1列,我们返回节点的值。
在函数headerData()中,我们返回水平方向的标题。QTreeView类在显示有层次结构的模型时没有竖直方向的标题,在函数中没有进行处理竖直方向的标题。
现在我们已经完成了Node和RegExpModel,当用户改变文本时,根节点就需要重新创建。
当用户改变输入的表达式时,主窗口的regExpChanged()就被调用。在这个函数中,解析器解析用户输入的表达式,解析器返回的指针作为解析树的根节点。
在这里没有介绍RegExpParser类,这个类不设计GUI和模型视图编程。完整的代码在书的CD上。
在这一节中,我们介绍了三个不同的自定义模型的创建。很多模型的项目和模型索引之间是一对一的关系,要比这里提到的要简单。更多的有关模型视图编程的例子可以参考Qt提供的文档。
RegExpParsery应用程序包含四个类:
·RegExpWindowis一个窗口,用户可以输入表达式,显示表达式的解析结构.
·RegExpParser,根据一个表达式生成一个解析树。
·RegExpModel用来表示解析树的一个树型模型。
·Node代表解析树中的一个项。
Node类定义如下:
每一个Node都有一个类型,一个字符串(可以为空),一个父节点(可以为空)和一个子节点列表(可以为空)。
在构造函数中初始化成员变量type和str。Node中所有的成员变量都是公有的,可以对type,string,parent,children直接操作。
函数qDeleteAll()遍历容器里所有的指针,然后进行删除。但是删除后它并不把指针设置为0,如果容器在析构函数以外使用,一般在语句的后面调用容器的clear()函数,清空所有的成员。
现在我们开始定义我的数据项(每一个数据项代表一个Node),首先我们创建一个模型:
这次我们需要一个有继承关系的模型,模型类没有从QAbstractTableModel继承,而是从QAbstractItemModel继承。我们必需要重新实现的函数基本一样,在这个类中我们必须重新实现的函数还有index()和parent()。为了设置模型的数据,我们调用setRootNode()函数,作为解析树的根节点。
在构造函数中,我们只是把根节点设置为0,把参数parent传递给基类。
在析构函数中我们删除根节点。如果根节点还有子节点,每一个子节点也会被Node的析构函数删除。
从新设置根节点前,我们删除原来的根节点(所有的子节点也一同删除)。然后设置新的根节点并调用call()函数,通知视图类刷新数据显示。
函数index()是QAbstractItemModel的重新实现。由模型类或者视图类需要得到一个子节点时调用(最顶层节点是一个无效的QModelIndex)。对于表格式和列表式的模型,QAbstractTableModel和QAbstractListModel的实现就是正确的,我们就不需要重新四实现这个函数。
在我们重新实现的index()函数中,如果没有根节点,我们返回一个无效的QModelIndex。如果有根节点,我们用行数,列数和子节点Node参数创建一个QModelIndex。对于有继承层次的模型,只知道行数,列数还不足以确定一个子节点。我们必须知道它的父节点。为了得到父节点,我们在QModelIndex中保存一个指向Node的指针。QModelIndex中除了可以保存一个行数,列数外,还可以保存一个void*指针或者一个int型数值。
子节点的Node*通过父节点的子节点列表得到。父节点由其QModelIndex索引和私有函数nodeFromIndex()得到:
函数nodeFromIndex()将给定索引的void*转换为一个Node*,如果给定的索引是无效的,就返回根节点rootNode,在模型类中,一个无效的模型索引指向的就是根节点。
对于一个给定的节点,行数就是它的子节点的个数。
列数在这个为2,第一列为节点的类型,第二列为节点的值。
通过一个子节点的QModelIndex得到父节点要比通过父节点得到子节点困难些。调用nodeFromIndex()可以得到父节点Node指针,但是要想得到父节点所在的行数,我们需要得到父节点的父节点,
在data()函数中,我们得到给定索引的Node*,利用这个指针得到数据。如果编辑方式不是Qt::DisplayRole,或者不能得到一个有效的指针,我们返回一个无效的QVariant类型的数据。如果是第0列,我们根据节点的类型设置显示的名称,如果是第1列,我们返回节点的值。
在函数headerData()中,我们返回水平方向的标题。QTreeView类在显示有层次结构的模型时没有竖直方向的标题,在函数中没有进行处理竖直方向的标题。
现在我们已经完成了Node和RegExpModel,当用户改变文本时,根节点就需要重新创建。
当用户改变输入的表达式时,主窗口的regExpChanged()就被调用。在这个函数中,解析器解析用户输入的表达式,解析器返回的指针作为解析树的根节点。
在这里没有介绍RegExpParser类,这个类不设计GUI和模型视图编程。完整的代码在书的CD上。
在这一节中,我们介绍了三个不同的自定义模型的创建。很多模型的项目和模型索引之间是一对一的关系,要比这里提到的要简单。更多的有关模型视图编程的例子可以参考Qt提供的文档。
Qt预定义的类已经提供了方便处理和显示数据的方式。然而,一些数据源不能直接使用这些定义好的模型,这就需要创建自定义的模型,优化对这些数据的处理。
在创建自定义模型之前,让我们首先回顾一下Qt的model/view结构的主要概念。在一个模型中每一个数据都有一个模型的索引(amodelindex)和一组属性,这些属性称为角色(roles),这些属性能够用任何类型的数据保存。在这一章的前几节最常用的角色为:Qt::DisplayRole和Qt::EditRole。其它角色有些是用来辅助性的数据,例如Qt::ToolTipRole,Qt::StatusTipRole和Qt::WhatsThisRole,还有一些是对显示属性的控制,如Qt::FontRole,Qt::TextAlignmentRole,Qt::TextColorRole,Qt::BackgroundColorRole。
Figure10.9.SchematicviewofQt'smodels
对一个列表模型(listmodel),用到的索引属性只有行号,QModelIndex::row()能够得到。对一个表格式模型(tablemodel),用到的索引属性为行号和列号,调用函数QModelIndex::row(),QModelIndex::column()得到。不论式列表模型还是表格模型,每一个项目(item)的父都是根项目(root),这个根项目用一个非法的QModelIndex得到。这一节的前两个例子说明怎么样创建一个自定义的表格模型。
一个树性模型(treemodel)和一个表格模型相似,但也略有不同。和表格模型一样,顶层父项目用一个非法的QModelIndex代替,但是每一个项目还能够有子项目,父项目能够用QModelIndex::parent()得到。每一个项目都有自己的角色数据,没有或者有多个子项目。由于项目能够把其它项目作为子项目,因此能够表示递归模型,最后一个例子说明了整个模型的使用。
本节的第一个例子是一个只读的模型,显示不同的货币对比。
Figure10.10.TheCurrenciesapplication
这个程序也可以使用一个简单的表格实现,但是我们想自定义一个模型对数据的存贮进行优化。如果我们想在一个表格中保存162中货币之间的比例关系,我们就需要保存162×162=26224个数据。如果使用下面的模型,我们只需要保存162个数据(每一种货币的对美元的比例)。
类CurrencyModel用一个标准的QTableView表示。模型中的数据类型为QMap<QString,double>;每一个key是一个货币代码,double型的数据表示为相对美元的比例。下面是一个代码段:
QMap<QString,double>currencyMap;
currencyMap.insert("AUD",1.3259);
currencyMap.insert("CHF",1.2970);
...
currencyMap.insert("SGD",1.6901);
currencyMap.insert("USD",1.0000);
CurrencyModelcurrencyModel;
currencyModel.setCurrencyMap(currencyMap);
QTableViewtableView;
tableView.setModel(¤cyModel);
tableView.setAlternatingRowColors(true);
现在我们看一下类的定义,首先看头文件
classCurrencyModel:publicQAbstractTableModel
{
public:
CurrencyModel(QObject*parent=0);
voidsetCurrencyMap(constQMap<QString,double>&map);
introwCount(constQModelIndex&parent)const;
intcolumnCount(constQModelIndex&parent)const;
QVariantdata(constQModelIndex&index,introle)const;
QVariantheaderData(intsection,Qt::Orientationorientation,
introle)const;
private:
QStringcurrencyAt(intoffset)const;
QMap<QString,double>currencyMap;
};
我们选择QAbstractTableModel作为基类,因为QAbstractTableModel和我们的数据源最为匹配。Qt提供了一些基本的模型类,包括QAbstractListModel,QAbstractTableModel,QAbstractItemModel。QAbstractItemModel类用来支持广泛类型的模型,而QAbstractListModel和QAbstractTableModel类对一维数据和二维数据提供支持。
Figure10.11.Inheritancetreefortheabstractmodelclasses
对一个只读的表格模型,我我们必须重写三个函数rowCount(),columnCount()和data()。在这个例子中,我们还实现了headerData(),提供一个函数setCurrencyMap()来初始化数据。
CurrencyModel::CurrencyModel(QObject*parent)
:QAbstractTableModel(parent)
{
}
在构造函数中,把父参数传递给基类。
intCurrencyModel::rowCount(constQModelIndex&/*parent*/)const
{
returncurrencyMap.count();
}
intCurrencyModel::columnCount(constQModelIndex&/*parent*/)const
{
returncurrencyMap.count();
}
对于本例中的表格模型,行数和列数为货币映射中的货币个数。父参数对表格模型来说没有意义,传递这个参数是因为rowCount()和columnCount()从QAbstractItemModel继承而来,基类是支持继承的。
QVariantCurrencyModel::data(constQModelIndex&index,introle)const
{
if(!index.isValid())
returnQVariant();
if(role==Qt::TextAlignmentRole){
returnint(Qt::AlignRight|Qt::AlignVCenter);
}elseif(role==Qt::DisplayRole){
QStringrowCurrency=currencyAt(index.row());
QStringcolumnCurrency=currencyAt(index.column());
if(currencyMap.value(rowCurrency)==0.0)
return"####";
doubleamount=currencyMap.value(columnCurrency)
/currencyMap.value(rowCurrency);
returnQString("%1").arg(amount,0,'f',4);
}
returnQVariant();
}
函数data()返回任何一个项目中一个角色的数据值。这个项目由一个QModelIndex确定。对一个表格模型,需要知道的是行号和列号,通过函数row()和column()可以得到。
如果角色是Qt::TextAlignmentRole,返回一个适合显示数字的对齐方式。如果是显示角色Qt::DisplayRole,我们得到每一个货币关联的值,然后计算汇率。
我们把返回数值作为一个double处理,但是我们对数字的显示没有控制。因此,我们返回一个字符串,对它进行格式化处理。
QVariantCurrencyModel::headerData(intsection,
Qt::Orientation/*orientation*/,
introle)const
{
if(role!=Qt::DisplayRole)
returnQVariant();
returncurrencyAt(section);
}
函数headData()是视图(view)调用,显示水平方向和垂直方向的标题。参数section表示行号或者列号(根据方向可以确定)。由于在这个例子中行和列都显示货币代码,我们不需要关注方向,只要返回给定行号或者列号的货币代码就可以。
voidCurrencyModel::setCurrencyMap(constQMap<QString,double>&map)
{
currencyMap=map;
reset();
}
调用函数setCurrencyMap()能够改变货币种类。QAbstractItemModel::reset()通知视图,它们正在使用的模型数据是非法的,需要对可视部分数据进行更新。
QStringCurrencyModel::currencyAt(intoffset)const
{
return(currencyMap.begin()+offset).key();
}
函数currencyAt()返回给定偏移的键值(即货币代码),我们使用一个STL风格的遍历器得到对应的数据。
如上所述,我们发现创建一个只读的模型很简单,只需关注数据源的特点就可以,需要考虑数据在内存中的保存格式和读取的速度。下一个例子也是基于表格的,但是这里所有的数据都是用户输入的。
这个应用程序用来存贮任何两个城市之间的距离数据。和前面的例子一样,我们使用QTableWidget控件,每一个项目中存储每一个城市对的距离数值。然而,一个自定义的模型能够更加有效,因为一个城市A到城市B的距离和城市B到城市A的距离是一样的,因此,数据沿主对角线是镜像的。
为了说明自定义数据模型和普通表格的不同,我们假设有三个城市A,B,C,如果我们为每一个组合保存一个值,我们需要存储9个值。然而,一个自定义模型只需要三个值(A,B),(A,C),(B,C)。
Figure10.12.TheCitiesapplication
现在我们开始实现这个例子:
QStringListcities;
cities<<"Arvika"<<"Boden"<<"Eskilstuna"<<"Falun"
<<"Filipstad"<<"Halmstad"<<"Helsingborg"<<"Karlstad"
<<"Kiruna"<<"Kramfors"<<"Motala"<<"Sandviken"
<<"Skara"<<"Stockholm"<<"Sundsvall"<<"Trelleborg";
CityModelcityModel;
cityModel.setCities(cities);
QTableViewtableView;
tableView.setModel(&cityModel);
tableView.setAlternatingRowColors(true);
像在前一个例子中一样,我们必须重新实现一些函数,此外,我们必须重新实现函数setData()和flags(),让这个数据模型能够编辑,下面是它的类定义:
classCityModel:publicQAbstractTableModel
{
Q_OBJECT
public:
CityModel(QObject*parent=0);
voidsetCities(constQStringList&cityNames);
introwCount(constQModelIndex&parent)const;
intcolumnCount(constQModelIndex&parent)const;
QVariantdata(constQModelIndex&index,introle)const;
boolsetData(constQModelIndex&index,constQVariant&value,
introle);
QVariantheaderData(intsection,Qt::Orientationorientation,
introle)const;
Qt::ItemFlagsflags(constQModelIndex&index)const;
private:
intoffsetOf(introw,intcolumn)const;
QStringListcities;
QVector<int>distances;
};
在这个模型中,我们使用两个数据结构:QStringList代表城市的名称,QVector<int>存储城市之间的距离值。
CityModel::CityModel(QObject*parent)
:QAbstractTableModel(parent)
{
}
这个构造函数给基类传递了parent参数,其它什么都没有做。
intCityModel::rowCount(constQModelIndex&/*parent*/)const
{
returncities.count();
}
intCityModel::columnCount(constQModelIndex&/*parent*/)const
{
returncities.count();
}
我们把城市放在一个正方形的网格中,行数和列数就是城市的数量。
QVariantCityModel::data(constQModelIndex&index,introle)const
{
if(!index.isValid())
returnQVariant();
if(role==Qt::TextAlignmentRole){
returnint(Qt::AlignRight|Qt::AlignVCenter);
}elseif(role==Qt::DisplayRole){
if(index.row()==index.column())
return0;
intoffset=offsetOf(index.row(),index.column());
returndistances[offset];
}
returnQVariant();
}
函数data()的实现与CurrencyModel里的实现相似。如果行列一样,此时两个城市是一样的,返回0,否则,它根据行数列数得到对应的城市,计算出距离。
QVariantCityModel::headerData(intsection,
Qt::Orientation/*orientation*/,
introle)const
{
if(role==Qt::DisplayRole)
returncities[section];
returnQVariant();
}
函数headerData()返回行,列标题,在这个例子中表为正方形表,行数列数相等,这里我们返回对应的城市名称。
boolCityModel::setData(constQModelIndex&index,
constQVariant&value,introle)
{
if(index.isValid()&&index.row()!=index.column()
&&role==Qt::EditRole){
intoffset=offsetOf(index.row(),index.column());
distances[offset]=value.toInt();
QModelIndextransposedIndex=createIndex(index.column(),
index.row());
emitdataChanged(index,index);
emitdataChanged(transposedIndex,transposedIndex);
returntrue;
}
returnfalse;
}
函数setData()在编辑表中项目时调用。只要模型的索引有效,两个城市不同,要修改的是数据的Qt::EditRole属性,函数就保存用户输入的距离值。
函数createIndex()生成指定行,列的模型索引。用这个函数我们得到与对角线对称的另一个项目的模型索引,这两个距离是相等地。我们用当前列数表示行数,用行数表示列数,就得到对应的项目。
如果我们改变了一个项目,就发送dataChanged()信号。由于一个项目改变后可能会引起不止一行一列改变,因此信号函数中有两个索引,由这两个索引表示一个由行列组成的矩形区域,一个代表变化的左上角项目索引,另一个代表变化右下角项目索引。同时对角位置的项目也改变了,也发出dataChanged()信号。函数返回值为true或者false,表示编辑是否成功。
Qt::ItemFlagsCityModel::flags(constQModelIndex&index)const
{
Qt::ItemFlagsflags=QAbstractItemModel::flags(index);
if(index.row()!=index.column())
flags|=Qt::ItemIsEditable;
returnflags;
}
函数flags()用来表示模型的项目能做的工作(如是否可以编辑)。抽象类QAbstractTableModel中这个函数的返回的是Qt::ItemIsSelectable和Qt::ItemIsEnabled。在CityModel中增加了Qt::ItemIsEditable。对角线上的值总是0,不能编辑。
voidCityModel::setCities(constQStringList&cityNames)
{
cities=cityNames;
distances.resize(cities.count()*(cities.count()-1)/2);
distances.fill(0);
reset();
}
如果重新设置的城市的列表,我们用新的列表代替原城市列表,重新计算距离数组distances,调用QAbstactItemModel::reset(),这个函数来通知这个模型的所有视图重新获取数据。
intCityModel::offsetOf(introw,intcolumn)const
{
if(row<column)
qSwap(row,column);
return(row*(row-1)/2)+column;
}
私有函数offsetOf()计算给定行,列的一个城市对的距离值在distances数组中的位置。例如,我们有四个城市A,B,C,D,如果用户改变了第3行第一列,在distances数组中的索引为3*(3-1)/2+1=4。如果用户改变了第一行第3列,调用qSwap()后,可以得到同样的偏移值。
Figure10.13.The
citiesand
distancesdatastructuresandthetablemodel
本节的最后一个例子是一个正则表达式的解析树模型。一个正则表达式包涵一个或者多个项(terms),项与项之间用“|”分开。因此表达式“alpha|bravo|Charlie”包含3个项。每一个项有包含多个因数(factors),例如bravo包含5个因数(每一个字母代表一个因数)。因数还可以进一步分解为原子(atom)和一个符号,如‘*’,‘+’,‘?’。一个正则表达式还可能包含表达式,解析可以是递归的
Figure10.14.TheRegexpParserapplication
RegExpParsery应用程序包含四个类:
·RegExpWindowis一个窗口,用户可以输入表达式,显示表达式的解析结构.
·RegExpParser,根据一个表达式生成一个解析树。
·RegExpModel用来表示解析树的一个树型模型。
·Node代表解析树中的一个项。
Node类定义如下:
classNode
{
public:
enumType{RegExp,Expression,Term,Factor,Atom,Terminal};
Node(Typetype,constQString&str="");
~Node();
Typetype;
QStringstr;
Node*parent;
QList<Node*>children;
};
每一个Node都有一个类型,一个字符串(可以为空),一个父节点(可以为空)和一个子节点列表(可以为空)。
Node::Node(Typetype,constQString&str)
{
this->type=type;
this->str=str;
parent=0;
}
在构造函数中初始化成员变量type和str。Node中所有的成员变量都是公有的,可以对type,string,parent,children直接操作。
Node::~Node()
{
qDeleteAll(children);
}
函数qDeleteAll()遍历容器里所有的指针,然后进行删除。但是删除后它并不把指针设置为0,如果容器在析构函数以外使用,一般在语句的后面调用容器的clear()函数,清空所有的成员。
现在我们开始定义我的数据项(每一个数据项代表一个Node),首先我们创建一个模型:
classRegExpModel:publicQAbstractItemModel
{
public:
RegExpModel(QObject*parent=0);
~RegExpModel();
voidsetRootNode(Node*node);
QModelIndexindex(introw,intcolumn,
constQModelIndex&parent)const;
QModelIndexparent(constQModelIndex&child)const;
introwCount(constQModelIndex&parent)const;
intcolumnCount(constQModelIndex&parent)const;
QVariantdata(constQModelIndex&index,introle)const;
QVariantheaderData(intsection,Qt::Orientationorientation,
introle)const;
private:
Node*nodeFromIndex(constQModelIndex&index)const;
Node*rootNode;
};
这次我们需要一个有继承关系的模型,模型类没有从QAbstractTableModel继承,而是从QAbstractItemModel继承。我们必需要重新实现的函数基本一样,在这个类中我们必须重新实现的函数还有index()和parent()。为了设置模型的数据,我们调用setRootNode()函数,作为解析树的根节点。
RegExpModel::RegExpModel(QObject*parent)
:QAbstractItemModel(parent)
{
rootNode=0;
}
在构造函数中,我们只是把根节点设置为0,把参数parent传递给基类。
RegExpModel::~RegExpModel()
{
deleterootNode;
}
在析构函数中我们删除根节点。如果根节点还有子节点,每一个子节点也会被Node的析构函数删除。
voidRegExpModel::setRootNode(Node*node)
{
deleterootNode;
rootNode=node;
reset();
}
从新设置根节点前,我们删除原来的根节点(所有的子节点也一同删除)。然后设置新的根节点并调用call()函数,通知视图类刷新数据显示。
QModelIndexRegExpModel::index(introw,intcolumn,
constQModelIndex&parent)const
{
if(!rootNode)
returnQModelIndex();
Node*parentNode=nodeFromIndex(parent);
returncreateIndex(row,column,parentNode->children[row]);
}
函数index()是QAbstractItemModel的重新实现。由模型类或者视图类需要得到一个子节点时调用(最顶层节点是一个无效的QModelIndex)。对于表格式和列表式的模型,QAbstractTableModel和QAbstractListModel的实现就是正确的,我们就不需要重新四实现这个函数。
在我们重新实现的index()函数中,如果没有根节点,我们返回一个无效的QModelIndex。如果有根节点,我们用行数,列数和子节点Node参数创建一个QModelIndex。对于有继承层次的模型,只知道行数,列数还不足以确定一个子节点。我们必须知道它的父节点。为了得到父节点,我们在QModelIndex中保存一个指向Node的指针。QModelIndex中除了可以保存一个行数,列数外,还可以保存一个void*指针或者一个int型数值。
子节点的Node*通过父节点的子节点列表得到。父节点由其QModelIndex索引和私有函数nodeFromIndex()得到:
Node*RegExpModel::nodeFromIndex(constQModelIndex&index)const
{
if(index.isValid()){
returnstatic_cast<Node*>(index.internalPointer());
}else{
returnrootNode;
}
}
函数nodeFromIndex()将给定索引的void*转换为一个Node*,如果给定的索引是无效的,就返回根节点rootNode,在模型类中,一个无效的模型索引指向的就是根节点。
intRegExpModel::rowCount(constQModelIndex&parent)const
{
Node*parentNode=nodeFromIndex(parent);
if(!parentNode)
return0;
returnparentNode->children.count();
}
对于一个给定的节点,行数就是它的子节点的个数。
intRegExpModel::columnCount(constQModelIndex&/*parent*/)const
{
return2;
}
列数在这个为2,第一列为节点的类型,第二列为节点的值。
QModelIndexRegExpModel::parent(constQModelIndex&child)const
{
Node*node=nodeFromIndex(child);
if(!node)
returnQModelIndex();
Node*parentNode=node->parent;
if(!parentNode)
returnQModelIndex();
Node*grandparentNode=parentNode->parent;
if(!grandparentNode)
returnQModelIndex();
introw=grandparentNode->children.indexOf(parentNode);
returncreateIndex(row,child.column(),parentNode);
}
通过一个子节点的QModelIndex得到父节点要比通过父节点得到子节点困难些。调用nodeFromIndex()可以得到父节点Node指针,但是要想得到父节点所在的行数,我们需要得到父节点的父节点,
QVariantRegExpModel::data(constQModelIndex&index,introle)const
{
if(role!=Qt::DisplayRole)
returnQVariant();
Node*node=nodeFromIndex(index);
if(!node)
returnQVariant();
if(index.column()==0){
switch(node->type){
caseNode::RegExp:
returntr("RegExp");
caseNode::Expression:
returntr("Expression");
caseNode::Term:
returntr("Term");
caseNode::Factor:
returntr("Factor");
caseNode::Atom:
returntr("Atom");
caseNode::Terminal:
returntr("Terminal");
default:
returntr("Unknown");
}
}elseif(index.column()==1){
returnnode->str;
}
returnQVariant();
}
在data()函数中,我们得到给定索引的Node*,利用这个指针得到数据。如果编辑方式不是Qt::DisplayRole,或者不能得到一个有效的指针,我们返回一个无效的QVariant类型的数据。如果是第0列,我们根据节点的类型设置显示的名称,如果是第1列,我们返回节点的值。
QVariantRegExpModel::headerData(intsection,
Qt::Orientationorientation,
introle)const
{
if(orientation==Qt::Horizontal&&role==Qt::DisplayRole){
if(section==0){
returntr("Node");
}elseif(section==1){
returntr("Value");
}
}
returnQVariant();
}
在函数headerData()中,我们返回水平方向的标题。QTreeView类在显示有层次结构的模型时没有竖直方向的标题,在函数中没有进行处理竖直方向的标题。
现在我们已经完成了Node和RegExpModel,当用户改变文本时,根节点就需要重新创建。
voidRegExpWindow::regExpChanged(constQString®Exp)
{
RegExpParserparser;
Node*rootNode=parser.parse(regExp);
regExpModel->setRootNode(rootNode);
}
当用户改变输入的表达式时,主窗口的regExpChanged()就被调用。在这个函数中,解析器解析用户输入的表达式,解析器返回的指针作为解析树的根节点。
在这里没有介绍RegExpParser类,这个类不设计GUI和模型视图编程。完整的代码在书的CD上。
在这一节中,我们介绍了三个不同的自定义模型的创建。很多模型的项目和模型索引之间是一对一的关系,要比这里提到的要简单。更多的有关模型视图编程的例子可以参考Qt提供的文档。
RegExpParsery应用程序包含四个类:
·RegExpWindowis一个窗口,用户可以输入表达式,显示表达式的解析结构.
·RegExpParser,根据一个表达式生成一个解析树。
·RegExpModel用来表示解析树的一个树型模型。
·Node代表解析树中的一个项。
Node类定义如下:
classNode
{
public:
enumType{RegExp,Expression,Term,Factor,Atom,Terminal};
Node(Typetype,constQString&str="");
~Node();
Typetype;
QStringstr;
Node*parent;
QList<Node*>children;
};
每一个Node都有一个类型,一个字符串(可以为空),一个父节点(可以为空)和一个子节点列表(可以为空)。
Node::Node(Typetype,constQString&str)
{
this->type=type;
this->str=str;
parent=0;
}
在构造函数中初始化成员变量type和str。Node中所有的成员变量都是公有的,可以对type,string,parent,children直接操作。
Node::~Node()
{
qDeleteAll(children);
}
函数qDeleteAll()遍历容器里所有的指针,然后进行删除。但是删除后它并不把指针设置为0,如果容器在析构函数以外使用,一般在语句的后面调用容器的clear()函数,清空所有的成员。
现在我们开始定义我的数据项(每一个数据项代表一个Node),首先我们创建一个模型:
classRegExpModel:publicQAbstractItemModel
{
public:
RegExpModel(QObject*parent=0);
~RegExpModel();
voidsetRootNode(Node*node);
QModelIndexindex(introw,intcolumn,
constQModelIndex&parent)const;
QModelIndexparent(constQModelIndex&child)const;
introwCount(constQModelIndex&parent)const;
intcolumnCount(constQModelIndex&parent)const;
QVariantdata(constQModelIndex&index,introle)const;
QVariantheaderData(intsection,Qt::Orientationorientation,
introle)const;
private:
Node*nodeFromIndex(constQModelIndex&index)const;
Node*rootNode;
};
这次我们需要一个有继承关系的模型,模型类没有从QAbstractTableModel继承,而是从QAbstractItemModel继承。我们必需要重新实现的函数基本一样,在这个类中我们必须重新实现的函数还有index()和parent()。为了设置模型的数据,我们调用setRootNode()函数,作为解析树的根节点。
RegExpModel::RegExpModel(QObject*parent)
:QAbstractItemModel(parent)
{
rootNode=0;
}
在构造函数中,我们只是把根节点设置为0,把参数parent传递给基类。
RegExpModel::~RegExpModel()
{
deleterootNode;
}
在析构函数中我们删除根节点。如果根节点还有子节点,每一个子节点也会被Node的析构函数删除。
voidRegExpModel::setRootNode(Node*node)
{
deleterootNode;
rootNode=node;
reset();
}
从新设置根节点前,我们删除原来的根节点(所有的子节点也一同删除)。然后设置新的根节点并调用call()函数,通知视图类刷新数据显示。
QModelIndexRegExpModel::index(introw,intcolumn,
constQModelIndex&parent)const
{
if(!rootNode)
returnQModelIndex();
Node*parentNode=nodeFromIndex(parent);
returncreateIndex(row,column,parentNode->children[row]);
}
函数index()是QAbstractItemModel的重新实现。由模型类或者视图类需要得到一个子节点时调用(最顶层节点是一个无效的QModelIndex)。对于表格式和列表式的模型,QAbstractTableModel和QAbstractListModel的实现就是正确的,我们就不需要重新四实现这个函数。
在我们重新实现的index()函数中,如果没有根节点,我们返回一个无效的QModelIndex。如果有根节点,我们用行数,列数和子节点Node参数创建一个QModelIndex。对于有继承层次的模型,只知道行数,列数还不足以确定一个子节点。我们必须知道它的父节点。为了得到父节点,我们在QModelIndex中保存一个指向Node的指针。QModelIndex中除了可以保存一个行数,列数外,还可以保存一个void*指针或者一个int型数值。
子节点的Node*通过父节点的子节点列表得到。父节点由其QModelIndex索引和私有函数nodeFromIndex()得到:
Node*RegExpModel::nodeFromIndex(constQModelIndex&index)const
{
if(index.isValid()){
returnstatic_cast<Node*>(index.internalPointer());
}else{
returnrootNode;
}
}
函数nodeFromIndex()将给定索引的void*转换为一个Node*,如果给定的索引是无效的,就返回根节点rootNode,在模型类中,一个无效的模型索引指向的就是根节点。
intRegExpModel::rowCount(constQModelIndex&parent)const
{
Node*parentNode=nodeFromIndex(parent);
if(!parentNode)
return0;
returnparentNode->children.count();
}
对于一个给定的节点,行数就是它的子节点的个数。
intRegExpModel::columnCount(constQModelIndex&/*parent*/)const
{
return2;
}
列数在这个为2,第一列为节点的类型,第二列为节点的值。
QModelIndexRegExpModel::parent(constQModelIndex&child)const
{
Node*node=nodeFromIndex(child);
if(!node)
returnQModelIndex();
Node*parentNode=node->parent;
if(!parentNode)
returnQModelIndex();
Node*grandparentNode=parentNode->parent;
if(!grandparentNode)
returnQModelIndex();
introw=grandparentNode->children.indexOf(parentNode);
returncreateIndex(row,child.column(),parentNode);
}
通过一个子节点的QModelIndex得到父节点要比通过父节点得到子节点困难些。调用nodeFromIndex()可以得到父节点Node指针,但是要想得到父节点所在的行数,我们需要得到父节点的父节点,
QVariantRegExpModel::data(constQModelIndex&index,introle)const
{
if(role!=Qt::DisplayRole)
returnQVariant();
Node*node=nodeFromIndex(index);
if(!node)
returnQVariant();
if(index.column()==0){
switch(node->type){
caseNode::RegExp:
returntr("RegExp");
caseNode::Expression:
returntr("Expression");
caseNode::Term:
returntr("Term");
caseNode::Factor:
returntr("Factor");
caseNode::Atom:
returntr("Atom");
caseNode::Terminal:
returntr("Terminal");
default:
returntr("Unknown");
}
}elseif(index.column()==1){
returnnode->str;
}
returnQVariant();
}
在data()函数中,我们得到给定索引的Node*,利用这个指针得到数据。如果编辑方式不是Qt::DisplayRole,或者不能得到一个有效的指针,我们返回一个无效的QVariant类型的数据。如果是第0列,我们根据节点的类型设置显示的名称,如果是第1列,我们返回节点的值。
QVariantRegExpModel::headerData(intsection,
Qt::Orientationorientation,
introle)const
{
if(orientation==Qt::Horizontal&&role==Qt::DisplayRole){
if(section==0){
returntr("Node");
}elseif(section==1){
returntr("Value");
}
}
returnQVariant();
}
在函数headerData()中,我们返回水平方向的标题。QTreeView类在显示有层次结构的模型时没有竖直方向的标题,在函数中没有进行处理竖直方向的标题。
现在我们已经完成了Node和RegExpModel,当用户改变文本时,根节点就需要重新创建。
voidRegExpWindow::regExpChanged(constQString®Exp)
{
RegExpParserparser;
Node*rootNode=parser.parse(regExp);
regExpModel->setRootNode(rootNode);
}
当用户改变输入的表达式时,主窗口的regExpChanged()就被调用。在这个函数中,解析器解析用户输入的表达式,解析器返回的指针作为解析树的根节点。
在这里没有介绍RegExpParser类,这个类不设计GUI和模型视图编程。完整的代码在书的CD上。
在这一节中,我们介绍了三个不同的自定义模型的创建。很多模型的项目和模型索引之间是一对一的关系,要比这里提到的要简单。更多的有关模型视图编程的例子可以参考Qt提供的文档。
相关文章推荐
- 10-3 实现自定义模型(Implementing Custom Models)
- 10-3 实现自定义模型(Implementing Custom Models)
- Qt中 实现自定义模型(Implementing Custom Models)
- 【b2c】商品自定义模型的研究就部分实现
- JBPM通过AssignmentHandler绑定自定义用户模型实现用户授权
- Coursera deeplearning.ai 深度学习笔记4-2-Deep Convolutional Models Case Studies-深度卷积模型案例及代码实现
- (五)Qt实现自定义模型基于QAbstractItemModel
- 【b2c】商品自定义模型的研究就部分实现二
- 织梦自定义模型如何实现采集功能
- (四)Qt实现自定义模型基于QAbstractTableModel (一般)
- Surface Shaders 内置灯光模型(Build in lighting models)Lighting.cginc 和如何自定义灯光类型
- C++ GUI Programming with Qt 4 - 10.3 实现自定义模型
- DEDECMS实现自定义表单(模型)分步提交实现思路
- FlUENT并行UDF实现:自定义NOx速率模型,炉膛温度及其相对标准差,空燃比等
- YII开发技巧分享——模型(models)中rules自定义验证规则
- MVC重写DefaultModelBinder实现自定义模型绑定
- Unity3D 自定义光照模型实现
- PHP YII框架开发小技巧之模型(models)中rules自定义验证规则
- Qt实现自定义模型基于QAbstractTableModel
- PHP YII框架开发小技巧之模型(models)中rules自定义验证规则