Qt扫盲-Qt Model/View 理论总结 [上篇]
Qt Model/View 理论总结 [上篇]
- 一、概述
- 1.model / view 架构
- 2. Model
- 3. View
- 4. Delegate
- 5. 排序
- 6. 快捷类
- 二、使用model/view
- 1. Qt包含两种 model
- 2. 在现有 model 中使用 view
- 三、Model 类
- 1. 基本概念
- 1.model 索引
- 2. 行和列
- 2. item 的父 item
- 3. Item roles
- 4. 总结
- 2. 使用model 的 Index
- 四、View 类
- 1. 概念
- 2. 使用Qt 提供的 view
- 1. 使用model
- 2. 使用 model 的多个 view
- 3. 处理元素的选择
- 1. view 间共享选择
- 五、Delegate 类
- 1. 概念
- 2. 使用现有 delegate
- 3. 一个简单的 delegate
- 1. 提供编辑器
- 2. 向 model 提交数据
- 3. 更新编辑器的几何形状
- 4. Editing hints
一、概述
Qt 包含了一组 item view类,它们使用 model / view 架构来管理数据之间的关系以及呈现给用户的方式。该体系结构引入的功能开发人员提供了更大的灵活性来自定义表现形式,而且这个框架还提供了一个标准的model 接口,以允许广泛的数据源与现有 item 目 View 一起使用。
在本文中,我们简要介绍了 model / view 编程概念,概述了所涉及的概念,并描述了 item 目View系统的体系结构。对架构中的每个组件都进行了解释,并给出了如何使用所提供的类的示例。这里参考的资料就是Qt的官方资料。
1.model / view 架构
Model-View-Controller,(Model-View-Controller, MVC)是一种源自Smalltalk的设计模式,经常用于构建用户界面。
在《设计模式》一书中,Gamma等人写道:
MVC由三种对象组成。model 是应用程序对象,View是它的屏幕显示,Controller, 定义了用户界面对用户输入的反应方式。
在使用MVC之前,用户界面设计倾向于将这些对象放在一起。MVC将它们解耦以提高灵活性和重用性。
如果 View 和 Controller 对象被组合,结果就是 model / view 架构。这仍然将数据的存储方式和呈现给用户的方式分开,但基于相同的原则提供了一个更简单的框架。
这种分离使得可以在多个不同的View中显示相同的数据,并实现新的View类型,而无需更改底层数据结构。
其实就是说,我们的业务如果能脱离界面的话,就最好分离开,而不是糅合,就行 Linux 并不依靠界面运行,但是 Windows系统其实就把系统和界面糅合了一些。
为了灵活地处理用户输入,我们引入了delegate 的概念。
在这个框架中使用delegate 的好处是,它允许自定义数据 item 的渲染和编辑方式。
该 model 与数据源进行通信,为架构中的其他组件提供接口。通信的性质取决于数据源的类型,以及model 的实现方式。
View从 model 中获取model 索引;这些是对数据 item 的引用。通过向model 提供model 索引,View可以从数据源检索数据 item 。
在标准View中,delegate 渲染数据 item 。当一个 item 目被编辑时,delegate 使用 model 的索引直接与 model 通信。
上面的话就看出来了, model 和 delegate 、view 通信的话,都是delegate 、view 用的 model 的索引。
一般来说, model / view 类可以分为上面描述的三组:model 、view 和 delegate 。
每个组件都由提供公共接口的抽象类定义,在某些情况下,还提供功能的默认实现。
抽象类旨在被子类化,以便提供其他组件所期望的全套功能;同时也允许编写专用组件。
model 、View和delegate 使用 信号(signal)和槽(slot) 相互通信:
- 来自 model 的信号通知 View 关于数据源所持有的数据的更改。
- 来自 View 的信号提供了关于用户与正在显示的 item 目交互的信息。
- 来自 delegate 的信号在编辑过程中用于告诉model 和View关于编辑器的状态。
2. Model
所有 item model 都基于 QAbstractItemModel 类。这个类定义了一个接口,View和delegate 使用该接口访问数据。数据本身并不一定要存储在model 中;它可以保存在由单独的类、文件、数据库或其他应用程序组件提供的数据结构或存储库中。 这就是分离数据。model 目的有点像做了一个中间层来联系和界面的关系,这个要理解理解。
model 类一节将介绍model 的基本概念。
QAbstractItemModel 提供了一个数据接口,它足够灵活,可以处理以表、列表和树的形式表示数据的 View。
然而,在为 列表 和类似 表格 的 数据结构 实现新 model 时,QAbstractListModel 和 QAbstractTableModel 类是更好的起点,因为它们提供了通用函数的适当默认实现。这些类都可以子类化,以提供支持特定类型列表和表的model 。, QAbstractTableModel 我用的比较多,
Qt提供了一些现成的 model 来处理数据 item :
- QStringListModel 用于存储一个简单的QString元素列表。
- QStandardItemModel 管理更复杂的 item 目树结构,每个 item 目可以包含任意数据。
- QFileSystemModel 提供了关于本地文件系统中的文件和目录的信息。
- QSqlQueryModel、qsqlltablemodel 和 QSqlRelationalTableModel 按 model / view 约定访问数据库(经常用)。
如果这些标准model 不能满足实际的需求,我们就需要子类化QAbstractListModel, QAbstractListModel或QAbstractTableModel来创建自己的自定义model 。
3. View
Qt 完整的提供了对不同类型的 View:
- QListView显示 item 目列表
- QTableView显示表中来自model 的数据
- QTreeView显示分层列表中的数据model item 。
这些类都基于QAbstractItemView抽象基类。虽然这些类是现成的实现,但它们也可以子类化以提供自定义View。
可用的View将在View类一节中介绍。
4. Delegate
QAbstractItemDelegate 是 model / view 框架中代理的抽象基类。
默认的delegate 实现由QStyledItemDelegate提供,它被Qt的标准View用作默认delegate 。
然而,QStyledItemDelegate 和 QItemDelegate 是 绘图 的独立替代方案,并为View中的 item 目提供编辑器。(这个编辑器的话我们就可以用自己的自定义控件了,或者Qt的像 QLineEdit、QSpinBox等控件)
它们之间的区别在于,QStyledItemDelegate 使用当前样式来绘制它的 item 。
因此,在实现自定义delegate 或使用 Qt样式表 时,我们建议使用QStyledItemDelegate作为基类。
5. 排序
在 model / view 架构中有两种排序方法;选择哪种方法取决于你的基础 model 。
如果你的model 是可排序的,就是如果它重新实现了QAbstractItemModel::sort()函数,QTableView和QTreeView都提供了一个API,允许你以编程方式对model 数据进行排序。
此外,我们还可以启用交互式排序(即允许用户通过单击View的标题对数据进行排序),通过分别将QHeaderView::sortIndicatorChanged() 信号连接到 QTableView::sortByColumn() 槽函数 或 QTreeView::sortByColumn() 槽函数。
另一种方法是,如果你的 model 没有所需的接口,或者你想使用 List View 来显示数据,则在 View 中显示数据之前,使用 proxy model 来转换model 的结构。这在 proxy model 一节中有详细介绍。
6. 快捷类
为了让依赖于Qt基于item的item view和table类的应用程序受益,许多便捷的类都派生自标准View类。
它们不打算被子类化。他们的目的就是为了去被使用的。
这些类的包括 QListWidget、QTreeWidget和QTableWidget。这些类不如View类灵活,不能与任意 model 一起使用。
Qt建议我们使用model/view方法来处理itemView中的数据,除非你非常需要一组基于item的类。确实我用了 model view 的编程方式,两个字形容:真香!
如果你想利用 model / view 方法提供的特性,同时仍然使用基于 item 的接口,可以考虑使用View类,例如 QListView、QTableView 和 QTreeView 与 QStandardItemModel。这个就是说,我们 view 还是可以用基于 mode/view 的控件,只是我们用 QStandardItemModel 来表示 view 里面每一个item数据,这样的灵活性就不那么高啦,但是Qt还是提供了的。
二、使用model/view
接下来的几节解释如何在Qt中使用 model/view 模式。每一节都包含一个示例,然后还有一节展示如何创建新组件。
1. Qt包含两种 model
Qt提供的两个标准 model 是QStandardItemModel 和 QFileSystemModel。QStandardItemModel 一个多用途 model ,可用于表示列表、表和树 view 所需的各种不同的数据结构。这个 model 还保存了数据 item 。QFileSystemModel是一个维护目录内容信息的 model 。因此,它本身不保存任何数据 item ,只是表示本地文件系统上的文件和目录。
QFileSystemModel提供了一个现成的 model 来进行实验,可以很容易地配置以使用现有数据。使用这个 model ,我们可以展示如何为现成的 view 设置 model ,并探索如何使用 model 索引操作数据。
2. 在现有 model 中使用 view
QListView和QTreeView类是最适合与QFileSystemModel一起使用的 view 。下面给出的示例在树 view 中显示目录的内容,与列表 view 中的相同信息相邻。这两个 view 共享用户的选择,因此选中的 item 目在两个 view 中都被突出显示。
共用的一个model的嘛。
我们设置了一个QFileSystemModel,以便它可以使用,并创建一些 view 来显示目录的内容。这展示了使用 model 的最简单方法。 model 的构造和使用是在一个main()函数中完成的:
int main(int argc, char *argv[])
{QApplication app(argc, argv);QSplitter *splitter = new QSplitter;QFileSystemModel *model = new QFileSystemModel;model->setRootPath(QDir::currentPath());
该 model 被设置为使用来自某个文件系统的数据。调用setRootPath()告诉model要将文件系统中的哪个驱动器路径暴露给 view 。
创建两个 view ,以便以两种不同的方式检查 model 中的 item :
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()));
view 的构造方式与其他部件相同。
要想在 view 中显示 model 中的 item 目,只需调用它的setModel()函数,将目录 model 作为参数即可。
我们在每个 view 上调用setRootIndex()函数,从文件系统 model 中为当前目录传入一个合适的 model 索引,从而过滤 model 提供的数据。
这里使用的index()函数是QFileSystemModel唯一的。我们给它提供一个目录,它会返回一个 model 索引。 model 索引在 model 类中讨论。
函数的其余部分只是显示splitter部件中的 view ,并运行应用程序的事件循环:
splitter->setWindowTitle("Two views onto the same file system model");splitter->show();return app.exec();
}
在上面的例子中,我们忽略了如何处理元素的选择。在Item view 中处理选择的部分会更详细地介绍这个主题。
三、Model 类
1. 基本概念
在 model / view 体系结构中, model 提供了一个标准接口, view 和 delegate 使用该接口访问数据。
在Qt中,标准接口是由QAbstractItemModel类定义的。无论数据 item 如何存储在任何底层数据结构中,QAbstractItemModel的所有子类都将数据表示为包含条目表的分层结构。
view 使用这种约定来访问 model 中的数据 item ,但是它们向用户显示信息的方式不受限制。
model 还通过信号和槽机制通知任何附加 view 有关数据更改的信息。
本节描述了一些基本概念,这些概念对于其他组件通过 model 类访问数据 item 的方式至关重要。后面的部分将讨论更高级的概念。
1.model 索引
为了确保数据的表示与访问数据的方式是分开的,引入了 model 索引的概念。可以通过 model 获得的每条信息都由 model 索引表示。 view 和 delegate 使用这些索引请求要显示的数据 item 。
因此,只有 model 需要知道如何获取数据,并且可以相当通用地定义 model 管理的数据类型。 (model 去外面取数据的格式无所谓,但是变化的内容仅限于model内,不会去影响到 view,下次我们扩展其实就很nice啦,改动的就小些啦)
model 索引包含一个指向创建它们的 model 的指针,这可以防止在使用多个 model 时出现混乱。
QAbstractItemModel *model = index.model();
model 索引提供对信息片段的临时引用,并可用于通过 model 检索或修改数据。由于 model 可能会不时地重新组织其内部结构,因此 model 索引可能会失效,不应该存储。如果需要对某条信息进行长期引用,则必须创建持久 model 索引。这提供了对 model 保持最新的信息的引用。
临时 model 索引由QModelIndex类提供,持久 model 索引由QPersistentModelIndex类提供。
要获得与数据 item 对应的 model 索引,必须为 model 指定三个属性:row 号、column 号和父 item 的 model 索引。
下面几节将详细描述和解释这些属性。
2. 行和列
在其最基本的形式中, model 可以作为一个简单的表来访问,其中的 item 根据其行号和列号进行定位。
这并不意味着底层数据块存储在数组结构中;行号和列号的使用只是允许组件相互通信的约定。
通过向 model 指定 item 目的行号和列号,我们可以检索关于任何给定 item 目的信息,并获得一个表示该 item 目的索引:
QModelIndex index = model->index(row, column, ...);
为简单的单级数据结构(如列表和表)提供接口的 model 不需要提供任何其他信息,但是,正如上面的代码所示,我们需要在获得 model 索引时提供更多信息。
上图显示了一个基本表 model 的表示,其中每个 item 目通过一对行号和列号定位。通过将相关的行号和列号传递给 model ,我们获得一个引用数据 item 的 model 索引。
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
model 中的顶级 item 总是通过指定 QModelIndex() 作为它们的父 item 来引用。这将在下一节中讨论。
2. item 的父 item
当在表或列表 view 中使用数据时, model 提供的类表接口是理想的;行号和列号系统与 view 显示 item 的方式完全对应。然而,像树 view 这样的结构要求 model 向其中的 item 目公开一个更灵活的接口。因此,每个 item 目也可以是另一个 item 目表的父 item ,就像树 view 中的顶级 item 目可以包含另一个 item 目列表一样。
当请求一个 model item 的索引时,我们必须提供一些关于该 item 的父 item 的信息。在 model 之外,引用 item 目的唯一方法是通过 model 索引,因此还必须给出父 model 索引:
QModelIndex index = model->index(row, column, parent);
上图 显示了树 model 的表示,其中每个 item 由父 item 、行号和列号引用。
item 目“A”和“C”在 model 中表示为顶层兄弟:
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
A item 有几个子 item 。 item 目“B”的 model 索引由以下代码获得:
QModelIndex indexB = model->index(1, 0, indexA);
3. Item roles
model 中的 item 可以为其他组件执行不同的 role,从而允许为不同的情况提供不同类型的数据。
例如,Qt::DisplayRole用于访问可以在 view 中显示为文本的字符串。通常, item 包含许多不同 role 的数据,标准 role 由Qt::ItemDataRole定义。
我们可以向 model 请求 item 目的数据,方法是将 item 目对应的 model 索引传递给 model ,并指定一个 role 来获得我们想要的数据类型:
QVariant value = model->data(index, role);
![]() | role 向 model 指示所引用的数据类型。 iew 可以以不同的方式显示 role ,因此为每个 role 提供适当的信息非常重要。创建新 model 一节更详细地介绍了 role 的一些特定用途。 |
item 目数据的最常见用途是由 Qt::ItemDataRole 中定义的标准 role 覆盖的。通过为每个 role 提供适当的 item 目数据, model 可以向 view 和 delegate 提供提示,说明 item 目应该如何呈现给用户。不同类型的 view 可以根据需要自由地解释或忽略此信息。还可以为特定于应用程序的目的定义其他 role 。
4. 总结
- model 索引以一种独立于任何底层数据结构的方式,为 model 提供关于 item 目位置的 view 和 delegate 信息。
- item 通过它们的行号和列号以及它们的父 item 的 model 索引来引用。
- model 索引是由 model 根据其他组件(如 view 和 delegate )的请求构造的。
- 如果在使用index()请求索引时为父 item 指定了有效的 model 索引,则返回的索引将引用 model 中该父 item 下面的 item 。获得的索引指向该 item 的子 item 。
- 如果在使用index()请求索引时为父 item 指定了无效的 model 索引,则返回的索引将指向 model 中的顶级 item 。
- role 区分与 item 关联的不同类型的数据。
2. 使用model 的 Index
为了演示如何使用 model 索引从 model 中检索数据,我们设置了一个没有 view 的QFileSystemModel,并在一个小部件中显示文件和目录的名称。虽然这不是使用 model 的正常方式,但它展示了 model 在处理 model 索引时使用的约定。
QFileSystemModel的加载是异步的,以最小化系统资源使用。在处理这个 model 时,我们必须考虑到这一点。
我们用以下方式构建文件系统 model :
QFileSystemModel *model = new QFileSystemModel;
connect(model, &QFileSystemModel::directoryLoaded, [model](const QString &directory) {
QModelIndex parentIndex = model->index(directory);
int numRows = model->rowCount(parentIndex);
});
model->setRootPath(QDir::currentPath);
在这种情况下,我们首先设置一个默认的QFileSystemModel。我们将它连接到一个lambda,使用该 model 提供的index()的特定实现来获取父索引。在lambda表达式中,我们使用rowCount()函数计算 model 的行数。最后,我们设置QFileSystemModel的根路径,让它开始加载数据并触发lambda表达式。
为简单起见,我们只对 model 第一列中的 item 感兴趣。我们依次检查每一行,获取每行中第一个 item 目的 model 索引,并读取存储在 model 中该 item 目的数据。
for (int row = 0; row < numRows; ++row) {
QModelIndex index = model->index(row, 0, parentIndex);
为了获得 model 索引,我们指定行号、列号(第一列为0),以及我们想要的所有元素的父元素对应的 model 索引。存储在每一 item 中的文本可以通过 model 的data()函数取得。我们指定 model 索引和DisplayRole以字符串形式获取 item 目的数据。
QString text = model->data(index, Qt::DisplayRole).toString();
// Display the text in a widget.}
上面的例子演示了从 model 中检索数据的基本原则:
- 使用rowCount()和columnCount()可以得到 model 的维度。这些函数通常需要指定一个父 model 索引。
-model 索引用于访问 model 中的 item 。指定 item 目需要行、列和父 model 索引。 - 要访问 model 中的顶层元素,可以用QModelIndex()指定一个空的 model 索引作为父索引。
- item 目包含不同 role 的数据。要获取特定 role 的数据,必须向 model 提供 model 索引和 role 。
四、View 类
1. 概念
在 model / view 架构中, view 从 model 中获取数据 item 并将它们呈现给用户。
数据的表示方式不必与 model 提供的数据表示形式相似,而且可能与用于存储数据 item 的底层数据结构完全不同。
通过使用 QAbstractItemModel 提供的标准 model 接口和 QAbstractItemView 提供的标准 view 接口,以及使用通用方式表示数据 item 的 model 索引,实现了内容与表现的分离。
view 通常管理从 model 中获得的数据的总体布局。它们可以自己渲染单个数据 item ,或者使用 delegate 来处理渲染和编辑功能。
除了显示数据, view 还处理 item 目之间的导航,以及 item 目选择的一些方面。
这些 view 还实现了基本的用户界面功能,例如上下文菜单和拖放。 view 可以为 item 目提供默认的编辑功能,也可以与 delegate 一起提供自定义编辑器。
可以在没有 model 的情况下构建 view ,但是必须提供 model 才能显示有用的信息。 view 通过使用可以为每个 view 单独维护或在多个 view 之间共享的选择 item 来跟踪用户选择的 item 目。
有些 view ,如QTableView和QTreeView,显示标题和 item 。这些也由一个 view 类QHeaderView实现。
标题通常访问包含它们的 view 的同一个 model 。它们使用QAbstractItemModel::headerData() 函数从 model 中获取数据,并且通常以标签的形式显示标题信息。新的标题可以从QHeaderView类子类化,为 view 提供更专门的标签。
2. 使用Qt 提供的 view
Qt 提供了三个可用的 view 类,它们以大多数用户熟悉的方式呈现 model 中的数据。QListView 可以将 model 中的 item 目显示为简单的列表,或者以经典图标 view 的形式显示。QTreeView 将 model 中的 item 目显示为列表的层次结构,允许以紧凑的方式表示深度嵌套结构。QTableView 以表格的形式呈现 model 中的 item 目,很像电子表格应用程序的布局。
上面显示的标准 view 的默认行为应该足以满足大多数应用程序。它们提供基本的编辑功能,并可以进行定制以适应更专业的用户界面的需求。
1. 使用model
我们将创建的字符串列表 model 作为示例 model ,在其中设置一些数据,并构建一个 view 来显示 model 的内容。这些都可以在一个函数中完成:
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。这就说我们可以使用 model 的抽象接口,并确保代码仍然有效,即使我们将字符串列表 model 替换为不同的 model 。以为用的都是 QAbstractItemModel 接口,这就是多态的牛逼啊!
QListView提供的列表 view 足以显示string列表 model 中的 item 目。我们使用下面的代码来构建 view 和建立 model :
QListView *view = new QListView;
view->setModel(model);
view 按正常方式显示:
view->show();return app.exec();
}
view 渲染 model 的内容,通过 model 的接口访问数据。当用户试图编辑 item 时, view 使用默认 delegate 来提供编辑器部件。
上图显示了QListView如何表示字符串列表 model 中的数据。由于 model 是可编辑的, view 自动允许使用默认 delegate 编辑列表中的每一 item 。
2. 使用 model 的多个 view
为同一个 model 提供多个 view ,只需为每个 view 设置相同的 model 即可。在下面的代码中,我们创建了两个表 view ,每个都使用了我们为这个例子创建的相同的简单表 model :
QTableView *firstTableView = new QTableView;
QTableView *secondTableView = new QTableView;firstTableView->setModel(model);
secondTableView->setModel(model);
在 model / view 架构中使用信号和槽意味着对 model 的更改可以传播到所有附加的 view ,确保我们始终可以访问相同的数据,无论使用的是哪个 view 。
上图显示了同一个 model 的两个不同 view ,每个 view 都包含一些选中的 item 目。尽管来自 model 的数据在整个 view 中一致地显示,但每个 view 都维护自己的内部选择 model 。这在某些情况下可能有用,但对于许多应用程序来说,共享选择 model 是可取的。
3. 处理元素的选择
在 view 中处理元素选择的机制由QItemSelectionModel类提供。所有的标准 view 都默认构建自己的选择 model ,并以正常的方式与它们交互。
view 使用的选择 model 可以通过selectionModel()函数获得,而替换选择 model 可以通过setSelectionModel()指定。当我们想为同一个 model 数据提供多个一致的 view 时,控制 view 使用的选择 model 的能力很有用。
一般来说,除非是 model 或 view 的子类化,否则不需要直接操作选择的内容。不过,如果需要的话,选择 model 的接口也是可以访问的,我们会在12.4.3节中讨论如何处理Item view 中的选择。
1. view 间共享选择
虽然 view 类默认提供自己的选择 model 很方便,但当我们在同一个 model 上使用多个 view 时,通常希望 model 的数据和用户的选择在所有 view 中都保持一致。由于 view 类允许替换它们的内部选择 model ,我们可以使用以下代码实现 view 之间的统一选择:
secondTableView->setSelectionModel(firstTableView->selectionModel());
第二个 view 是第一个 view 的选择 model 。这两个 view 现在都在同一个选择 model 上操作,保持数据和所选 item 目同步。
在上面的示例中,使用了相同类型的两个 view 来显示相同 model 的数据。然而,如果使用了两种不同类型的 view ,则所选择的 item 目在每个 view 中可能表现得非常不同;例如,表 view 中的连续选择可以表示为树 view 中高亮显示的 item 目的片段集。
五、Delegate 类
1. 概念
与 model - view - controller 模式不同, model / view 设计没有包含一个完全独立的组件来管理与用户的交互。通常, view 负责向用户展示 model 数据,并负责处理用户输入。为了在获取输入的方式上具有一定的灵活性,交互由 delegate 执行。这些组件提供输入功能,还负责在某些 view 中渲染单个 item 目。控制 delegate 的标准接口定义在QAbstractItemDelegate类中。
delegate 希望能够通过实现paint()和sizeHint()函数来渲染它们自己的内容。然而,简单的基于部件的 delegate 可以继承QStyledItemDelegate而不是QAbstractItemDelegate,并利用这些函数的默认实现。
delegate 编辑器可以通过使用小部件来管理编辑过程,也可以通过直接处理事件来实现。第一种方法将在本节后面介绍,它也会在Spin Box delegate 的例子中展示。
Pixelator的例子展示了如何创建一个自定义 delegate 来为tableview执行特殊的渲染。
2. 使用现有 delegate
Qt提供的标准 view 使用QStyledItemDelegate实例来提供编辑功能。delegate接口的默认实现会以标准 view (QListView、QTableView和QTreeView)的通常风格渲染元素。
所有标准 role 都由标准 view 使用的默认 delegate 处理。解释它们的方式在QStyledItemDelegate文档中有描述。
view 使用的 delegate 由itemDelegate()函数返回。setItemDelegate()函数允许你为标准 view 安装一个自定义 delegate ,在为自定义 view 设置 delegate 时,必须使用这个函数。
3. 一个简单的 delegate
这里实现的 delegate 使用QSpinBox来提供编辑功能,主要用于显示整数的 model 。虽然我们为此设置了一个自定义的基于整数的表 model ,但我们可以轻松地使用QStandardItemModel,因为自定义 delegate 控制数据输入。我们构建一个表 view 来显示 model 的内容,这将使用自定义 delegate 进行编辑。
我们继承了QStyledItemDelegate的代理,因为我们不想编写自定义的显示函数。但是,我们仍然必须提供管理编辑器部件的函数:
class SpinBoxDelegate : public QStyledItemDelegate
{Q_OBJECTpublic:SpinBoxDelegate(QObject *parent = nullptr);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;
};
注意,构造 delegate 时没有设置编辑器部件。我们只在需要时构建编辑器部件。
1. 提供编辑器
在这个例子中,当表 view 需要提供一个编辑器时,它要求 delegate 提供一个适合于正在修改的 item 的编辑器部件。createEditor()函数提供了 delegate 设置适当部件所需的一切:
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;
}
请注意,我们不需要保留指向编辑器部件的指针,因为当不再需要它时, view 会负责销毁它。
我们在编辑器上安装了 delegate 的默认事件过滤器,以确保它提供了用户期望的标准编辑快捷方式。
可以向编辑器添加额外的快捷方式,以允许更复杂的行为;这些将在编辑提示一节中讨论。
view 通过调用我们稍后为这些目的定义的函数来确保编辑器的数据和几何信息被正确设置。我们可以根据 view 提供的 model 索引创建不同的编辑器。例如,如果我们有一列整数和一列字符串,我们可以返回QSpinBox或QLineEdit,这取决于正在编辑哪一列。
delegate 必须提供将 model 数据复制到编辑器中的函数。在这个例子中,我们读取了存储在display role 中的数据,并相应地设置了spin box中的值。
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);
}
在这个例子中,我们知道编辑器小部件是一个spin box,但我们可以为 model 中的不同类型的数据提供不同的编辑器,在这种情况下,我们需要在访问其成员函数之前将小部件转换为适当的类型。
2. 向 model 提交数据
当用户完成微调框中的值编辑后, view 会调用setModelData()函数,要求 delegate 将编辑后的值存储到 model 中。
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);
}
由于 view 为 delegate 管理编辑器部件,我们只需要使用提供的编辑器内容更新 model 。在本例中,我们确保微调框是最新的,并使用指定的索引用它包含的值更新 model 。
标准的 QStyledItemDelegate 类通过发出 closeEditor() 信号来通知 view 何时完成编辑。 view 确保编辑器部件被关闭和销毁。在这个例子中,我们只提供了简单的编辑功能,所以我们永远不需要发射这个信号。
所有对数据的操作都通过 QAbstractItemModel 提供的接口来执行。这使得 delegate 基本上独立于它所操作的数据类型,但是为了使用某些类型的编辑器部件,必须做一些假设。在这个例子中,我们假设 model 总是包含整数值,但我们仍然可以将此 delegate 用于不同类型的 model ,因为QVariant为意外数据提供了合理的默认值。
3. 更新编辑器的几何形状
delegate 的职责是管理编辑器的几何图形。几何形状必须在编辑器创建时设置,并且当 item 目的大小或在 view 中的位置发生变化时设置。幸运的是,该 view 在 view 选 item 对象中提供了所有必要的几何信息。
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,const QStyleOptionViewItem &option,const QModelIndex &/* index */) const
{editor->setGeometry(option.rect);
}
在这个例子中,我们只使用 item 目矩形中的view选 item 提供的几何信息。呈现具有多个元素的 item 的 delegate 不会直接使用 item 矩形。它将使编辑器相对于 item 目中的其他元素进行定位。
4. Editing hints
在编辑之后, delegate 应该向其他组件提供有关编辑过程结果的提示,并提供有助于任何后续编辑操作的提示。这是通过发送带有适当提示的closeEditor()信号来实现的。
这是由默认的QStyledItemDelegate事件过滤器处理的,我们在构建spin box时安装了它。
可以调整旋转框的行为,使其对用户更友好。在QStyledItemDelegate提供的默认事件过滤器中,如果用户点击Return来确认他们在微调框中的选择,则 delegate 将值提交给 model 并关闭微调框。
我们可以通过在微调框上安装自己的事件过滤器来改变这种行为,并根据需要提供编辑提示;例如,我们可以发送带有EditNextItem提示的closeEditor(),以自动开始编辑 view 中的下一 item 。
另一种不需要使用事件过滤器的方法是提供我们自己的编辑器部件,为了方便,可能会子类化QSpinBox。这种替代方法将使我们能够更多地控制编辑器部件的行为,但代价是编写额外的代码。如果需要自定义标准Qt编辑器部件的行为,在 delegate 中安装事件过滤器通常更容易。
delegate 不必发出这些提示,但是那些不发出提示的 delegate 与应用程序的集成程度较低,而且与那些发出提示以支持常见编辑操作的 delegate 相比,它们的可用性较差。
相关文章:

