模型子类化参考
模型的子类需要提供很多在QAbstractItemModel中定义的虚函数的实现。需要实现的方法的数量取决于你想创建的子类的风格——它提供一个简单的列表视图,还是一个表格视图,或者是一个复杂的层次视图。从QAbstractListModel和QAbstractTableModel继承的子类可以直接利用这两个类的许多默认的虚函数。
子类中需要实现的方法可以分为三种:
1. 处理项数据:所有的模型需要实现方法来保证视图和委托能够查询模型的尺寸、检测每个项以及返回其中的数据。
2. 浏览和创建索引:具有层次关系的模型需要实现方法来使得视图可以调用它们来它们公开的树形结构,并获得每个项的模型索引。
3. 支持拖拽操作和处理MIME类型数据:模型继承方法来控制内部和外部的拖拽行为的实现方式。这些方法允许项数据被描述为其他的组件和应用能够接受的MIME类型。
项数据管理
对于模型中的数据,模型可以提供多种等级的访问:只读, 调整大小和编辑。
只读访问
一个模型如果想实现对数据的只读访问,这些方法必须被子类实现:
Flags():这个方法被其他的组件使用来获得该模型的每个项数据的信息。标志位的组合应当包括Qt::ItemIsEnabled和Qt::ItemIsSelectable。
Data():用来把项数据提供给视图和委托。一般地,模型只需要为Qt::DisplayRole和其他的特定应用的用户角色。但是把数据提供给Qt::ToolTipRole和Qt::AccessibleTextRole和Qt::AccessibleDescriptionRole同样是很好的选择。请参阅Qt::ItemDataRole枚举值手册来获取与每个角色有关的类型的信息。
headerData():提供能显示标头信息的视图。能够显示标头信息的视图才能得到这些信息。
rowCount():提供模型公开的数据的行数。
这4个方法在任何类型的模型中(包括列表模型,即QAbstractListModel及其子类,和表格模型,即QAbstractTableModel及其子类)都必须被实现。
另外,以下的方法必须在QAbstractTableModel和QAbstractItemModel的直接子类中实现:
columnCount():提供模型公开的数据的列数,列表模型不需要提供它的实现因为它已经在QAbstractListModel中实现了。
可编辑项
可编辑模型允许项数据被更改,也可以提供方法来插入或者删除行和列。要使能编辑功能,以下这些方法必须被正确实现:
Flags():必须为每个项返回正确的标识位组合。特别地,除了提供给只读模型中的数据项的值以外,还必须包括Qt::ItemIsEditable。
setData():用来改变一个与特定的模型索引相联系的项的值。为了能够接受用户接口元素提供的用户输入,这个方法必须处理与Qt::EditRole相关联的数据。这个实现也可以接受与其他的Qt::EditRole值相关联的数据。在修改数据完成后,模型必须发射dataChanged()信号来通知其他的组件这里发生了改变。
setHeaderData():用来改变行或列的标头信息。在修改完成后,模型必须发射headerDataChanged()信号来通知其他的组件这里发生了改变。
可变大小模型
所有种类的模型都支持行的插入和删除。表格模型和层次模型还支持列的插入和删除。在模型的维度发生改变之前和之后,让其他的组件意识到这种变化是很重要的。所以,可以通过实现以下这些方法来改变模型的大小,但是这些方法的实现必须保证调用合适的方法来使与模型相关的视图和委托意识到变化:
insertRows():用来给所有类型的模型添加新行和新的数据项。该方法的实现必须在向任何底层数据结构插入新行之前调用beginInsertRows()方法,并且在插入完成后立即调用endInsertRows()方法。
removeRows():用来删除任何模型包含的行和数据项。该方法的实现必须在从任何底层数据结构删除行之前调用beginRemoveRows()方法,并且在删除完成后立即调用endRemoveRows()方法。
insertColumns():用来给表格模型或层次模型添加新列和新的数据项。该方法的实现必须在向任何底层数据结构插入新行之前调用beginInsertColumns()方法,并且在插入完成后立即调用endInsertColumns()方法。
removeColumns():用来删除表格模型或层次模型包含的行和数据项。该方法的实现必须在从任何底层数据结构删除行之前调用beginRemoveColumns()方法,并且在删除完成后立即调用endRemoveColumns()方法。
一般地,这些方法在成功执行之后应该返回true。然而在一些情况下,这些行为可能只成功了一部分;比如,能够被插入的行数比指定的行数要少。在这种情况下,模型应当返回false来宣布失败,从而使能任何相关的组件来处理这种情况。
改变大小的API的实现过程中调用的方法所发射的信号会给与模型相关联的组件一个在任何数据变得不可用之前采取行动的机会。带有开始和结束方法的插入和删除方法的封装也使得模型能够正确处理长期模型索引。
一般情况下,开始和结束方法能够把该模型的底层数据结构的变化通知给其他的部件。但是对于比较复杂的数据结构的变化而言,比如数据的内部重组或者排序,应当发射layoutChanged()信号来使得任何视图获得更新。
模型数据的懒加载
模型数据的懒加载有效地允许有关模型信息的请求被拖延到视图真正需要它的时候才发送。
有些模型需要从远程数据库中获得数据,或者由于数据的组织方式的原因,在获取数据时必须进行非常耗时的操作。为了精确地显示模型数据,视图一般会尽可能多地请求数据,限制返回给他们的信息量从而减少不必要的数据跟进的请求是非常有用的。
在某些层次模型中,查找给定的项的孩子的成本是很高的。懒加载能有效地保证模型的rowCount()方法只在必要的时候调用。在这种情况下,我们可以通过重新实现hasChildren()方法来为视图确认子项的存在和在QTreeView视图下给父项合适的修饰提供代价不高的方法。
无论hasChildren()方法的重新实现的返回值是true还是false,都不会影响视图调用rowCount()方法来查询子项的个数。比如,在QTreeView视图中,如果父项没有展开(来显示子项),那么该视图就并不需要知道一共有多少子项。
很多项都有子项,让hasChildren()任何时候都返回true有时是一个很有用方法。这可以保证每个项都可以尽可能晚地检查子项,从而使得初始化模型数据的项目时尽可能地快速。这样做唯一的缺点就是,直到用户试图查看一个并不存在的子项之前,没有子项的项目可能会错误地显示。
浏览和模型索引的创建
层次模型需要提供方法,以便视图浏览它们所关联的树形结构以及获得项目的模型索引。
父项和子项
由于与视图相关联的结构是由底层的数据结构决定的,每个子类化的模型类都有责任通过实现以下这些方法来创建它自己的模型索引。:
index():给定父项的索引模型。这个方法允许视图和委托访问该项的子项。如果找不到与参数中的行、列、父项相对应的项目,这个方法必须返回一个无效的模型索引:QModelIndex()。
parent():提供一个任何给定的子项的父项的模型索引。如果这个方法是由模型中的顶级项目调用的,或者在模型中没有有效的父项,这个方法必须返回一个由空的QModelIndex()构造函数构造的无效的模型索引。
上述的两个方法都会使用createIndex()工厂函数来创建供其他组件使用的索引。模型一般都会给这个方法提供独有的标识符来保证模型索引可以在以后与对应的项目重新关联。
拖拽动作的支持和MIME类型的处理
模型/视图类支持拖拽操作,并且提供适用于很多应用的默认行为。但是,我们依然可以自定义在拖拽行为中项的编码方式。比如默认情况下项目是移动还是复制,以及如何插入到已存在的模型中。
另外,便捷视图类实现了紧贴开发者预期目的专门的方法。Convenience Views部分提供了这个行为的概述。
MIME data
默认情况下,内建模型和视图使用一个内部的MIME类型(application/x-qabstractitemmodeldatalist)来在模型索引之间传输信息。该MIME类型具体说明了列表中项的数据,包括每个项所在的行和列,和每个项支持的角色的信息。
用这个MIME类型编码的数据可以通过调用QAbstractItemModel::mimeData()方法获得。这个方法的参数的数据类型是QModelIndexList,它包含着需要被序列化的项。
当在一个自定义的项中实现拖拽支持时,可以通过实现以下方法来以特定的格式导出项的数据:
mimeData():这个方法可以被重新实现来返回不同于默认的application/x-qabstractitemmodeldatalist内部格式的数据。子类可以通过基类获得默认的QMimeData对象并以其他的格式向对象添加数据。
对于很多模型来说,以共同的诸如text/plain和image/png的MIME类型表示的格式来提供项的内容是很有用的。注意,通过QMimeData::setImageData()、QMimeData::setColorData()和QMimeData::setHtml()方法可以很容易地将图片、颜色和HTML文档添加到一个QMimeData对象中。
接收放下数据
当一个拖拽操作在视图上发生时,底层模型会被要求决定它能支持哪种操作,以及它能接受哪种MIME类型。这个信息由QAbstractItemModel::supportedDropActions()和QAbstractItemModel::mimeTypes()方法提供。不重载QAbstractItemModel所提供的实现的模板,支持复制操作和默认的内部的项的MIME类型。
当串行化的数据被放在一个视图上时,数据会被模板的QAbstractItemModel::dropMimeData()方法插入到当前的模板中。这个方法的默认实现不会覆盖模型中原有的任何数据;而是会将这个项的数据作为某个项的同级或者孩子。
为了利用QAbstractItemModel的默认实现以获得内建的MIME类型,新的模板必须提供以下方法的重新实现:
insertRows(),insertColumns():这两个方法允许模型使用QAbstractItemModel::dropMimeData()方法来自动插入新的数据。
setData():允许新行和新列中写入数据。
setItemData():这个方法为了写入新数据提供了更有效的支持。
为了接受其它类型的数据,以下方法必须被重新实现:
supportedDropActions():用来返回一个“放下”动作的组合,它指出了这个模型支持哪几种拖拽操作。
mimeTypes():用来返回该模型可以解码和处理的MIME类型的集合。一般地,能够被输入到模型中的类型也可以被外部的组件编码来使用。
dropMimeData():实现对拖拽操作传输过来的数据的解码操作,决定数据放在模型的什么位置,并且在必要的时候插入新的行和列。在子类中这个方法如何实现取决于每个模型的数据需求。
如果dropMimeData()方法的实现通过插入行和列改变了模型的维度,或者有的数据项被修改了,一定要注意保证相关的信号都被发射了。在其它的方法的实现中简单地调用诸如setData(),insertRows(),insertColumns()这样的函数是很有用的,因为这可以保证模型正常运转。
为了保证拖拽操作正常运行,实现以下方法来从模型中删除数据是很重要的。
removeRows()
removeRow()
removeColumns()
removeColumn()
更多关于项视图上拖拽的信息,请参阅Using drag and drop with item views。
便捷视图类
便捷视图类(QListWidget,QTableWidget和QTreeWidget)重载了默认的拖拽方法从而提供了低灵活性、高自然度的、适合于许多应用的拖拽行为。比如,把数据拖入到一个QTableWidget的单元中并覆盖原有的数据是一般性的行为,底层模型将会重新设置该单元中的数据而不是在模型中插入新的行和列。更多关于便捷视图类中的拖拽操作的信息,请参阅Using drag and drop with item views。
处理大量数据的最优化操作
canFetchMore()方法检查父项是否有更多可获得的数据并根据结果返回true或者false。fetchMore()方法用来从指定的父项中取得数据。这些方法都可以被组合使用,比如,对于含有用于移动QAbstractItemModel的增量数据的数据库查询,我们可以重新实现canFetchMore()方法来查询是否还有需要移动的数据并且重新实现fetchMore()方法来按需求移动数据。
另一个例子是动态移动树形模型,当该模型的某个分支需要被延伸时,我们重新实现fetchMore()方法。
如果在你的fetchMore()方法的重新实现过程中给模型添加了新行,你需要调用begInInsertRows()方法和endInsertRows()方法。同时,canFetchMore()和fetchMore()方法也都必须被重新实现,因为这两个方法的默认实现返回false而且什么都不做。