Model/View Programming
Introduction to Model/View Programming
Qt提供一组项视图类, 它们使用模型/视图体系结构, 管理数据及用户显示数据方式之间的关系. 这个体系结果引入的功能分离为开发人员提供更大的灵活性, 并提供一个标准模型接口, 允许广泛的数据源与现有视图一起使用. 本文简要介绍模型/视图范式, 概述所涉及的概念, 描述项视图系统的体系结构, 解释体系结构中的每个组件, 并通过示例展示如何使用所提供的类.
The model/view architecture
模型-视图-控制 (MVC) 是一种源自Smalltalk的设计模式. 在 设计模式中, Gamma 等人写道:
MVC 由三种对象组成. Model是应用程序对象, View是屏幕显示, Controller定义用户界面对输入的反应方式. 在MVC之前, 用户界面设计倾向于将这些对象组合在一起. MVC将它们解耦, 增加灵活性和重用性.
如果视图和控制组合在一起, 结果就是模型/视图结构. 这仍然将数据的存储与向用户呈现数据的方式分开, 但是基于同一原则, 我们可以提供一个更简单的框架. 这种分离可以实现在多个不同的视图中显示相同的数据, 并在实现新视图时, 无需更改底层数据结构. 为了灵活处理用户输入, 我们引入委托. 在这个框架中增加委托的优点是: 允许自定义数据的渲染和编辑方式.
![]() | 模型/视图结构 模型与数据源通信, 为体系结构中的其他组件提供 接口. 如何通信取决于数据源类型和模型的实现方式. 视图使用模型索引从模型检索数据. 在标准视图中, 委托渲染数据项. 编辑数据项时, 委托使用模型索引直接与模型通信. |
通常, 模型/视图类可以分为上述三组类: 模型, 视图和委托. 每种组件都由一个抽象类定义, 这些抽象类提供通用接口; 某些情况下, 还提供默认的函数实现. 抽象类通过子类化, 以便提供其他组件所期望的全套功能; 这也允许编写专门的组件.
模型, 视图, 代理使用 信号和槽函数通信:
- 模型信号通知视图, 数据源保存的数据的更改.
- 视图信号提供关于用户与正在显示的项目的交互的信息.
- 委托信号在编辑过程中用于告知模型和视图编辑器的状态.
模型
所有模型类派生自 QAbstractItemModel. 这个类定义视图和委托访问数据的接口. 模型不一定存储数据; 它可以持有数据结构或数据对象, 如文件, 数据库或其他应用程序组件.
模型类参见 Model Classes.
QAbstractItemModel 提供了一组数据接口, 它足够灵活, 可以处理以表, 列表和树形式表示数据的视图. 然而, 当你需要实现列表和类表数据结构的新模型时, QAbstractListModel 和 QAbstractTableModel 是更好的起点, 因为它们提供了公共函数的适当默认实现. 这些类中的每一个都可以子类化, 以提供支持特定类型的列表和表的模型.
有关模型的子类化过程参见 Creating New Models.
Qt提供一些现成的模型:
- QStringListModel : 存储 QString 列表.
- QStandardItemModel : 管理树形项目, 每一项包含任意数据.
- QFileSystemModel : 提供本地系统的文件和目录信息.
- QSqlQueryModel, QSqlTableModel, QSqlRelationalTableModel 使用模型/视图访问数据库.
若上述标准模型无法满足你的要求, 你可以子类化 QAbstractItemModel, QAbstractListModel, QAbstractTableModel, 自定义模型.
视图
Qt提供不同类型的视图: QListView 显示项列表, QTableView 在表格中显示模型数据, QTreeView 将模型数据显示成树型结构. 这些类基于 QAbstractItemView. 虽然这些类已经提供完整实现, 但它们也可以被子类化, 自定义视图.
视图类参见 View Classes.
委托
QAbstractItemDelegate 是模型/视图框架的基本抽象类. QStyledItemDelegate提供默认的委托实现, 是Qt的标准视图的默认委托. 但是, QStyledItemDelegate 和 QItemDelegate 是为视图中的项提供编辑和绘制的独立替代方案. 它们之间的区别是 QStyledItemDelegate 使用当前样式绘制项. 因此, 我们建议自定义委托或使用Qt样式表时, 基类使用 QStyledItemDelegate.
委托类参见 Delegate Classes.
排序
在模型/视图体系结构中, 有两种不同的排序方法; 选择哪种方法取决于你的模型.
如果你的模型是可排序的, 即, 如果它重新实现 QAbstractItemModel::sort() 函数, QTableView 和 QTreeView 提供一个API, 允许你使用编程方式对模型数据排序. 此外, 你还可以将QHeaderView::sortIndicatorChanged()信号分别连接QTableView::sortByColumn()或QTreeView::sortByColumn(), 启动交互式排序 (即. 允许用户单击视图标题对数据排序).
还有一种方法, 如果你的模型没有所需接口, 或你想使用列表视图显示数据, 那么你可以在视图显示数据之前, 使用代理模型转换模型结构. 详见 Proxy Models.
便捷类
许多便捷类从标准的视图类派生而来, 以利于依赖Qt基于项的项视图和表类的应用程序. 它们不打算被子类化.
这些类有 QListWidget, QTreeWidget, QTableWidget.
这些类不如视图类灵活, 不能与任意模型一起使用. 我们建议你使用模型/视图方法处理项视图中的数据, 除非你强烈需要一组基于项的类.
如果你希望在使用基于项的界面的同时, 利用模型/视图方法提供的功能, 请考虑使用视图类, 如 QListView, QTableView, QTreeView, 并使用 QStandardItemModel.
使用模型和视图
下面章节讨论如何在Qt中使用模型/视图模式. 每个章节包含一个示例, 展示如何创建新组件.
两个Qt模型
QStandardItemModel 和 QFileSystemModel是Qt提供的两个标准模型. QStandardItemModel 是多用途模型, 它可以表示多种数据结构, 用于列表, 表格, 树形视图. 这些模型也持有项的数据. QFileSystemModel 是一个存储目录及文件信息的模型. 它不能存储任意数据元素, 仅代表本地系统的文件和目录.
QFileSystemModel 提供一个可供实验的现有模型, 简单配置就能使用现有数据. 我们使用这个模型展示如何为标准视图设置模型, 及如何使用模型索引操作数据.
使用已存在的模型/视图
QListView 和 QTreeView 是最适合与 QFileSystemModel一起使用的视图. 下面示例, 在树形视图中显示目录内容, 旁边的列表视图显示同样内容. 视图共享用户选择, 以便在两个视图中高亮显示所选文件或目录.
我们创建一个 QFileSystemModel, 然后创建两个视图显示目录内容. 这是使用模型最简单的方法. 在 main()
函数中构建和使用模型:
int main(int argc, char *argv[]) { QApplication app(argc, argv); QSplitter *splitter = new QSplitter; QFileSystemModel *model = new QFileSystemModel; model->setRootPath(QDir::currentPath());
调用 setRootPath(), 设置模型的数据为当前目录.
我们创建两个视图, 以便用两种不同的方式显示模型数据:
QTreeView *tree = new QTreeView(splitter); tree->setModel(model); tree->setRootIndex(model->index(QDir::currentPath())); QListView *list = new QListView(splitter); list->setModel(model); list->setRootIndex(model->index(QDir::currentPath()));
视图的创建方式与其他widget一样. 调用 setModel(), 参数是模型对象指针, 设置视图的模型. 然后, 调用 setRootIndex() 设置当前显示的数据, 参数是调用QFileSystemModel::index()返回的 模型索引.
模型索引类参见 Model Classes.
main()函数的剩余部分设置QSplitter的标题, 并运行应用程序的事件循环:
splitter->setWindowTitle("Two views onto the same file system model"); splitter->show(); return app.exec(); }
在上面的例子中, 我们忽略如何处理项目的选择. 详见 Handling Selections in Item Views.
模型类
在了解如何处理选择前, 先知道模型/视图框架的概念很有用.
基本概念
在模型/视图框架中, 模型提供视图和委托访问数据的标准接口. 在Qt中, 标准接口由抽象类 QAbstractItemModel 定义. 无论底层数据具有何种结构, QAbstractItemModel 的所有子类都将数据表示为表格形式的分层结构. 视图利用这个约束访问模型中的数据, 但是它们向用户显示信息的方式不受限制.
模型还通过信号和槽函数机制向视图通知数据更改的信息.
This section describes some basic concepts that are central to the way items of data are accessed by other components via a model class. More advanced concepts are discussed in later sections.
模型索引
模型索引的引入是为了分离数据结构和访问方式. 模型的每条信息都是通过索引获取. 视图和委托使用索引请求显示的数据项.
因此, 只有模型需要知道如何获取数据, 并能自由地定义数据类型. 模型索引包含一个指向创建它们的模型指针, 防止处理多个模型时可能出现的混淆现象.
QAbstractItemModel *model = index.model();
模型索引提供对数据的临时引用, 用于模型检索或修改数据. 模型可能随时更新内部数据结构, 因此, 模型索引可能无效, 不应存储. 如果你需要持久引用一条数据, 则必须创建一个 持久模型索引. This provides a reference to the information that the model keeps up-to-date. 临时模型索引由 QModelIndex 提供, 持久模型索引由 QPersistentModelIndex 提供.
要想获取与数据项对应的模型索引, 必须指定三个属性: 行号, 列号和索引的父项的模型索引. 下面章节详细描述和解释这些特性.
行和列
在最基本的形式中, 模型可以作为一个简单的表格访问, 其中的项目按行号和列号排列. 这并不表明底层数据存储在数组结构中; 行列编号的使用只是允许组件相互通信的约定. 我们可以通过在模型中指定任何给定项的行号和列号来检索有关该项的信息, 并且我们会收到一个表示该项的索引:
QModelIndex index = model->index(row, column, ...);
为列表和表等简单的单层数据结构提供接口的模型不需要提供任何其他信息, 但正如上面的代码所示, 我们在获取模型索引时需要提供更多信息.
![]() | Rows and columns 左图显示了一个基本表模型, 其中每个项都由一对行号和列号定位. 我们将相关的行和列编号传递给模型, 获得引用数据项的模型索引. QModelIndex indexA = model->index(0, 0, QModelIndex()); QModelIndex indexB = model->index(1, 1, QModelIndex()); QModelIndex indexC = model->index(2, 1, QModelIndex()); 在模型中, 顶级项是父项, 是一个空的 |
父项
对于列表或表格形式的数据, 模型提供的访问接口比较理想, 行和列编号能精确地映射到视图显示项. 但是, 树视图等结构要求模型提供更灵活的接口. 因此, 每个项也可以是另一个项的父项, 就像树视图中的顶级项可以包含另一个项列表.
当请求模型项的索引时, 我们必须提供有关该项的父项信息. 在模型之外, 引用项的唯一方法是通过模型索引, 因此还必须给出父模型索引:
QModelIndex index = model->index(row, column, parent);
![]() | 父项, 行, 列 图示显示一个树形模型, 每个项具有父项, 行号, 列号. "A" 和 "C" 表示顶级项的同级子项: QModelIndex indexA = model->index(0, 0, QModelIndex()); QModelIndex indexC = model->index(2, 1, QModelIndex()); "A" 有一组子项, 子项 "B" 的获取方式如下: QModelIndex indexB = model->index(1, 0, indexA); |
项的角色
模型中的项可以为其他组件执行各种 角色, 从而允许为不同的情况提供不同类型的数据. 例如, Qt::DisplayRole 用于访问可以在视图中显示为文本的字符串. 通常, 项包含许多不同角色的数据, 标准角色由 Qt::ItemDataRole定义.
我们可以通过向模型传递与项对应的模型索引, 并通过指定角色来获得我们想要的数据类型, 从而向模型询问项的数据:
QVariant value = model->data(index, role);
![]() | 项的角色 角色向模型指示要引用的数据类型. 视图可以以不同的方式显示角色, 因此为每个角色提供适当的信息很重要. Creating New Models 章节更详细地介绍角色的一些特定用途. |
Qt::ItemDataRole中定义的标准角色涵盖了项目数据的最常见用途. 通过为每个角色提供适当的项目数据, 模型可以向视图和委托提供关于如何向用户显示项目的提示. 不同类型的视图可以根据需要自由解释或忽略这些信息, 还可以为应用程序定义其他角色.
总结
- 模型索引独立于任何底层数据结构, 为视图和委托提供模型中项的位置信息.
- 项通过行号, 列号及父项的模型索引引用.
- 模型索引由模型创建, 辅助其他组件(如视图和委托)的访问模型.
- 如果在调用 index()请求索引时为父项指定了有效的模型索引, 则返回的索引将引用模型中该父项下的子项.
- 如果在调用 index()请求索引时为父项指定了无效的模型索引, 则返回的索引将引用模型中的顶级项.
- 角色 区分与项相关联的不同类型的数据.
使用模型索引
为了演示如何使用模型索引从模型中检索数据, 我们设置了一个没有视图的 QFileSystemModel, 并在widget中显示文件和目录的名称. 尽管这并不是使用模型的正常方式, 但它展示了模型在处理模型索引时使用的约定.QFileSystemModel加载是异步的, 以最大限度地减少系统资源的使用. 在处理这种模式时, 我们必须考虑到这一点.
如下所示, 我们创建一个文件系统模型:
QFileSystemModel *model = new QFileSystemModel; QModelIndex parentIndex = model->index(QDir::currentPath()); int numRows = model->rowCount(parentIndex);
在上面的代码中, 我们创建一个默认的 QFileSystemModel. 然后, 连接一个lambda表达式, 在lambda中, 我们调用 index() 获取父索引, 并调用 rowCount() 获取模型的行数.最后, 调用setRootPath设置QFileSystemModel的根目录, 加载数据, 触发lambda表达式.
简便起见, 我们只获取模型每一行的首个项目索引, 并读取存储的数据.
for (int row = 0; row < numRows; ++row) { QModelIndex index = model->index(row, 0, parentIndex);
为获取模型索引, 我们为所需的所有项的父对象指定行号, 列号(第一列是零)和适当的模型索引. 调用 data() , 并传递 DisplayRole 和模型参数, 获取存储在每个项中的文本.
QString text = model->data(index, Qt::DisplayRole).toString(); // Display the text in a widget. }
上述示例展示从模型检索数据的基本原则:
- 调用 rowCount() 和 columnCount()获取模型的维度. 这些函数通常需要指定父模型索引.
- 模型索引用于访问模型的项, 需要行号, 列号和父模型索引.
- 对于模型的顶级项, 使用空的
QModelIndex()
作为父索引. - 项包含不同的角色数据. 若需要获取特定角色的数据, 必须向模型提供模型索引和角色名.
扩展阅读
新模型可以派生自 QAbstractItemModel, 并实现相应的标准接口. 在 Creating New Models 一节, 我们创建一个模型展现这一点, 这个模型保存字符串列表.
视图类
概述
在模型/视图体系结构中, 视图从模型获取数据, 并呈现给用户. 数据的呈现方式不依赖模型的数据表示, 且可能与底层数据结构完全不同.
内容和显示的完全分离通过 QAbstractItemModel提供的标准模型接口, QAbstractItemView提供的标准视图接口, 及通用方式表示数据的模型索引实现. 视图通常管理从模型中获得的数据的总体布局. 它们可以自己渲染单个数据项, 也可以使用委托处理渲染和编辑功能.
除显示数据外, 视图还处理项之间的导航, 项选择等. 视图还实现了基本的用户界面功能, 如上下文菜单和拖放. 视图可以为项提供默认的编辑功能, 也可以与 委托 一起提供自定义编辑器.
在没有模型的情况下, 你可以创建视图, 但是你必须提供模型, 才能显示有用信息. 视图可以通过选择器追踪用户选择的项, 你可以为每个视图维护一个选择器, 也可以在多个视图间共享选择器.
某些视图(如 QTableView 和 QTreeView)显示标头, 这也是由视图类QHeaderView实现. 标题通常访问与视图相同的模型. 标题调用 QAbstractItemModel::headerData() 从模型检索数据, 通常以标签的形式显示头信息. 自定义标头从 QHeaderView 派生, 为视图提供更专业的标签.
使用现有视图
Qt提供三种视图类. QListView 以列表方式显示模型中的项, 也可以显示经典图标. QTreeView 以树形方式显示模型中的项, 允许以紧凑的方式表示深度嵌套的结构. QTableView 以表格方式显示模型中的项, 类似电子表格应用程序的布局.
标准视图的默认行为对大多数应用程序应该足够了. 它们提供基本的编辑功能, 并可以根据需求自定义.
使用模型
我们创建一个模型示例, 一个字符串列表模型, 并创建一个视图, 显示模型内容:
int main(int argc, char *argv[]) { QApplication app(argc, argv); // Unindented for quoting purposes: QStringList numbers; numbers << "One" << "Two" << "Three" << "Four" << "Five"; QAbstractItemModel *model = new StringListModel(numbers);
注意: StringListModel
继承自 QAbstractItemModel. 这种方式允许我们使用模型的抽象接口, 即使我们替换成其他模型, 代码仍然正常工作.
如下所示, 我们创建一个 QListView 视图对象, 显示字符串列表模型:
QListView *view = new QListView; view->setModel(model);
视图采用常用方式显示:
view->show(); return app.exec(); }
视图渲染模型内容, 并通过模型接口访问数据. 用户编辑项时, 视图使用默认委托提供编辑功能.
上面的代码展示如何利用 QListView 显示字符串列表模型数据. 编辑模型时, 视图自动使用默认委托编辑每一个模型项.
多个视图共用一个模型
多个视图共用一个模型很简单. 如下代码, 我们创建两个表格视图, 并设置相同的模型:
QTableView *firstTableView = new QTableView; QTableView *secondTableView = new QTableView; firstTableView->setModel(model); secondTableView->setModel(model);
模型/视图体系结构使用信号和槽函数通信, 改变模型会传递给所有相关视图, 确保每个视图总是访问相同的数据.
上述代码展示两个不同的视图共用相同模型, 每个视图包含不同的选中项. 虽然模型数据在视图中显示一致, 但是每个视图都有内置的选择模型. 有些应用程序要求共享选择模型.
处理项的选择
QItemSelectionModel 提供处理视图中项选择机制. 默认情况下, 所有标准视图都会创建自己的选择模型. 你可以调用 selectionModel() 获取选择模型, 调用 setSelectionModel()替换选择模型. 当我们希望在同一模型数据上提供多个一致的视图时, 控制视图使用的选择模型的能力非常有用.
通常, 除非你正在对模型或视图进行子类化, 否则不需要直接操作选择的内容. 但是, 如果需要, 可以访问选择模型的接口, 详见 Handling Selections in Item Views.
视图间共享选择模型
尽管, 视图提供的默认选择模型很方便, 但是, 多个视图共享同一个模型时, 所有的视图一致地显示用户的选择很有必要. 由于视图类允许替换内部的选择模型, 我们可以通过如下方式实现视图间的统一选择:
secondTableView->setSelectionModel(firstTableView->selectionModel());
第二个视图设置第一个视图的选择模型. 两个视图使用同一个选择模型, 从而保证数据和所选项的同步.
上述代码示例, 两个相同类型的视图展示相同的模型数据. 然后, 如果使用两个不同类型的视图, 则所选项在每个视图的表示方式可能非常不同; 例如, 表格视图的连续选择可以表示为树视图的高亮显示项的片段集.
委托类
概述
不像模型-视图-控制模式, 模型/视图设计不包括用于管理与用户交互的完全独立的组件. 通常, 视图负责向用户呈现模型数据, 并处理用户输入. 为了在输入方式上具有一定的灵活性, 交互由委托执行. 这些组件提供输入功能, 还负责视图中项的显示. QAbstractItemDelegate 提供标准的委托接口.
委托可以重新实现 paint() 和 sizeHint() 渲染内容. 但是, 基于widget的简单委托可以子类化 QItemDelegate, 而不是 QAbstractItemDelegate, 利用QItemDelegate提供的默认实现.
委托的编辑器可以使用widget管理编辑过程, 也可以直接处理事件实现编辑. 第一种方式稍后介绍, 也可查看示例 Spin Box Delegate.
Pixelator 示例展示如何创建一个自定义委托, 这个委托为表视图执行专门的渲染.
使用已存在的委托
Qt标准视图使用 QItemDelegate 实例提供编辑功能. 委托的默认实现为每个标准视图( QListView, QTableView, QTreeView)提供常用样式.
所有标准角色都由标准视图使用的默认委托处理, 详见 QItemDelegate.
视图调用 itemDelegate() 函数获取委托. 调用setItemDelegate() 设置委托, 并且在为自定义视图设置委托时必须使用此函数.
简单的委托
在这里, 我们使用 QSpinBox 为委托提供编辑功能, 显示整数. 尽管, 我们已经自定义一个整数的表模型, 但是, 我们可以很容易使用 QStandardItemModel 自定义委托, 控制数据输入. 我们构造一个表视图显示模型内容, 并使用自定义委托编辑内容.
我们从 QStyledItemDelegate 派生一个委托子类, 并重新实现函数管理编辑widget:
class SpinBoxDelegate : public QStyledItemDelegate { Q_OBJECT public: SpinBoxDelegate(QObject *parent = 0); QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setEditorData(QWidget *editor, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; };
注意: 创建委托对象时, 并没有创建编辑widget. 我们仅在需要时创建编辑widget.
提供一个编辑器
在示例中, 当表视图需要编辑器时, 它要求委托提供一个合适的编辑widget. createEditor() 创建一个要求的编辑widget:
QWidget *SpinBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &/* index */) const { QSpinBox *editor = new QSpinBox(parent); editor->setFrame(false); editor->setMinimum(0); editor->setMaximum(100); return editor; }
注意: 我们并不需要保存编辑widget的指针, 在不需要它时, 视图负责销毁.
我们在编辑器上初始化委托的默认事件过滤器, 确保它提供用户期望的标准编辑快捷方式. 也可以在编辑器中上添加其他快捷方式, 增加更复杂的行为; 参见 Editing Hints.
视图调用QStyledItemDelegate::setEditorData(), QStyledItemDelegate::updateEditorGeometry()设置编辑器的数据和几何图形. 我们可以根据视图提供的模型索引创建不同的编辑器. 例如, 如果我们有一列整数和一列字符串, 那么我们可以根据模型索引的列号创建 QSpinBox
或 QLineEdit
.
委托必须重新实现setEditorData(), 把模型数据的数据复制到编辑器, 如下代码所示, 我们读取存储在模型中的角色数据, 设置QSpinBox的值.
void SpinBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { int value = index.model()->data(index, Qt::EditRole).toInt(); QSpinBox *spinBox = static_cast<QSpinBox*>(editor); spinBox->setValue(value); }
In this example, we know that the editor widget is a spin box, but we could have provided different editors for different types of data in the model, in which case we would need to cast the widget to the appropriate type before accessing its member functions.
将数据保存到模型
用户在QSpinBox中编辑完数据, 视图调用 setModelData() 函数把数据保存到模型中.
void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QSpinBox *spinBox = static_cast<QSpinBox*>(editor); spinBox->interpretText(); int value = spinBox->value(); model->setData(index, value, Qt::EditRole); }
因为视图管理委托的编辑widget, 我们仅需要使用编辑器数据更新模型. 这种情况下, 我们获取QSpinBox的值更新模型索引指定的数据.
QItemDelegate 发出 closeEditor() 信号, 通知视图何时完成编辑. 这个视图确保编辑器widget关闭并销毁. 在这个例子中, 我们只提供简单的编辑工具, 所以我们永远不需要发出这个信号.
所有对数据的操作都是通过 QAbstractItemModel提供的接口执行的. 这使得委托在很大程度上独立于它所处理的数据类型, 但为了使用某些类型的编辑器widget, 必须做出一些假设. 在本例中, 我们假设模型始终包含整数值, 但我们仍然可以将此委托用于不同类型的模型, 因为 QVariant 为意外数据提供了合理的默认值.
更新编辑器的几何大小
委托负责管理编辑器的几何大小. 创建编辑器, 更改项目在视图中的大小或位置时, 必须设置widget的几何大小. 幸运的是, 视图在QStyleOptionViewItem对象内, 提供所有必要的几何图形信息.
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const { editor->setGeometry(option.rect); }
在这种情况下, 我们仅使用QStyleOptionViewItem提供的几何大小. 委托不会直接使用项矩形. 它会使用编辑器相对于项中的其他元素位置.
Editing hints
编辑后, 委托发送 closeEditor() 信号, 通知其他组件编辑结果, 以便提供后续编辑操作提示. 默认情况下, 这个信号由 QItemDelegate 事件过滤器处理, QItemDelegate构造时, 将其初始化在QSpinBox上.
QSpinBox的行为可以调整. 在 QItemDelegate的默认筛选器中, 如果用户点击 Return 后, 委托将值提供给模型, 并关闭QSpinBox. 我们可以在QSpinBox上自定义事件过滤器, 提供自定义提示; 例如, 我们在收到 closeEditor() 信号(提示标识是 EditNextItem )后, 自动启动下一个编辑项.
另一种方案是不重新实现事件过滤器, 而是子类化 QSpinBox. 这种方式可以更好的控制编辑器行为. 两种方式对比, 自定义事件过滤器更便捷.
委托不必发出这些提示, 但是无法满足多数应用程序的需求, 且降低可用性.
处理视图选择
概述
视图中使用的选择模型提供基于模型/视图体系结构的选择描述. 尽管, 标准的选择模型满足多数应用程序的要求, 但是, 选择模型允许自定义, 以满足特殊的模型和视图要求.
视图的选择项存储在 QItemSelectionModel 实例中. 这个类为单个模型的项维护模型索引, 独立于任何视图. 由于模型可能有多个视图, 因此, 这种方式允许多个视图共享选择, 为应用程序提供一致的显示方式.
选择由 选择范围组成. 这些只记录每个选定项范围的开始和结束模型索引, 有效地维护了关于大量项的选择信息. 项的非连续选择由多个选择范围描述.
选择是应用于选择模型所拥有的模型索引的集合. 项的最新选择称为 当前选择. The effects of this selection can be modified even after its application through the use of certain types of selection commands. These are discussed later in this section.
当前项和选定项
视图中始终存在一个当前项和一个选定项, 这两个状态是独立的. 一个项可以同时是当前项和选择项. 视图确保始终存在当前项, 键盘导航必须有当前项.
下表列出当前项和选择项的区别.
Current Item | Selected Items |
---|---|
There can only be one current item. | There can be multiple selected items. |
The current item will be changed with key navigation or mouse button clicks. | The selected state of items is set or unset, depending on several pre-defined modes - e.g., single selection, multiple selection, etc. - when the user interacts with the items. |
The current item will be edited if the edit key, F2, is pressed or the item is double-clicked (provided that editing is enabled). | The current item can be used together with an anchor to specify a range that should be selected or deselected (or a combination of the two). |
The current item is indicated by the focus rectangle. | The selected items are indicated with the selection rectangle. |
在处理选择时, 你可以使用 QItemSelectionModel 记录模型中所有项的选择状态. 一旦设置选择模型, 项就有选择或非选择两种状态. 你可以随时检索所有选定项的索引, 使用信号和槽函数机制向其他组件通知选择状态的变化.
使用选择模型
标准视图提供的选择模型可以满足多数应用程序的要求. 调用 selectionModel() 获取选择模型, 调用 setSelectionModel() 在视图间共享选择模型.
创建选择模型时, 要求提供一个模型和一对 QItemSelection 的模型索引. 索引指向模型的项, 并解释为项的左上和右下项. 如果想要将选择应用到模型的项上, 那么需要将选择提交到选择模型; Qt提供多种实现方式, 每种方式对选择模型中已存在的选择项有不同的影响.
选择项
为了演示选择的一些基本特性, 我们构建了一个共有32项的自定义表模型实例, 并打开其数据的表视图:
TableModel *model = new TableModel(8, 4, &app); QTableView *table = new QTableView(0); table->setModel(model); QItemSelectionModel *selectionModel = table->selectionModel();
首先, 我们获取表视图的默认选择模型, 我们不修改模型中的任何项, 而是选择视图左上角的多个项. 为此, 我们获取选择区域的左上角和右下角对应项的模型索引:
QModelIndex topLeft; QModelIndex bottomRight; topLeft = model->index(0, 0, QModelIndex()); bottomRight = model->index(5, 2, QModelIndex());
为了选择模型中的项, 并显示在视图中, 我们构造一个选择对象, 并应用于选择模型:
QItemSelection selection(topLeft, bottomRight); selectionModel->select(selection, QItemSelectionModel::Select);
使用由 选择标志组合定义的标识将选择应用于选择模型. 在这种情况下, 所使用的标志会导致记录在选择对象中的项被包括在选择模型中, 而不管它们以前的状态如何. 所得到的选择由视图显示.
选择可以使用选择标识修改. 选择可能具有复杂的结构, 它由选择模型表示. 我们研究如何更新选择时, 将采用不同的选择标识操作所选项.
读取选择状态
调用 selectedIndexes() 获取选择模型中的模型索引. This returns an unsorted list of model indexes that we can iterate over as long as we know which model they are for:
QModelIndexList indexes = selectionModel->selectedIndexes(); QModelIndex index; foreach(index, indexes) { QString text = QString("(%1,%2)").arg(index.row()).arg(index.column()); model->setData(index, text); }
The above code uses Qt's convenient foreach keyword to iterate over, and modify, the items corresponding to the indexes returned by the selection model.
选择模型发出信号表示选择中的变化. 我们可以将 selectionChanged() 信号连接到一个槽函数, 并在选择发生变化时检查模型中被选中或取消选中的项. 槽函数的参数类型是 QItemSelection: 一个包含与新选择的项相对应的索引列表; 另一个包含与新取消选择的项相对应的索引.
在下面的代码中, 我们定义一个槽函数, 接收 selectionChanged() 信号, 使用字符串填充所选项, 并清除取消选择项的内容.
void MainWindow::updateSelection(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndex index; QModelIndexList items = selected.indexes(); foreach (index, items) { QString text = QString("(%1,%2)").arg(index.row()).arg(index.column()); model->setData(index, text); } items = deselected.indexes(); foreach (index, items) model->setData(index, ""); }
我们可以使用 currentChanged() 信号跟踪当前项. 第一个参数是之前的当前项, 第二个参数时当前项.
在下面的代码中, 我们提供一个接收 currentChanged() 信号的槽函数, 并使用提供的信息更新 QMainWindow的状态栏:
void MainWindow::changeCurrent(const QModelIndex ¤t, const QModelIndex &previous) { statusBar()->showMessage( tr("Moved from (%1,%2) to (%3,%4)") .arg(previous.row()).arg(previous.column()) .arg(current.row()).arg(current.column())); }
使用这些信号可以直接监控用户的选择, 但我们也可以直接更新选择模型.
更新选择
选择命令由 QItemSelectionModel::SelectionFlag定义的选择标志组合提供. 每个选择标志告诉选择模型在调用 select() 函数时如何更新所选项的内部记录. 最常用的标志是 Select 标志, 它指示选择模型将指定的项记录为已选择. Toggle 标志使选择模型反转指定项目的状态, 选择给定的任何取消选择的项, 并取消选择任何当前选择的项. Deselect 选择标志取消选择所有指定的项.
通过创建项选择并将其应用于选择模型, 可以更新选择模型中的各个项. 在下面的代码中, 我们将第二个项选择应用于上面显示的表模型, 使用 Toggle 命令反转给定项的选择状态.
QItemSelection toggleSelection; topLeft = model->index(2, 1, QModelIndex()); bottomRight = model->index(7, 3, QModelIndex()); toggleSelection.select(topLeft, bottomRight); selectionModel->select(toggleSelection, QItemSelectionModel::Toggle);
这个操作的结果显示在表格视图中, 提供了一种方便的方式来可视化我们选择的结果:
默认情况下, 选择命令仅操作模型索引指定的各个项. 但是, 用于描述选择命令的标志可以与其他标志组合, 以更改整行和整列. 例如, 如果只使用一个索引调用 select() , 但使用的命令是 Select 和 Rows的组合, 则会选择包含引用项的整行. 以下代码演示 Rows 和 Columns 标志的使用:
QItemSelection columnSelection; topLeft = model->index(0, 1, QModelIndex()); bottomRight = model->index(0, 2, QModelIndex()); columnSelection.select(topLeft, bottomRight); selectionModel->select(columnSelection, QItemSelectionModel::Select | QItemSelectionModel::Columns); QItemSelection rowSelection; topLeft = model->index(0, 0, QModelIndex()); bottomRight = model->index(1, 0, QModelIndex()); rowSelection.select(topLeft, bottomRight); selectionModel->select(rowSelection, QItemSelectionModel::Select | QItemSelectionModel::Rows);
尽管只有四个索引提供给选择模型, 但使用 Columns 和 Rows 选择标志意味着选择了两列和两行. 下图显示这两个选择的结果:
在示例模型上执行的命令都涉及累积模型中的项选择. 它可以清除所选项, 也可以用新选择项替换当前所选项.
要用新选择项替换当前选择项, 请将其他选择标志与 Current 标志组合. 使用此标志的命令指示选择模型将其当前的模型索引集合替换为 select()调用中指定的索引集合. 要在开始添加新选项之前清除所有选择项, 请将其他选择标志与 Clear 标志组合. 这具有重置选择模型的模型索引集合的效果.
选择模型的所有项
要选择模型中的所有项, 需要为模型的每个级别创建一个选择, 该选择涵盖该级别中的所有项. 我们通过检索具有给定父索引的左上和右下项目对应的索引来实现这一点:
QModelIndex topLeft = model->index(0, 0, parent); QModelIndex bottomRight = model->index(model->rowCount(parent)-1, model->columnCount(parent)-1, parent);
用这些索引和模型构造一个选择. 然后在选择模型中选择相应的项:
QItemSelection selection(topLeft, bottomRight); selectionModel->select(selection, QItemSelectionModel::Select);
需要对模型中的所有级别执行此操作. 对于顶级项, 我们将以通常的方式定义父索引:
QModelIndex parent = QModelIndex();
对于层次模型, hasChildren() 函数用于确定任何给定项是否是另一级别项的父项.
创建新模型
模型/视图组件之间的功能分离允许创建可以利用现有视图的模型. 这种方法使我们能够使用标准的图形用户界面组件(如 QListView, QTableView, QTreeView)显示来自各种来源的数据.
QAbstractItemModel 类提供了一个足够灵活的接口, 可以支持以分层结构排列信息的数据源, 从而允许以某种方式插入, 删除, 修改或排序数据. 它还支持拖放操作.
QAbstractListModel 和 QAbstractTableModel 类为更简单的非分层数据结构的接口提供了支持, 并且更容易用作简单列表和表模型的起点.
在本节中, 我们将创建一个简单的只读模型探索模型/视图体系结构的基本原理. 在本节的后面, 我们将调整这个简单的模型, 以便用户可以修改项.
有关更复杂模型的示例, 参见 Simple Tree Model.
有关QAbstractItemModel子类的要求, 参见 Model Subclassing Reference.
为现有数据结构创建新模型时, 重要的是要考虑应使用哪种类型的模型提供数据接口. 如果数据结构可以表示为项的列表或表, 则可以子类化 QAbstractListModel 或 QAbstractTableModel, 因为这些类为许多函数提供了合适的默认实现.
然而, 如果底层数据结构只能用层次树结构表示, 则有必要子类化 QAbstractItemModel. Simple Tree Model 示例采用这种方法.
在本节中, 我们实现了一个基于字符串列表的简单模型, 因此 QAbstractListModel 提供了一个理想的基类.
无论底层数据结构采用何种形式, 通常最好在专用模型中用允许对底层数据结构进行更自然访问的标准 QAbstractItemModel API 进行补充. 这使得用数据填充模型变得更容易, 但仍然允许其他通用模型/视图组件使用标准API与其交互. 下面描述的模型仅为此目的提供了一个自定义构造函数.
只读的模型示例
这里实现的模型是一个基于标准 QStringListModel 类, 简单的, 无层次的, 只读的数据模型. 它有一个 QStringList 作为其内部数据源, 并且只实现制作一个功能模型所需的内容. 为了使实现更容易, 我们子类化 QAbstractListModel 因为它为列表模型定义了合理的默认行为, 并且它公开了一个比 QAbstractItemModel 更简单的接口.
实现模型时, 重要的是要记住: QAbstractItemModel 本身不存储任何数据, 它只是提供了视图用于访问数据的接口. 对于最小只读模型, 只需要实现几个函数, 因为大多数接口都有默认实现. 类声明如下:
class StringListModel : public QAbstractListModel { Q_OBJECT public: StringListModel(const QStringList &strings, QObject *parent = 0) : QAbstractListModel(parent), stringList(strings) {} int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; private: QStringList stringList; };
除模型的构造函数外, 我们只需要实现两个函数: rowCount() 返回模型的行数, data() 返回模型索引对应的数据项.
headerData() 为树视图和表视图提供一些可以在其头标题中显示的内容.
注意: 这是一个非层次模型, 因此我们不必担心父子关系. 如果我们的模型是分层的, 我们还必须实现 index() 和 parent() 函数.
字符串列表内部存储在 stringList
私有成员变量中.
模型的大小
我们希望模型中的行数与字符串列表中的字符串数相同. 我们实现 rowCount() 函数时考虑到了这一点:
int StringListModel::rowCount(const QModelIndex &parent) const { return stringList.count(); }
由于模型是非层次的, 我们可以安全地忽略与父项对应的模型索引. 默认情况下, 从 QAbstractListModel 派生的模型只包含一列, 因此我们不需要重新实现 columnCount() 函数.
模型标题和数据
对于视图中的项, 我们希望返回字符串列表中的字符串. data() 函数负责返回与索引参数对应的数据项:
QVariant StringListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= stringList.size()) return QVariant(); if (role == Qt::DisplayRole) return stringList.at(index.row()); else return QVariant(); }
只有当提供的模型索引有效, 行号在字符串列表中的项目范围内, 并且请求的角色是我们支持的角色时, 我们才会返回有效的 QVariant.
某些视图(如 QTreeView 和 QTableView)可以显示标题和项数据. 如果我们的模型显示在带有标题的视图中, 我们希望标题显示行和列的编号. 我们重新实现 headerData() 函数, 提供有关标头的信息:
QVariant StringListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) return QString("Column %1").arg(section); else return QString("Row %1").arg(section); }
同样, 只有当角色是我们支持的角色时, 我们才会返回有效的 QVariant. 在决定要返回的确切数据时, 还要考虑标头的方向.
并非所有视图都显示带有项数据的标题, 而有些视图可能隐藏标题. 尽管如此, 建议你实现 headerData() 函数.
一个项可以有多个角色, 根据指定的角色提供不同的数据. 模型中的项只有一个角色 DisplayRole, 因此无论指定的角色是什么, 我们都会返回项的数据. 但是, 我们可以在其他角色中重用为 DisplayRole 提供的数据, 例如视图可以在工具提示中显示项信息的 ToolTipRole.
一个可编辑模型
只读模型显示了如何向用户提供简单的选择, 但对于许多应用程序来说, 可编辑列表模型要有用得多. 我们可以通过将我们实现的data()函数更改为只读, 并通过实现两个额外的函数: flags() 和 setData()修改只读模型, 使项可编辑. 以下函数声明被添加到类定义中:
Qt::ItemFlags flags(const QModelIndex &index) const; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
使模型可编辑
委托在创建编辑器之前检查项目是否可编辑. 模型必须让委托知道其项是可编辑的. 我们通过为模型中的每个项目返回正确的标志来实现这一点; 在这种情况下, 我们启用所有项目, 并使它们既可选择又可编辑:
Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemIsEnabled; return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; }
注意: 我们不必知道委托如何执行实际编辑过程. 我们只需要为委托提供一种在模型中设置数据的方法. 这是通过 setData() 函数实现的:
bool StringListModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.isValid() && role == Qt::EditRole) { stringList.replace(index.row(), value.toString()); emit dataChanged(index, index); return true; } return false; }
在此模型中, 字符串列表中与模型索引相对应的项将被所提供的值替换. 但是, 在修改字符串列表之前, 我们必须确保索引有效, 项的类型正确, 并且角色受支持. 按照惯例, 我们坚持认为角色是 EditRole, 因为这是标准项委托使用的角色. 但是, 对于布尔值, 你可以使用 Qt::CheckStateRole , 并设置Qt::ItemIsUserCheckable 标志; 然后使用复选框编辑该值. 该模型中的底层数据对于所有角色都是相同的, 因此, 此细节只会使模型与标准组件的集成更加容易.
设置数据后, 模型发出 dataChanged() 信号通知视图某些数据已更改. 由于只有一项数据发生变化, 因此信号中指定的项范围仅限于一个模型索引.
此外, 还需要更改data()函数以添加 Qt::EditRole 测试:
QVariant StringListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= stringList.size()) return QVariant(); if (role == Qt::DisplayRole || role == Qt::EditRole) return stringList.at(index.row()); else return QVariant(); }
插入和移除行
视图可以更改模型中的行数和列数. 在字符串列表模型中, 只有更改行数才有意义, 因此我们只重新实现插入和删除行的功能. 这些在类定义中声明:
bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()); bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex());
由于此模型中的行对应于列表中的字符串, insertRows()
函数会在指定位置之前将多个空字符串插入到字符串列表中. 插入的字符串数等于指定的行数.
父索引通常用于确定应在模型中的何处添加行. 在这种情况下, 我们只有一个顶级字符串列表, 所以我们只在该列表中插入空字符串.
bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent) { beginInsertRows(QModelIndex(), position, position+rows-1); for (int row = 0; row < rows; ++row) { stringList.insert(position, ""); } endInsertRows(); return true; }
这个模型首先调用 beginInsertRows() 函数来通知其他组件行数即将更改. 这个函数指定要插入的第一行和最后一行的行号, 以及其父项的模型索引. 更改字符串列表后, 它调用 endInsertRows() 完成操作, 并通知其他组件模型的维度已经更改, 返回true表示成功.
从模型中删除行的函数编写起来也很简单. 要从模型中删除的行由给定的位置和行数指定. 我们忽略父索引以简化实现, 只从字符串列表中删除相应的项.
bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent) { beginRemoveRows(QModelIndex(), position, position+rows-1); for (int row = 0; row < rows; ++row) { stringList.removeAt(position); } endRemoveRows(); return true; }
beginRemoveRows() 函数总是在删除任何基础数据之前调用, 并指定要删除的第一行和最后一行. 这允许其他组件在数据不可用之前访问数据. 删除行后, 模型会发出 endRemoveRows() 完成操作, 并让其他组件知道模型的维度已经更改.
下一步
我们可以使用 QListView 以垂直列表的形式显示该模型或任何其他模型提供的数据. 对于字符串列表模型, 此视图还提供了一个默认编辑器. We examine the possibilities made available by the standard view classes in View Classes.
有关子类化QAbstractItemModel的更多要求, 详见 Model Subclassing Reference, 这个文档也为指出在不同类型的模型中启用各种功能所必须实现的虚函数.
视图容器类
基于项的widget名称反映了它们的用途: QListWidget
提供一个项列表, QTreeWidget
显示一个多级树结构, QTableWidget
提供一个单元格项表. 每个类都继承了 QAbstractItemView
类的行为, 该类实现了项选择和标题管理等常用函数.
List widgets
项的单层列表通常使用 QListWidget
和多个 QListWidgetItem
显示. QListWidget的构造方式与任何其他widget相同:
QListWidget *listWidget = new QListWidget(this);
在构造QListWidgetItem时, 列表项可以直接加到QListWidget:
new QListWidgetItem(tr("Sycamore"), listWidget); new QListWidgetItem(tr("Chestnut"), listWidget); new QListWidgetItem(tr("Mahogany"), listWidget);
它们也可以在构建之后, 加到QListWidget:
QListWidgetItem *newItem = new QListWidgetItem; newItem->setText(itemText); listWidget->insertItem(row, newItem);
列表中的每个项都可以显示一个文本标签和一个图标. 你可以自定义文本的颜色和字体. 工具提示, 状态提示和 "What's This?" 帮助都可以轻松配置, 以确保列表正确集成到应用程序中.
newItem->setToolTip(toolTipText); newItem->setStatusTip(toolTipText); newItem->setWhatsThis(whatsThisText);
默认情况下, 列表中的项按创建顺序显示. 项列表可以根据 Qt::SortOrder 排序, 以生成按字母顺序正向或反向排序的项列表:
listWidget->sortItems(Qt::AscendingOrder); listWidget->sortItems(Qt::DescendingOrder);
Tree widgets
项的树或层次列表由 QTreeWidget
和 QTreeWidgetItem
提供. QTreeWidget中的每个项都可以有自己的子项目, 并且可以显示多列信息. QTreeWidget的创建方式与其他widget一样:
QTreeWidget *treeWidget = new QTreeWidget(this);
在将项添加到QTreeWidget之前, 必须设置列数. 例如, 我们可以定义两列, 并创建一个标题, 在每列的顶部提供标签:
treeWidget->setColumnCount(2); QStringList headers; headers << tr("Subject") << tr("Default"); treeWidget->setHeaderLabels(headers);
为每个部分设置标签的最简单方法是提供一个字符串列表. 对于更复杂的头, 你可以构建一个树项, 根据自己的意愿对其装饰, 并将其用作QTreeWidget的头.
QTreeWidget的顶级项是用QTreeWidget作为父widget构建的. 它们可以按任意顺序插入, 也可以通过在构造每个项时, 指定前一个项来确保它们按特定顺序列出:
QTreeWidgetItem *cities = new QTreeWidgetItem(treeWidget); cities->setText(0, tr("Cities")); QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities); osloItem->setText(0, tr("Oslo")); osloItem->setText(1, tr("Yes")); QTreeWidgetItem *planets = new QTreeWidgetItem(treeWidget, cities);
QTreeWidget处理顶级项的方式与树中较深层的其他项目略有不同. 通过调用 takeTopLevelItem() 函数, 可以从树的顶层移除项, 但通过调用其父项的 takeChild() 函数可以移除较低级别的项. 使用 insertTopLevelItem() 函数将项插入到树的顶层. 在树的较低级别, 使用父项的 insertChild() 函数.
在树的顶层和底层之间移动项很容易. 我们只需要检查这些项是否是顶级项, 并且这些信息是由每个项的 parent()
函数提供的. 例如, 我们可以删除QTreeWidget中的当前项, 而不管其位置如何:
QTreeWidgetItem *parent = currentItem->parent(); int index; if (parent) { index = parent->indexOfChild(treeWidget->currentItem()); delete parent->takeChild(index); } else { index = treeWidget->indexOfTopLevelItem(treeWidget->currentItem()); delete treeWidget->takeTopLevelItem(index); }
按如下方式, 在QTreeWidget中插入项:
QTreeWidgetItem *parent = currentItem->parent(); QTreeWidgetItem *newItem; if (parent) newItem = new QTreeWidgetItem(parent, treeWidget->currentItem()); else newItem = new QTreeWidgetItem(treeWidget, treeWidget->currentItem());
Table widgets
类似于电子表格应用程序中的表表格用 QTableWidget
和 QTableWidgetItem
. 构建. 这两个类提供了一个滚动的表格widget, 包含标题和项.
你可以使用一定数量的行和列创建表, 也可以根据需要将这些行和列添加到未定大小的表中.
QTableWidget *tableWidget; tableWidget = new QTableWidget(12, 3, this);
在将项添加到表的所需位置之前, 将在表外构造项:
QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg( pow(row, column+1))); tableWidget->setItem(row, column, newItem);
通过在表外构建项并将其用作标题, 可以将水平和垂直标题添加到表中:
QTableWidgetItem *valuesHeaderItem = new QTableWidgetItem(tr("Values")); tableWidget->setHorizontalHeaderItem(0, valuesHeaderItem);
注意: 表格创建时, 行数和列数是0.
公共部分
每个便利类都有许多基于项的通用功能, 这些功能可通过每个类中的相同接口使用. 我们将在下面的部分中介绍这些,并为不同的widget提供一些示例. 有关每个函数的更多详细信息, 参见 Model/View Classes.
隐藏项
有时, 你需要在视图中隐藏项, 而不是删除它们. 上面所有widget的项都可以隐藏. 你可以调用isItemHidden()函数检测项是否隐藏, 并且可以调用 setItemHidden()
隐藏项.
由于此操作是基于项的, 因此所有三个便利类都可以使用相同的功能.
选择
项的选择方式由widget的选择模式 (QAbstractItemView::SelectionMode)控制. 这个属性控制用户是否可以选择一个或多个项, 以及在多个项中选择, 选择是否必须是连续的. 上面所有widget的选择模式的工作方式相同.
Single item selections: Where the user needs to choose a single item from a widget, the default SingleSelection mode is most suitable. In this mode, the current item and the selected item are the same. | |
Multi-item selections: In this mode, the user can toggle the selection state of any item in the widget without changing the existing selection, much like the way non-exclusive checkboxes can be toggled independently. | |
Extended selections: Widgets that often require many adjacent items to be selected, such as those found in spreadsheets, require the ExtendedSelection mode. In this mode, continuous ranges of items in the widget can be selected with both the mouse and the keyboard. Complex selections, involving many items that are not adjacent to other selected items in the widget, can also be created if modifier keys are used.If the user selects an item without using a modifier key, the existing selection is cleared. |
调用 selectedItems()
函数获取所有已选择的项. 如下代码所示, 我们可以计算所有选中项的数值总和:
QList<QTableWidgetItem *> selected = tableWidget->selectedItems(); QTableWidgetItem *item; int number = 0; double total = 0; foreach (item, selected) { bool ok; double value = item->text().toDouble(&ok); if (ok && !item->text().isEmpty()) { total += value; number++; } }
注意: 对于单选模式, 当前项将在选择中. 在多选和扩展选择模式中, 根据用户形成选择的方式, 当前项目可能不在选择范围内.
搜索
有时, 你需要在视图中查找项目. 所有三个视图便利类都提供了一个通用的 findItems()
函数, 使其尽可能一致和简单.
根据 Qt::MatchFlags中指定的条件, 通过项所包含的文本搜索项. 我们可以调用 findItems()
函数获得匹配项的列表:
QTreeWidgetItem *item; QList<QTreeWidgetItem *> found = treeWidget->findItems( itemText, Qt::MatchWildcard); foreach (item, found) { treeWidget->setItemSelected(item, true); // Show the item->text(0) for each item. }
如果QTreeWidget的项包含搜索字符串中给定的文本, 则上面的代码会导致它们被选中. 这个模式也可以用于QListWidget和QTableWidget.
拖拽视图中的项
Qt的拖拽完全由模型/视图框架支持. 列表, 表格和树中的项可以在视图中拖动, 数据以MIME编码方式导入和导出.
标准视图自动支持内部拖放, 其中项可以四处移动以更改显示顺序. 默认情况下, 不会为这些视图启用拖放功能, 因为它们是为最简单, 最常见的用途而配置的. 要允许拖动项, 需要启用视图的某些属性, 并且项本身也必须允许拖动.对于只允许从视图导出项而不允许将数据放入其中的模型, 其要求比完全启用拖放模型的要求要低.
有关在新模型中启用拖放支持的更多信息, 参见 Model Subclassing Reference.
使用便捷视图类
默认情况下, QListWidget, QTableWidget, QTreeWidget 使用的每种类型的项都配置了不同的标志集. 例如, 每个 QListWidgetItem 或 QTreeWidgetItem 最初都是启用的, 可检查的, 可选择的, 并且可以用作拖放操作的源; 每个 QTableWidgetItem 也可以编辑并用作拖放操作的目标.
尽管所有标准项都为拖放设置了一个或两个标志, 但你通常需要在视图本身中设置各种属性, 以利用内置的拖放支持:
- 若要启用项目拖动, 将视图的 dragEnabled 属性设置为
true
. - 若要允许用户在视图中放置内部或外部项, 将视图的 viewport()的 acceptDrops 属性设置为
true
. - 若要向用户显示当前拖动的项在放置时的位置, 设置视图的 showDropIndicator 属性. 这为用户提供了关于视图中的项目放置的连续更新信息.
例如, 我们可以使用以下代码行在QListWidget中启用拖放:
QListWidget *listWidget = new QListWidget(this); listWidget->setSelectionMode(QAbstractItemView::SingleSelection); listWidget->setDragEnabled(true); listWidget->viewport()->setAcceptDrops(true); listWidget->setDropIndicatorShown(true);
结果是一个QListWidget, 它允许在视图中复制项, 甚至允许用户在包含相同类型数据的视图之间拖动项. 在这两种情况下, 项都是复制的,而不是移动的.
为了使用户能够在视图中移动项, 我们必须设置QListWidget的 dragDropMode:
listWidget->setDragDropMode(QAbstractItemView::InternalMove);
使用模型/视图类
为视图设置与便捷类相同的模式. 例如, 与QListWidget相同的方式设置 QListView:
QListView *listView = new QListView(this); listView->setSelectionMode(QAbstractItemView::ExtendedSelection); listView->setDragEnabled(true); listView->setAcceptDrops(true); listView->setDropIndicatorShown(true);
由于视图显示的数据由模型控制, 因此所使用的模型还必须支持拖放操作. 你可以重新实现 QAbstractItemModel::supportedDropActions() 函数, 提供模型支持的操作. 例如, 以下代码启动复制和移动操作:
Qt::DropActions DragDropListModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; }
尽管, 你可以任意组合 Qt::DropActions 的值, 但是还需要编写模型代码支持它们. 例如, 想要列表模型支持 Qt::MoveAction 这个模型必须直接或继承基类的 QAbstractItemModel::removeRows()实现.
启用项的拖放操作
模型重新实现 QAbstractItemModel::flags() 函数提供合适的标志, 表明视图中的哪些项支持拖动, 哪些项支持放下.
例如, 继承自 QAbstractListModel 的列表模型返回 Qt::ItemIsDragEnabled 和 Qt::ItemIsDropEnabled 组合标志:
Qt::ItemFlags DragDropListModel::flags(const QModelIndex &index) const { Qt::ItemFlags defaultFlags = QStringListModel::flags(index); if (index.isValid()) return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; else return Qt::ItemIsDropEnabled | defaultFlags; }
注意, 项可以放到模型的顶层, 但仅对有效项启用拖动.
在上面的代码中, 由于模型是从 QStringListModel派生的, 我们调用flags()函数的实现获取一组默认的标志.
Encoding exported data
在拖放操作中, 数据项从模型导出时, 它们的编码方式与一个或多个MIME类型相对应. 模型重新实现 QAbstractItemModel::mimeTypes() 函数声明项的MIME类型, 返回标准的MIME类型列表.
例如, 一个只提供纯文本的模型提供如下实现:
QStringList DragDropListModel::mimeTypes() const { QStringList types; types << "application/vnd.text.list"; return types; }
模型必须重新实现 QAbstractItemModel::mimeData() 函数, 提供 QMimeData 对象实现.
下面代码展示如何为给定索引提供对应的 QMimeData 对象, 它存储项的纯文本数据.
QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); foreach (const QModelIndex &index, indexes) { if (index.isValid()) { QString text = data(index, Qt::DisplayRole).toString(); stream << text; } } mimeData->setData("application/vnd.text.list", encodedData); return mimeData; }
上述函数的参数是一个模型索引列表, 这是一种通用的方式, 可以在层次模型和非层次模型中使用.
注意: 自定义数据类型必须声明为meta objects, 并实现流运算符. 详见 QMetaObject.
向模型插入拖放数据
模型处理拖放数据的方式取决于类型(列表, 表格或树)及显示方式. 通常, 存储拖放数据最恰当的方式是使用模型的底层数据结构.
不同类型的模型采用不同的处理方式. 列表和表格模型仅提供一个存储数据的平面结构. 因此, 数据拖放到视图的现有项时, 它们可能在已存在的项上插入新行(和新列), 或者它们使用拖放数据覆盖现有项的内容. 树模型经常将包含新数据的子项添加存储到底层数据中, 这种行为对用户更具预测性.
模型重新实现 QAbstractItemModel::dropMimeData()处理拖放数据. 例如, 处理简单字符串列表的模型可以提供一种实现, 处理拖放到已存在项上的数据时, 你可以将数据放入模型的最顶层(即., 拖放到一个无效项上).
模型也可以重新实现 QAbstractItemModel::canDropMimeData(), 根据项或拖放数据判断是否支持拖放操作.
模型首先判断操作是否可以执行, 如数据格式是否可用, 模型的目标是否有效:
bool DragDropListModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(action); Q_UNUSED(row); Q_UNUSED(parent); if (!data->hasFormat("application/vnd.text.list")) return false; if (column > 0) return false; return true; } bool DragDropListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (!canDropMimeData(data, action, row, column, parent)) return false; if (action == Qt::IgnoreAction) return true;
若数据不支持纯文本格式, 或拖放的列号无效等, 则模型可能返回无法拖放.
根据是否将拖放数据放到模型的现有项中, 对其进行不同的处理. 在这个简单的示例中, 我们希望在现有项之间、列表中第一个项之前和最后一个项之后支持拖放操作.
发生拖放时, 与父项相对应的模型索引将是有效的, 表示拖放发生在某个项上, 或者将是无效的, 表示拖放数据发生在视图中与模型顶层相对应的某个位置.
int beginRow; if (row != -1) beginRow = row;
我们首先检查行号, 看看是否可以使用它将项插入到模型中, 而不管父索引是否有效.
else if (parent.isValid()) beginRow = parent.row();
如果父模型索引有效, 则拖放操作发生在项上. 在这个简单列表模型中, 我们找出项的行号, 并使用该值将拖放的项插入到模型的顶层.
else beginRow = rowCount(QModelIndex());
在视图中发生拖放操作, 且行号不可用时, 我们将项追加到模型的顶层.
在层次模型中, 当一个项发生拖放时, 最好将新项作为该项的子项插入到模型中. 在这个简单示例中, 模型只有一个层级, 因此这种方法是不合适的.
解码导入的数据
dropMimeData() 的每个实现还必须解码数据, 并将其插入模型的底层数据结构中.
对于一个简单的字符串列表模型, 编码的项可以被解码, 并流式传输到 QStringList中:
QByteArray encodedData = data->data("application/vnd.text.list"); QDataStream stream(&encodedData, QIODevice::ReadOnly); QStringList newItems; int rows = 0; while (!stream.atEnd()) { QString text; stream >> text; newItems << text; ++rows; }
然后可以将字符串插入到底层数据存储中. 为了保持一致性, 可以调用模型的接口实现:
insertRows(beginRow, rows, QModelIndex()); foreach (const QString &text, newItems) { QModelIndex idx = index(beginRow, 0, QModelIndex()); setData(idx, text); beginRow++; } return true; }
注意: 模型必须重新实现 QAbstractItemModel::insertRows() 和 QAbstractItemModel::setData().
Proxy Models
在模型/视图框架中, 单个模型提供的数据项可以由任意数量的视图共享, 并且每个视图都可能以完全不同的方式表示相同的信息. 自定义视图和委托是提供相同数据的完全不同表示的有效方法. 然而, 应用程序通常需要提供对相同数据的处理版本的常规视图, 例如对项列表不同排序的视图.
尽管将排序和筛选操作作为视图的内部功能似乎是合适的, 但这种方法不允许多个视图共享这种潜在成本高昂的操作. 另一种方法是在模型中排序, 这会导致类似的问题, 即每个视图都必须显示根据最近的处理操作组织的数据项.
为了解决这个问题, 模型/视图框架使用代理模型管理模型和视图之间提供的信息. 代理模型是从视图的角度来看行为类似于普通模型的组件, 并代表该视图访问源模型中的数据. 模型/视图框架使用的信号和槽确保每个视图都得到适当的更新, 无论在其自身和源模型之间放置多少代理模型.
Using proxy models
代理模型被插入到现有模型和任意数量的数据之间. Qt提供一个标准的代理模型QSortFilterProxyModel, 你通常可以直接实例化这个类, 但也可以子类化 QSortFilterProxyModel, 自定义过滤和排序行为. 你可以按照以下方式使用QSortFilterProxyModel:
QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(parent); filterModel->setSourceModel(stringListModel); QListView *filteredView = new QListView; filteredView->setModel(filterModel);
代理模型继承自 QAbstractItemModel, 所以你可以使用它们连接任意类型的视图, 并在各个视图间共享. 它们还可以以流水线的方式处理从其他代理模型获得的信息.
QSortFilterProxyModel 可以直接在应用程序中实例化和使用, 也可以子类化实现比较操作.
自定义代理模型
通常, 代理模型中使用的处理类型包括从源模型的原始位置映射到代理模型中的不同位置中的每个数据项. 在一些模型中, 一些项目可能在代理模型中没有相应的位置; 这些模型是过滤代理模型. 视图使用代理模型提供的模型索引访问项, 并且这些视图不包含有关源模型或原始项在模型中的位置信息.
QSortFilterProxyModel 在源模型中的数据提供给视图之前对其过滤, 还允许将源模型的内容作为预先排序的数据提供给视图.
Custom filtering models
QSortFilterProxyModel 提供了一个非常通用的过滤模型, 可以满足各种常见情景. 对于高级用户, 可以子类化 QSortFilterProxyModel , 自定义过滤机制.
QSortFilterProxyModel 的子类可以重新实现两个虚拟函数, 每当请求或使用代理模型的模型索引时, 都会调用这两个函数:
- filterAcceptsColumn() 从源模型的一部分中筛选特定列.
- filterAcceptsRow() 从源模型的一部分中筛选特定的行.
QSortFilterProxyModel 中的默认实现返回true, 将所有项都传递到视图; 这些函数的重新实现应该返回false以过滤掉单独的行和列.
自定义排序模型
QSortFilterProxyModel QSortFilterProxyModel实例使用std::stable_sort()函数设置源模型中的项和代理模型中的各项之间的映射, 从而允许在不修改源模型结构的情况下向视图公开排序后的项层次结构. 重新实现 lessThan(), 自定义排序行为.
Model Subclassing Reference
模型子类需要实现 QAbstractItemModel 基类中定义的许多虚函数. 这些函数的实现数量取决于模型的类型——它是为视图提供简单的列表, 表还是复杂的项层次结构. 从 QAbstractListModel 和 QAbstractTableModel 继承的模型可以利用这些类提供的默认实现函数. 以树状结构公开数据项的模型必须实现 QAbstractItemModel中的许多虚函数.
模型子类中实现的函数可以分为三组:
- 项数据处理: 所有模型都需要实现此类函数, 使视图和委托能够查询模型的维度, 检查项和检索数据.
- 导航和索引创建: 层次模型需要提供视图可以调用的函数, 以导航它们公开的树状结构, 并获得项的模型索引.
- 拖放和MIME类型处理: 继承控制内部和外部拖放操作的函数, 这些函数允许用其他组件和应用程序能够理解的MIME类型描述数据项.
项数据处理
对于模型数据, 模型可以提供不同级别的访问: 它们可以是简单的只读组件, 一些模型可能支持调整大小操作, 而另一些模型可能允许编辑项.
只读访问
如果模型提供数据的只读访问, 则子类必须实现下列函数:
flags() | Used by other components to obtain information about each item provided by the model. In many models, the combination of flags should include Qt::ItemIsEnabled and Qt::ItemIsSelectable. |
data() | Used to supply item data to views and delegates. Generally, models only need to supply data for Qt::DisplayRole and any application-specific user roles, but it is also good practice to provide data for Qt::ToolTipRole, Qt::AccessibleTextRole, and Qt::AccessibleDescriptionRole. See the Qt::ItemDataRole enum documentation for information about the types associated with each role. |
headerData() | Provides views with information to show in their headers. The information is only retrieved by views that can display header information. |
rowCount() | Provides the number of rows of data exposed by the model. |
这四个函数必须在所有类型的模型子类中重新实现, 包括 QAbstractListModel 子类和QAbstractTableModel 子类.
除此之外, 下列函数必须在 QAbstractTableModel 和 QAbstractItemModel的派生类中实现:
columnCount() | Provides the number of columns of data exposed by the model. List models do not provide this function because it is already implemented in QAbstractListModel. |
可编辑项
可编辑的模型允许修改项数据, 它也可能提供函数, 允许插入和移除行和列. 若想启用编辑, 子类必须实现下列函数:
flags() | Must return an appropriate combination of flags for each item. In particular, the value returned by this function must include Qt::ItemIsEditable in addition to the values applied to items in a read-only model. |
setData() | Used to modify the item of data associated with a specified model index. To be able to accept user input, provided by user interface elements, this function must handle data associated with Qt::EditRole. The implementation may also accept data associated with many different kinds of roles specified by Qt::ItemDataRole. After changing the item of data, models must emit the dataChanged() signal to inform other components of the change. |
setHeaderData() | Used to modify horizontal and vertical header information. After changing the item of data, models must emit the headerDataChanged() signal to inform other components of the change. |
Resizable models
所有类型的模型都可以支持插入和移除行. 表格模型和层次结构模型也可以支持插入和移除列. 改变行列前后, 将这些变化通知给其他组件. 若想修改模型的行列, 子类必须实现下列函数, 且重新实现函数时, 必须调用适当的函数通知视图和代理这些变化:
insertRows() | Used to add new rows and items of data to all types of model. Implementations must call beginInsertRows() before inserting new rows into any underlying data structures, and call endInsertRows() immediately afterwards. |
removeRows() | Used to remove rows and the items of data they contain from all types of model. Implementations must call beginRemoveRows() before rows are removed from any underlying data structures, and call endRemoveRows() immediately afterwards. |
insertColumns() | Used to add new columns and items of data to table models and hierarchical models. Implementations must call beginInsertColumns() before inserting new columns into any underlying data structures, and call endInsertColumns() immediately afterwards. |
removeColumns() | Used to remove columns and the items of data they contain from table models and hierarchical models. Implementations must call beginRemoveColumns() before columns are removed from any underlying data structures, and call endRemoveColumns() immediately afterwards. |
通常, 若操作成功, 则返回true. 但是, 某些情况下, 操作可能部分成功; 例如, 若插入少于指定行数的行. 这种情况下, 模型应该返回false, 表示无法处理这种情况.
在调整模型行列的函数中, 发出信号使附加组件有机会在任何数据变得不可用之前采取行动. 在插入和删除操作, 调用begin和end函数可以使模型能够正确地管理 持久模型索引.
通常, begin和end函数能够向其他组件通知模型底层结构的更改. 对于模型结构的更复杂的更改, 可能涉及内部重组, 数据排序或任何其他结构更改, 有必要执行以下顺序:
- 发出 layoutAboutToBeChanged() 信号
- 更新表示模型结构的内部数据.
- 使用 changePersistentIndexList()更新持久索引
- 发出 layoutChanged() 信号.
上述步骤可用于任何结构更新, 以代替更高级, 更方便的保护方法. 例如, 如果一个200万行的模型需要删除所有奇数行, 即100万个不明确的范围, 每个范围有1个元素. 使用beginRemoveRows和endRemoveRows100万次是可能的, 但这显然是低效的. 相反, 这可以一次更新所有必要的持久索引, 然后发出单个布局更改信号.
模型数据的延迟填充
模型数据的延迟填充有效地允许延迟对有关模型的信息的请求, 直到视图实际需要它为止.
有些模型需要从远程源获取数据, 或者必须执行耗时的操作才能获得有关数据组织方式的信息. 由于视图通常请求尽可能多的信息以准确显示模型数据, 因此限制返回给它们的信息量以减少不必要的后续数据请求可能会很有用.
在分层模型中, 查找给定项的子项数量是一项昂贵的操作, 确保只有在必要时才调用模型的rowCount()实现是非常有用的. 在这种情况下, 可以重新实现hasChildren()函数, 为视图提供一种廉价的方式来检查子项的存在, 并且在QTreeView的情况下, 为其父项绘制适当的装饰.
无论hasChildren()的重新实现返回true
还是false
, 视图都可能不需要调用rowCount()了解存在多少子级. 例如, 如果父项尚未展开以显示子项, 则QTreeView不需要知道有多少子项.
如果知道许多项都会有子项, 那么重新实现hasChildren(), 直接返回true
有时是一种有用的方法. 这样保证以后再检查每个项的子项, 同时使模型数据的初始群体尽可能快. 唯一的缺点是, 在某些视图中, 不存在的子项的项可能显示错误.
导航和索引创建
分层模型需要实现函数, 导航它们公开的树状结构, 并获得项的模型索引.
Parents and children
由于向视图公开的结构是由底层数据结构决定的, 因此每个模型子类都可以重新实现下列函数, 创建自己的模型索引:
index() | Given a model index for a parent item, this function allows views and delegates to access children of that item. If no valid child item - corresponding to the specified row, column, and parent model index, can be found, the function must return QModelIndex(), which is an invalid model index. |
parent() | Provides a model index corresponding to the parent of any given child item. If the model index specified corresponds to a top-level item in the model, or if there is no valid parent item in the model, the function must return an invalid model index, created with the empty QModelIndex() constructor. |
上面的两个函数都使用createIndex()工厂函数为其他组件生成索引. 模型通常会为此函数提供一些唯一的标识符, 确保模型索引稍后可以与其相应的项重新关联.
拖放和MIME类型处理
模型/视图类支持拖放操作, 为许多应用程序提供了足够的默认行为. 但是, 你也可以自定义拖放操作期间项的编码方式, 默认情况下是复制还是移动项, 以及如何将项插入现有模型.
此外, 便捷视图类实现了专门的行为, 这些行为应该严格遵循现有开发人员的预期. 参见 Convenience Views.
MIME数据
默认情况下, 内置模型和视图使用内部MIME类型(application/x-qabstractitemmodeldatalist
)传递有关模型索引的信息. 这是项列表指定数据, 包含每个项的行号和列号, 以及关于每个项支持的角色信息.
这类MIME数据可以调用 QAbstractItemModel::mimeData() 获得.
在自定义模型中实现拖放支持时, 可以通过重新实现以下功能以专用格式导出数据项:
mimeData() | This function can be reimplemented to return data in formats other than the default application/x-qabstractitemmodeldatalist internal MIME type.Subclasses can obtain the default QMimeData object from the base class and add data to it in additional formats. |
多数模型使用通用的MIME类型(如text/plain
和image/png
)表示项的内容. 注意: 调用QMimeData::setImageData(), QMimeData::setColorData(), QMimeData::setHtml()函数设置图像, 颜色和HTML.
接受拖放数据
在视图上发生拖拽操作时, 视图会调用QAbstractItemModel::supportedDropActions(), QAbstractItemModel::mimeTypes()函数检测支持哪些类型的操作及MIME类型, 这两个函数的默认实现支持复制操作和默认的内部MIME类型.
在视图上拖入序列化数据时, 视图会调用QAbstractItemModel::dropMimeData()函数将数据插入当前模型, 这个函数的默认实现不会覆盖模型的任何数据; 相反, 它尝试将数据作为项的兄弟项或子项插入模型.
若想使用QAbstractItemModel对内置MIME类型的默认实现, 你必须重新实现下列函数:
insertRows() | These functions enable the model to automatically insert new data using the existing implementation provided by QAbstractItemModel::dropMimeData(). |
insertColumns() | |
setData() | Allows the new rows and columns to be populated with items. |
setItemData() | This function provides more efficient support for populating new items. |
若想接收其他格式的数据, 你必须重新实现下列函数:
supportedDropActions() | Used to return a combination of drop actions, indicating the types of drag and drop operations that the model accepts. |
mimeTypes() | Used to return a list of MIME types that can be decoded and handled by the model. Generally, the MIME types that are supported for input into the model are the same as those that it can use when encoding data for use by external components. |
dropMimeData() | Performs the actual decoding of the data transferred by drag and drop operations, determines where in the model it will be set, and inserts new rows and columns where necessary. How this function is implemented in subclasses depends on the requirements of the data exposed by each model. |
若重新实现 dropMimeData() 函数, 通过插入或删除行或列更改模型的维度, 或修改数据项, 则必须确保发出所有相关信号. 调用子类的实现函数(如setData(), insertRows()和insertColumns()), 就可以确保模型的行为一致.
若想确保拖拽操作的正常工作, 则必须实现如下函数删除模型的数据:
有关视图项拖拽的更多信息, 详见 Using drag and drop with item views.
便捷视图
便捷视图(QListWidget, QTableWidget, QTreeWidget) 覆盖默认的拖拽功能, 提供不太灵活, 但更自然的行为, 适合多数应用程序. 例如, 把数据放入 QTableWidget中的单元格中, 使用正在传输的数据替换现有内容更为常见, 因此底层模型会覆盖目标项的数据, 而不是在模型中插入新行和列. 有关便捷视图拖拽的更多信息, 参见 Using drag and drop with item views.
针对大量数据的性能优化
canFetchMore() 函数检查父级是否有更多可用数据. fetchMore() 函数根据指定的父级获取数据. 例如, 在涉及增量数据的数据库查询中, 可以将这两个函数组合起来, 为 QAbstractItemModel提供数据. 我们重新实现 canFetchMore() 指示是否有更多的数据要提取, 并根据要求模型提供数据.
另一个例子是动态填充的树模型, 当树模型中的一个分支展开时, 我们会重新实现 fetchMore().
如果 fetchMore() 的重新实现将行添加到模型中, 则需要调用 beginInsertRows() 和 endInsertRows(). 此外, canFetchMore() 和 fetchMore() 都必须重新实现, 因为它们的默认实现返回false并且不执行任何操作.
The Model/View Classes
These classes use the model/view design pattern in which the underlying data (in the model) is kept separate from the way the data is presented and manipulated by the user (in the view).
The abstract interface for item model classes | |
Abstract model that can be subclassed to create one-dimensional list models | |
Abstract model that can be subclassed to create table models | |
Used to locate data in a data model | |
Used to locate data in a data model | |
Base class for proxy item models that can do sorting, filtering or other data processing tasks | |
Proxies its source model unmodified | |
Manages information about selected items in a model | |
Keeps track of a view's selected items | |
Manages information about a range of selected items in a model | |
Support for sorting and filtering data passed between another model and a view | |
Model that supplies strings to views | |
Item for use with the QStandardItemModel class | |
Generic model for storing custom data | |
Data model for the local filesystem | |
Used to display and edit data items from a model | |
The basic functionality for item view classes | |
Model/view implementation of a column view | |
Mapping between a section of a data model to widgets | |
Header row or header column for item views | |
Display and editing facilities for data items from a model | |
Makes it possible to create item editor creator bases without subclassing QItemEditorCreatorBase | |
Abstract base class that must be subclassed when implementing new item editor creators | |
Widgets for editing item data in views and delegates | |
The possibility to register widgets without having to subclass QItemEditorCreatorBase | |
List or icon view onto a model | |
Item-based list widget | |
Item for use with the QListWidget item view class | |
Display and editing facilities for data items from a model | |
Default model/view implementation of a table view | |
Item-based table view with a default model | |
Item for use with the QTableWidget class | |
Way to interact with selection in a model without using model indexes and a selection model | |
Default model/view implementation of a tree view | |
Tree view that uses a predefined tree model | |
Item for use with the QTreeWidget convenience class | |
Way to iterate over the items in a QTreeWidget instance |