Qt扫盲-Qt Model/View 理论总结 [上篇]
Qt Model/View 理论总结 [上篇] 一、概述1.model / view 架构2. Model3. View4. Delegate5. 排序6. 快捷类 二、使用model/view1. Qt包含两种 model2. 在现有 model 中使用 view 三、Model 类1. 基本概念1.model 索引2. 行和列2. item 的父 item3. Item roles4. 总结 2. 使用mo…...

【猿灰灰赠书活动 - 01期】- 【Python网络爬虫入门到实战】
说明:博文为大家争取福利,与机械工业出版社合作进行送书活动 图书:《Python网络爬虫入门到实战》 一、好书推荐 图书介绍 本书介绍了Python3网络爬虫的常见技术。首先介绍了网页的基础知识,然后介绍了urllib、Requests请求库以及X…...

小兔鲜项目 uniapp (1)
目录 项目架构 uni-app小兔鲜儿电商项目架构 小兔鲜儿电商课程安排 创建uni-app项目 1.通过HBuilderX创建 2.通过命令行创建 pages.json和tabBar案例 uni-app和原生小程序开发区别 用VS Code开发uni-app项目 拉取小兔鲜儿项目模板代码 基础架构–引入uni-ui组件库 操…...
盛弘电气2021秋招笔试题
笔试时间:2020.09.16,60分钟 宣讲会后直接笔试,若通过会有两轮面试,7-15 天出结果。 题型:简答题8道,每题5分,共40分。编程题4道,每题15分,共60分。 公司介绍:公司现阶段主要产品为充电桩,专注于电力电子技术控制电能,交直流变换。 薪资待遇:本科8-15K,研究生…...

