模型/视图教程
每个 UI 开发者都应该了解 ModelView 编程,本教程的目标是向您提供一个易于理解的 ModelView 编程入门介绍。
表格、列表和树小部件是 GUI 中经常使用的组件。这些小部件有两种不同的方式来访问它们的数据。传统方式涉及到包含内部数据存储容器的组件。这种方法非常直观,但在许多非平凡应用中,它会导致数据同步问题。第二种方法是模型/视图编程,其中组件不维护内部数据容器。它们通过标准化接口访问外部数据,因此避免了数据重复。起初这可能会觉得复杂,但如果仔细观察,它不仅容易掌握,而且模型/视图编程的许多好处也变得更加清晰。
在这个过程中,我们将了解 Qt 提供的一些基本技术,例如
- 标准小部件与模型/视图小部件的差异
- 表单和模型之间的适配器
- 开发简单的模型/视图应用程序
- 预定义模型
- 中级主题,如
- 树视图
- 选择
- 委托
- 使用模型测试进行调试
您还将了解,您的应用程序是否可以使用模型/视图编程来更容易地编写,或者经典小部件是否也能同样良好地工作。
本教程包含示例代码,您可以编辑并将其集成到项目中。教程的源代码位于 Qt 的 examples/widgets/tutorials/modelview 目录中。
要获取更详细的信息,您还可以查看参考文档。
1. 简介
模型/视图是一种技术,用于在处理数据集的小部件中将数据与视图分离。标准小部件不是为将数据与视图分离而设计的,这也是 Qt 有两种不同类型小部件的原因。这两种类型的小部件外观相同,但它们与数据的不同交互。
标准小部件使用小部件本身的一部分数据。 | |
视图类在外部数据(模型)上运行 |
1.1 标准小部件
让我们更仔细地看看一个标准表格小部件。表格小部件是一个二维数组的数据元素,用户可以更改。可以通过读取和写入表格小部件提供的数据元素将表格小部件集成到程序流中。这种方法非常直观且在许多应用中非常有用,但使用标准表格小部件显示和编辑数据库表可能会出现问题。必须协调两个数据副本:一个在组件外部,一个在组件内部。开发者负责同步这两个版本。除此之外,展示和数据之间的紧密耦合使得编写单元测试更加困难。
1.2 模型/视图拯救之
模型/视图架构提升,以提供更灵活的解决方案。模型/视图消除了标准小部件可能出现的数据一致性问题的困扰。模型/视图还使得使用相同数据的多个视图变得更加容易,因为一个模型可以被传递到多个视图中。最重要的区别是模型/视图小部件不存储在表格单元格后的数据。实际上,它们直接从您的数据中运行。由于视图类不了解您数据的结构,您需要提供一个包装器,使您的数据符合QAbstractItemModel接口。视图使用该接口从数据中读取和写入。任何实现QAbstractItemModel接口的类的实例都被称为模型。一旦视图接收到模型的指针,它将读取和显示其内容,并成为其编辑器。
1.3 模型/视图小部件概述
以下是模型/视图小部件及其对应的标准小部件的概述。
小部件 | 标准小部件 (一个基于项目的便利类) | 模型/视图视图类 (用于外部数据) |
---|---|---|
QListWidget | QListView | |
QTableWidget | QTableView | |
QTreeWidget | QTreeView | |
QColumnView将树展示为列表层次结构 | ||
QComboBox既可以作为视图类,也可以作为传统小部件使用 |
1.4 在表单和模型之间使用适配器
在表单和模型之间有适配器会大有用处。
我们可以直接从表中编辑存储在表中的数据,但在文本字段中编辑数据要舒适得多。没有直接模型/视图对应的项目来分离操作单个值(如QLineEdit、QCheckBox)和小部件的数据和视图,因此我们需要适配器来连接表单和数据源。
QDataWidgetMapper是一个很好的解决方案,因为它将表单小部件映射到表格行,使得为数据库表构建表单变得非常容易。
适配器的另一个例子是QCompleter。Qt为QComboBox和QLineEdit等Qt小部件提供自动完成,如下所示。 QCompleter使用模型作为其数据源。
2. 简单的模型/视图应用程序
如果您想开发模型/视图应用程序,您应该从哪里开始?我们建议从简单的示例开始,并逐步扩展它。这使理解架构变得容易得多。在调用IDE之前试图详细了解模型/视图架构,已被证明对许多开发人员来说不那么方便。从具有演示数据的简单模型/视图应用程序开始要容易得多!只需用您自己的数据替换以下示例中的数据。
以下有7个非常简单且独立的独立应用程序,展示了模型/视图编程的不同方面。源代码可在examples/widgets/tutorials/modelview
目录中找到。
2.1 只读表
我们从一个使用QTableView来展示数据的示例应用程序开始。我们稍后将会添加编辑功能。
(文件来源:examples/widgets/tutorials/modelview/1_readonly/main.cpp)
// main.cpp #include <QApplication> #include <QTableView> #include "mymodel.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); QTableView tableView; MyModel myModel; tableView.setModel(&myModel); tableView.show(); return a.exec(); }
我们有标准的main()函数
这里是有趣的部份:我们创建了一个MyModel的实例,并使用tableView.setModel(&myModel);将它的指针传递给tableView。 tableView将调用它收到的指针的方法以找出两件事:
- 应显示多少行和列。
- 应将什么内容打印到每个单元格。
模型需要一些代码来响应这些。
我们有一个表格数据集,因此从更容易使用的QAbstractTableModel开始,而不是更通用的QAbstractItemModel。
(文件来源:examples/widgets/tutorials/modelview/1_readonly/mymodel.h)
// mymodel.h #include <QAbstractTableModel> class MyModel : public QAbstractTableModel { Q_OBJECT public: explicit MyModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; };
QAbstractTableModel需要实现三个抽象方法。
(文件来源:examples/widgets/tutorials/modelview/1_readonly/mymodel.cpp)
// mymodel.cpp #include "mymodel.h" MyModel::MyModel(QObject *parent) : QAbstractTableModel(parent) { } int MyModel::rowCount(const QModelIndex & /*parent*/) const { return 2; } int MyModel::columnCount(const QModelIndex & /*parent*/) const { return 3; } QVariant MyModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) return QString("Row%1, Column%2") .arg(index.row() + 1) .arg(index.column() +1); return QVariant(); }
行数和列数由MyModel::rowCount()和MyModel::columnCount()提供。当视图需要知道单元格的文本时,它会调用MyModel::data()方法。行和列信息通过参数index
指定,并设置角色为Qt::DisplayRole。其他角色将在下一节介绍。在我们的示例中,要显示的数据是生成的。在真实的应用程序中,MyModel
将有一个称为MyData
的成员,它作为所有读取和写入操作的目标。
这个小示例展示了模型的无积极性。模型不知道何时使用或需要哪些数据。它只是在视图请求时简单地提供数据。
当模型的数据需要更改时会发生什么?视图如何意识到数据已更改并需要重新读取?模型必须发出一个信号,指明已更改的单元格范围。这将在2.3节中演示。
2.2 用角色扩展只读示例
除了控制视图显示的文本外,模型还控制文本的显示方式。当我们对模型进行轻微更改时,我们得到以下结果:
实际上,除了data()方法外,不需要更改来设置字体、背景颜色、对齐方式和复选框。以下是产生上述结果的data()方法。不同的是,这次我们使用int参数role根据其返回不同信息。
(文件来源:examples/widgets/tutorials/modelview/2_formatting/mymodel.cpp)
// mymodel.cpp QVariant MyModel::data(const QModelIndex &index, int role) const { int row = index.row(); int col = index.column(); // generate a log message when this method gets called qDebug() << QString("row %1, col%2, role %3") .arg(row).arg(col).arg(role); switch (role) { case Qt::DisplayRole: if (row == 0 && col == 1) return QString("<--left"); if (row == 1 && col == 1) return QString("right-->"); return QString("Row%1, Column%2") .arg(row + 1) .arg(col +1); case Qt::FontRole: if (row == 0 && col == 0) { // change font only for cell(0,0) QFont boldFont; boldFont.setBold(true); return boldFont; } break; case Qt::BackgroundRole: if (row == 1 && col == 2) // change background only for cell(1,2) return QBrush(Qt::red); break; case Qt::TextAlignmentRole: if (row == 1 && col == 1) // change text alignment only for cell(1,1) return int(Qt::AlignRight | Qt::AlignVCenter); break; case Qt::CheckStateRole: if (row == 1 && col == 0) // add a checkbox to cell(1,0) return Qt::Checked; break; } return QVariant(); }
每个格式化属性将通过单独调用data()方法从模型中请求。使用role
参数让模型知道请求的是哪个属性。
有关Qt::ItemDataRole枚举的功能,请参阅Qt命名空间文档。
现在我们需要确定使用分离的模型对应用程序性能的影响,因此让我们追踪视图调用data()方法频率。为了跟踪视图调用模型的频率,我们在data()方法中放置了一个调试语句,它记录到错误输出流。在我们的简单例子中,data()将调用42次。每次将光标悬停在字段上,data()都将再次调用——每个单元格调用7次。这就是为什么确保在调用data()时数据可用并且昂贵的查找操作已缓存的很重要的原因。
2.3 单元格内部的时钟
我们仍然有一个只读表,但这次内容每秒都会改变,因为我们正在显示当前时间。
(文件来源:examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)
QVariant MyModel::data(const QModelIndex &index, int role) const { int row = index.row(); int col = index.column(); if (role == Qt::DisplayRole && row == 0 && col == 0) return QTime::currentTime().toString(); return QVariant(); }
为了使时钟滴答作响,我们需要每秒通知视图时间已经改变并且需要重新读取。我们使用定时器来做这件事。在构造函数中,我们将其间隔设置为1秒,并且连接其timeout信号。
(文件来源:examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)
MyModel::MyModel(QObject *parent) : QAbstractTableModel(parent) , timer(new QTimer(this)) { timer->setInterval(1000); connect(timer, &QTimer::timeout , this, &MyModel::timerHit); timer->start(); }
这里是相应的槽函数
(文件来源:examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)
void MyModel::timerHit() { // we identify the top left cell QModelIndex topLeft = createIndex(0,0); // emit a signal to make the view reread identified data emit dataChanged(topLeft, topLeft, {Qt::DisplayRole}); }
我们通过发射dataChanged()信号,要求视图再次读取左上角单元格中的数据。值得注意的是,我们没有明确地将dataChanged()信号连接到视图。在调用setModel()时,这自动发生。
2.4 设置列和行的标题
可以通过视图方法隐藏标题:tableView->verticalHeader()->hide();
但是,标题内容是通过模型设置的,因此我们重实现了headerData()方法
(文件来源:examples/widgets/tutorials/modelview/4_headers/mymodel.cpp)
QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { switch (section) { case 0: return QString("first"); case 1: return QString("second"); case 2: return QString("third"); } } return QVariant(); }
请注意,方法headerData()还有一个参数角色,它在MyModel::data()中的意义相同。
2.5 最小编辑示例
在本例中,我们将构建一个应用程序,它可以自动通过重复输入到表格单元格中的值来用内容填充窗口标题。为了能够轻松访问窗口标题,我们将QTableView置于QMainWindow中。
模型决定了编辑功能是否可用。我们只需修改模型,即可启用可用编辑功能。这是通过重写以下虚拟方法来完成的:setData() 和 flags()。
(文件来源:examples/widgets/tutorials/modelview/5_edit/mymodel.h)
// mymodel.h #include <QAbstractTableModel> #include <QString> const int COLS= 3; const int ROWS= 2; class MyModel : public QAbstractTableModel { Q_OBJECT public: MyModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex &index) const override; private: QString m_gridData[ROWS][COLS]; //holds text entered into QTableView signals: void editCompleted(const QString &); };
我们使用二维数组QString m_gridData
来存储我们的数据。这使得m_gridData
成为MyModel
的内核。其余的MyModel
则像是一个包装器,并将m_gridData
适配到QAbstractItemModel接口。我们还介绍了editCompleted()
信号,这使得将修改后的文本传输到窗口标题成为可能。
(文件来源:examples/widgets/tutorials/modelview/5_edit/mymodel.cpp)
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == Qt::EditRole) { if (!checkIndex(index)) return false; //save value from editor to member m_gridData m_gridData[index.row()][index.column()] = value.toString(); //for presentation purposes only: build and emit a joined string QString result; for (int row = 0; row < ROWS; row++) { for (int col= 0; col < COLS; col++) result += m_gridData[row][col] + ' '; } emit editCompleted(result); return true; } return false; }
setData()函数会在用户编辑单元格时被调用。index
参数告诉我们哪个字段被编辑了,value
提供了编辑过程的结果。因为我们单元格只包含文本,所以角色始终设置为Qt::EditRole。如果存在复选框并且用户权限允许选择复选框,也会带有Qt::CheckStateRole角色的调用。
(文件来源:examples/widgets/tutorials/modelview/5_edit/mymodel.cpp)
Qt::ItemFlags MyModel::flags(const QModelIndex &index) const { return Qt::ItemIsEditable | QAbstractTableModel::flags(index); }
可以用flags()调整单元格的各种属性。
只要返回Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled,就可以显示单元格可被选择的编辑器。
如果编辑单元格修改的数据比该单元格中的数据更多,模型必须发出dataChanged()信号,以便读取已更改的数据。
3. 中级主题
3.1 树视图
您可以将上面的示例转换为具有树视图的应用程序。只需将QTableView替换为QTreeView即可,这将结果在一个读写树中。不需要对模型进行任何更改。由于模型本身没有层次结构,因此树中不会有任何层次结构。
QListView、QTableView和QTreeView都使用模型抽象,这是一个合并的列表、表格和树。这使得可以使用来自同一模型的不同类型的视图类。
这是我们模型现在的样子
我们想展示一个真实的树。上述示例中,我们为了构建模型而将数据包装起来。这次我们使用QStandardItemModel,这是一个包含层次数据并实现了QAbstractItemModel的容器。要显示树,QStandardItemModel必须用QStandardItem填充,它能够持有所有项的标准属性,如文本、字体、复选框或画笔。
(文件来源:examples/widgets/tutorials/modelview/6_treeview/mainwindow.cpp)
// modelview.cpp #include "mainwindow.h" #include <QTreeView> #include <QStandardItemModel> #include <QStandardItem> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , treeView(new QTreeView(this)) , standardModel(new QStandardItemModel(this)) { setCentralWidget(treeView); QList<QStandardItem *> preparedRow = prepareRow("first", "second", "third"); QStandardItem *item = standardModel->invisibleRootItem(); // adding a row to the invisible root item produces a root element item->appendRow(preparedRow); QList<QStandardItem *> secondRow = prepareRow("111", "222", "333"); // adding a row to an item starts a subtree preparedRow.first()->appendRow(secondRow); treeView->setModel(standardModel); treeView->expandAll(); } QList<QStandardItem *> MainWindow::prepareRow(const QString &first, const QString &second, const QString &third) const { return {new QStandardItem(first), new QStandardItem(second), new QStandardItem(third)}; }
我们简单实例化一个QStandardItemModel并在构造函数中添加几个QStandardItems。然后我们可以创建一个层次数据结构,因为QStandardItem可以包含其他QStandardItems。节点在视图内部可以折叠和展开。
3.2 与选择一起工作
我们想要访问所选项的内容并将其与层次级别一起输出到窗口标题中。
因此,让我们创建一些项
(文件来源:examples/widgets/tutorials/modelview/7_selections/mainwindow.cpp)
#include "mainwindow.h" #include <QTreeView> #include <QStandardItemModel> #include <QItemSelectionModel> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , treeView(new QTreeView(this)) , standardModel(new QStandardItemModel(this)) { setCentralWidget(treeView); auto *rootNode = standardModel->invisibleRootItem(); // defining a couple of items auto *americaItem = new QStandardItem("America"); auto *mexicoItem = new QStandardItem("Canada"); auto *usaItem = new QStandardItem("USA"); auto *bostonItem = new QStandardItem("Boston"); auto *europeItem = new QStandardItem("Europe"); auto *italyItem = new QStandardItem("Italy"); auto *romeItem = new QStandardItem("Rome"); auto *veronaItem = new QStandardItem("Verona"); // building up the hierarchy rootNode-> appendRow(americaItem); rootNode-> appendRow(europeItem); americaItem-> appendRow(mexicoItem); americaItem-> appendRow(usaItem); usaItem-> appendRow(bostonItem); europeItem-> appendRow(italyItem); italyItem-> appendRow(romeItem); italyItem-> appendRow(veronaItem); // register the model treeView->setModel(standardModel); treeView->expandAll(); // selection changes shall trigger a slot QItemSelectionModel *selectionModel = treeView->selectionModel(); connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &MainWindow::selectionChangedSlot); }
视图在独立的选择模型中管理选择,可以通过selectionModel()方法检索。我们检索选择模型以将其槽连接到其selectionChanged()信号。
(文件来源:examples/widgets/tutorials/modelview/7_selections/mainwindow.cpp)
void MainWindow::selectionChangedSlot(const QItemSelection & /*newSelection*/, const QItemSelection & /*oldSelection*/) { // get the text of the selected item const QModelIndex index = treeView->selectionModel()->currentIndex(); QString selectedText = index.data(Qt::DisplayRole).toString(); // find out the hierarchy level of the selected item int hierarchyLevel = 1; QModelIndex seekRoot = index; while (seekRoot.parent().isValid()) { seekRoot = seekRoot.parent(); hierarchyLevel++; } QString showString = QString("%1, Level %2").arg(selectedText) .arg(hierarchyLevel); setWindowTitle(showString); }
通过调用 treeView->selectionModel()->currentIndex() 获取对应的模型索引,然后使用模型索引获取字段字符串。然后我们只需计算项目级别的 hierarchyLevel
。顶层项目没有父项,其 parent() 方法将返回默认构造的 QModelIndex()。这就是为什么我们使用 parent() 方法迭代到顶层,并计算迭代过程中执行的步骤数量。
如上所示,可以检索选择模型,但也可以使用 QAbstractItemView::setSelectionModel 进行设置。这就是为什么可以有3个视图类具有同步选择的原因,因为只使用了一个选择模型的实例。要在3个视图之间共享选择模型,请使用 selectionModel() 将结果分配给第二个和第三个视图类,以及使用 setSelectionModel。
3.3 预定义模型
使用模型/视图的典型方法是包装特定数据,使其可用于视图类。然而,Qt还提供了预定义的模型以用于常见的底层数据结构。如果其中某个可用数据结构适合您的应用程序,则预定义模型是一个很好的选择。
QStringListModel | 存储字符串列表 |
QStandardItemModel | 存储任意层级的项 |
QFileSystemModel | 封装本地文件系统 |
QSqlQueryModel | 封装 SQL 结果集 |
QSqlTableModel | 封装 SQL 表 |
QSqlRelationalTableModel | 封装带有外键的 SQL 表 |
QSortFilterProxyModel | 对另一个模型进行排序和/或过滤 |
3.4 代理
在所有之前的示例中,数据均以单元格中的文本或复选框形式表示,并以文本或复选框的形式进行编辑。提供这些表示和编辑服务的组件称为 代理。我们刚开始使用代理,因为视图使用默认代理。但是想象一下,我们想有一个不同的编辑器(例如,滑块或下拉列表)。或者想象一下,我们想以图形方式表示数据。让我们看一下 Star Delegate 示例,其中使用了星号来表示评分
视图有一个 setItemDelegate() 方法,用于替换默认代理并安装自定义代理。可以通过创建一个从 QStyledItemDelegate 继承的类来编写新的代理。为了编写一个显示星号且没有输入功能的自定义代理,我们只需要重写2个方法。
class StarDelegate : public QStyledItemDelegate { Q_OBJECT public: StarDelegate(QWidget *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; };
paint() 根据底层数据的内容绘制星号。可以通过调用 index.data() 来查找数据。代理的 sizeHint() 方法用于获取每个星号的尺寸,因此单元格将提供足够的高度和宽度来容纳星号。
如果您想在视图类的网格内以自定义图形表示方式显示数据,写自定义代理是正确的选择。如果您想离开网格,则不会使用自定义代理,而是使用自定义视图类。
Qt 文档中对代理的其他引用
3.5 使用 ModelTest 进行调试
模型的被动性质给程序员带来了新的挑战。模型的不一致性可能导致应用崩溃。由于模型被视图的多次调用所影响,很难确定是哪个调用导致了应用的崩溃以及哪个操作引入了问题。
Qt Labs提供了一种名为ModelTest的软件,可以在编程运行时检查模型。每次模型更改时,ModelTest都会扫描模型并在断言中报告错误。这对于树模型尤为重要,因为它们的层次结构留下了许多细微不一致的可能性。
与视图类不同,ModelTest使用超出范围的索引来测试模型。这意味着即使在不使用ModelTest的情况下运行良好,应用也可能因ModelTest而崩溃。因此,当你使用ModelTest时,也需要处理所有超出范围的索引。
4. 其他信息来源
4.1 书籍
Qt的文档中详细介绍了模型/视图编程,也见于几本优秀的书籍中。
- C++ GUI Programming with Qt 4 / Jasmin Blanchette, Mark Summerfield, Prentice Hall, 2nd edition, ISBN 0-13-235416-0. 也有德语版本:C++ GUI Programmierung mit Qt 4: Die offizielle Einführung, Addison-Wesley, ISBN 3-827327-29-6
- Qt4编程的艺术,构建Qt应用程序的艺术 / Daniel Molkentin, Open Source Press, ISBN 1-59327-147-6. 从 Qt 4, Einführung in die Applikationsentwicklung 翻译而来,Open Source Press, ISBN 3-937514-12-0。
- Qt开发的基石 / Johan Thelin, Apress, ISBN 1-59059-831-8。
- 高级Qt编程 / Mark Summerfield, Prentice Hall, ISBN 0-321-63590-6. 本书覆盖了150多页的模型/视图编程。
以下列表概述了上面列出的前三本书中包含的示例程序的概述。其中一些程序是开发类似应用的优秀模板。
示例名称 | 使用的视图类 | 使用的模型 | 涵盖的方面 | |
---|---|---|---|---|
团队负责人 | QListview | QStringListModel | 第1本书,第10章,图10.6 | |
颜色名称 | QListView | QSortFilterProxyModel 应用到 QStringListModel | 第1本书,第10章,图10.8 | |
货币 | QTableView | 基于 QAbstractTableModel 的自定义模型 | 只读 | 第1本书,第10章,图10.10 |
城市 | QTableView | 基于 QAbstractTableModel 的自定义模型 | 读 / 写 | 第1本书,第10章,图10.12 |
布尔解析器 | QTreeView | 基于 QAbstractItemModel 的自定义模型 | 只读 | 第1本书,第10章,图10.14 |
轨道编辑器 | QTableWidget | 提供自定义编辑器的自定义代理 | 第1本书,第10章,图10.15 | |
地址簿 | QListView QTableView QTreeView | 基于 QAbstractTableModel 的自定义模型 | 读 / 写 | 第2本书,第8.4章 |
带排序功能的地址簿 | QSortfilterProxyModel | 引入排序和过滤功能 | 第2本书,第8.5章 | |
带复选框的地址簿 | 在模型/视图引入复选框 | 第2本书,第8.6章 | ||
转置网格的地址簿 | 基于 QAbstractProxyModel 的自定义代理模型 | 引入自定义模型 | 第2本书,第8.7章 | |
支持拖放功能的地址簿 | 引入拖放支持 | 第2本书,第8.8章 | ||
带有自定义编辑器的地址簿 | 引入自定义代理 | 第2本书,第8.9章 | ||
视图 | QListView QTableView QTreeView | QStandardItemModel | 只读 | 第3本书,第5章,图5-3 |
Bardelegate | QTableView | 基于 QAbstractItemDelegate 的展示的自定义代理 | 第3本书,第5章,图5-5 | |
Editdelegate | QTableView | 基于 QAbstractItemDelegate 的编辑的自定义代理 | 第3册,第5章,图5-6 | |
单项视图 | 基于QAbstractItemView的自定义视图 | 自定义视图 | 第3册,第5章,图5-7 | |
列表模型 | QTableView | 基于QAbstractTableModel的自定义模型 | 只读 | 第3册,第5章,图5-8 |
树形模型 | QTreeView | 基于QAbstractItemModel的自定义模型 | 只读 | 第3册,第5章,图5-10 |
编辑整数 | QListView | 基于QAbstractListModel的自定义模型 | 读 / 写 | 第3册,第5章,第5-37列表,图5-11 |
排序 | QTableView | QSortFilterProxyModel 应用到 QStringListModel | 演示排序 | 第3册,第5章,图5-12 |
4.2 Qt文档
Qt 5.0附带19个模型/视图示例。示例可以在项视图示例页面找到。
示例名称 | 使用的视图类 | 使用的模型 | 涵盖的方面 |
---|---|---|---|
地址簿 | QTableView | QAbstractTableModel QSortFilterProxyModel | 使用QSortFilterProxyModel从单一数据池生成不同的子集 |
基本的排序/过滤模型 | QTreeView | QStandardItemModel QSortFilterProxyModel | |
图表 | 自定义视图 | QStandardItemModel | 设计与选择模型协作的自定义视图 |
颜色编辑器工厂 | QTableWidget | 通过新的自定义编辑器增强标准委托以选择颜色 | |
组合小部件映射器 | QDataWidgetMapper用于映射QLineEdit、QTextEdit和QComboBox | QStandardItemModel | 展示了QComboBox可以作为一个视图类 |
自定义排序/过滤模型 | QTreeView | QStandardItemModel QSortFilterProxyModel | 子类化QSortFilterProxyModel以实现高级排序和过滤 |
目录视图 | QTreeView | QFileSystemModel | 一个非常小的示例,演示如何将模型分配给视图 |
可编辑的树形模型 | QTreeView | 自定义树形模型 | 高级树木操作的综合性示例,展示了使用底层自定义模型的单元格编辑和树结构 |
获取更多 | QListView | 自定义列表模型 | 动态更改模型 |
冻结列 | QTableView | QStandardItemModel | |
访谈 | 多个 | 自定义条目模型 | 多个视图 |
像素化器 | QTableView | 自定义表格模型 | 自定义委托的实现 |
拼图 | QListView | 自定义列表模型 | 支持拖放模型/视图 |
简单的DOM模型 | QTreeView | 自定义树形模型 | 自定义树模型的只读示例 |
简单的树形模型 | QTreeView | 自定义树形模型 | 自定义树模型的只读示例 |
简单的小部件映射器 | QDataWidgetMapper用于映射QLineEdit、QTextEdit和QSpinBox | QStandardItemModel | 基本的QDataWidgetMapper使用方法 |
电子表格 | QTableView | 自定义委托 | |
星型委托 | QTableWidget | 全面的自定义委托示例。 |
有关模型/视图技术的参考文档也可用。
© 2024 The Qt Company Ltd. 本文档的贡献是各自所有者的版权。本文档是根据自由软件基金会的发布并由其发布的GNU自由文档许可协议版本1.3的条款许可的。Qt和其他相关标志是The Qt Company Ltd.在芬兰和其他全球国家的商标。所有其他商标均为其各自所有者的财产。