Poco框架(跨平台自动化测试框架)
Poco基于UI控件搜索原理 ,适用于Android、iOS原生和各种主流的游戏引擎应用。 中文官方文档:欢迎使用Poco (ポコ) UI自动化框架 — poco 1.0 文档 参考文档: Poco介绍 - Airtest Project Docs 环境准备 安装库:pip install po…...
使用RANSAC算法在点云中拟合原始3D形状:pyRANSAC-3D的介绍和应用
随机样本共识(RANSAC)是一种强大的算法,用于从数据集中估计数学模型的参数,特别是在数据包含大量异常值时。在3D计算机视觉中,RANSAC常用于从点云数据中拟合原始形状,例如平面、长方体和圆柱体。本文将介绍一个名为pyRANSAC-3D的开源库,它提供了RANSAC算法的Python实现,…...

GPT-3.5 人工智能还是人工智障?——西红柿炒钢丝球!!
人工智能还是人工智障?——西红柿炒钢丝球 西红柿炒钢丝球的 基本信息西红柿炒钢丝球的 详细制作方法材料步骤 备注幕后花絮。。。。。。。。。关于GPT-3.5,你的看法: 西红柿炒钢丝球的 基本信息 西红柿炒钢丝球是一道具有悠久历史的传统中式…...
移动技术相关基本概念
信息网络隔离装置 一种能够保障企业信息网络安全的高级网络设备,主要作用是隔离内外网,阻隔外界攻击,保护企业网络不遭受黑客攻击、木马病毒入侵、信息泄露等安全威胁。同时还能对企业内部的流量进行监视,保护企业敏感数据不被内…...

数学建模—分类模型
本讲将介绍分类模型。对于而分类模型,我们将介绍逻辑回归(logistic regression)和Fisher线性判别分析两种分类算法;对于多分类模型,我们将简单介绍Spss中的多分类线性判别分析和多分类逻辑回归的操作步骤下。 本题按水…...
腾讯云SA3服务器AMD处理器CPU网络带宽性能详解
腾讯云AMD服务器SA3实例CPU采用2.55GHz主频的AMD EPYCTM Milan处理器,睿频3.5GHz,搭载最新一代八通道DDR4,内存计算性能稳定,默认网络优化,最高内网收发能力达1900万pps,最高内网带宽可支持100Gbps。腾讯云…...
Vue组件之间的传值汇总
组件之间的传值 1、父传子 props 2、父传子 slot 3、父传子 不建议用 attrs 4、 子传父 ref 5、子传父 emit 6、povide/inject只能在setup的时候用。 7、利用vuex和pinia去实现数据的交互 1、实现代码App.vue <script setup>import TestProps from ./components/T…...

Cadence OrCAD Capture CIS批量替换GND符号的方法
🏡《总目录》 🏡《宝典目录》 目录 1,概述2,方法3,总结1,概述 如下图所示,有时由于绘图是从多个地方复制粘贴而来,一个图纸中会存在多种GND符号。此时比较容易引起GND网络名不同意的问题,为了避免该问题可对其批量替换。 2,方法 第1步:选择需要替换的GND符号…...

图像的转置之c++实现(qt + 不调包)
1.基本原理 图像的转置就是将图像的横坐标和纵坐标交换位置,和矩阵的转置是一样的,公式见下: 2.代码实现(代码是我以前自学图像处理时写的,代码很粗糙没做任何优化,但很好理解) /*图像的转置函…...
qt中cmake自动处理ui文件的前提
说明:个人理解,未必正确 参考了下面的网址 http://cn.voidcc.com/question/p-wpcanvtj-tn.html http://cn.voidcc.com/question/p-wpcanvtj-tn.html cmake中将set(CMAKE_AUTOUIC ON)打开 set(CMAKE_AUTOUIC ON) # 自动处理ui文件, 自动处理ui文件是有…...

python接口自动化之使用requests库发送http请求
requests库 什么是Requests ?Requests 是⽤Python语⾔编写,基于urllib,采⽤Apache2 Licensed开源协议的 HTTP 库。它⽐ urllib 更加⽅便,可以节约我们⼤量的⼯作,完全满⾜HTTP测试需求。 安装:cm…...

flink kafka消费者如何处理kafka主题的rebalance
背景: 我们日常使用kafka客户端消费kafka主题的消息时,当消费者退出/加入消费者组,kafka主题分区数有变等事件发生时,都会导致rebalance的发生,此时一般情况下,如果我们不自己处理offset,我们不…...
【Spring】基于xml文件和注解方式的自动装配
自动装配:根据指定的策略,在IOC容器中匹配某个bean,自动为bean中的类类型属性或接口类型的属性赋值,可以通过bean标签中的autowire属性设置自动装配的策略。 一、基于xml文件 一个类型的bean在IOC容器中只出现一次,默…...

ArcGIS Pro技术应用(暨基础入门、制图、空间分析、影像分析、三维建模、空间统计分析与建模、python融合)
GIS是利用电子计算机及其外部设备,采集、存储、分析和描述整个或部分地球表面与空间信息系统。简单地讲,它是在一定的地域内,将地理空间信息和 一些与该地域地理信息相关的属性信息结合起来,达到对地理和属性信息的综合管理。GIS的…...

『赠书活动 | 第十七期』《Python网络爬虫:从入门到实战》
💗wei_shuo的个人主页 💫wei_shuo的学习社区 🌐Hello World ! 『赠书活动 | 第十七期』 本期书籍:《Python网络爬虫:从入门到实战》 赠书规则:评论区:点赞|收…...

C++——vector介绍及其简要模拟实现
vector的介绍 此主题介绍转载自(https://cplusplus.com/reference/vector/vector/) 1.vector是一个表示可变大小数组的序列容器 2.vector同数组一样,采用连续存储空间来存储元素,这样可以用下标来对vector中的元素进行访问,但是vector的大…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...

Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...