每个UI开发人员都应该了解ModelView编程,而本教程的目标就是为您提供一个易于理解的关于这个主题的介绍。
表、列表和树小部件是 GUI 中经常使用的组件。这些小部件有两种不同的方式来访问它们的数据。传统的方法涉及到包含存储数据的内部容器的小部件。这种方法非常直观,但是,在许多重要的应用程序中,它会导致数据同步问题。第二种方法是模型/视图编程,其中小部件不维护内部的数据容器。它们通过标准化接口访问外部数据,从而避免了数据重复。乍一看,这似乎很复杂,但一旦深入了解,它不仅容易掌握,而且模型/视图编程的许多好处也会变得更加清晰。
在这个过程中,我们会了解到Qt提供的一些基本技术,例如:
您还将了解使用模型/视图编程是否可以更容易地编写新应用程序,或者经典的小部件是否也能同样工作。
本教程包含示例代码,供您编辑并集成到项目中。教程的源代码位于Qt的examples/widgets/tutorials/modelview目录中。
要获得更详细的信息,您还可以查看参考文档
模型/视图是一种用于在处理数据集的小部件中从视图中分离数据的技术。标准窗口小部件不是为将数据从视图中分离而设计的,这就是Qt有两种不同类型窗口小部件的原因。这两种类型的小部件外观相同,但它们与数据的交互方式不同。
标准小部件使用的数据是小部件一部分。 | |
---|---|
视图类对外部数据(模型)进行操作 |
让我们仔细看看一个标准的表小部件。表小部件是用户可以更改的数据元素的2D数组。通过读写表小部件提供的数据元素,可以将表小部件集成到程序流中。这种方法在许多应用程序中非常直观和有用,但是使用标准表小部件显示和编辑数据库表可能会有问题。必须协调数据的两个副本:一个在小部件外部;一个在小部件内部。开发人员负责同步两个版本。除此之外,表示和数据的紧密耦合使得编写单元测试更加困难。
模型/视图进一步提供了一个更通用的架构的解决方案。模型/视图消除了标准小部件可能出现的数据一致性问题。模型/视图还使得使用同一数据的多个视图变得更容易,因为一个模型可以传递给多个视图。最重要的区别是模型/视图小部件不将数据存储在表格单元格后面。实际上,它们直接从您的数据进行操作。因为视图类不知道数据的结构,所以需要提供包装器来使数据符合QAbstractItemModel接口。视图使用这个接口来读取和写入数据。实现QAbstractItemModel的类的任何实例都被称为模型。一旦视图接收到模型的指针,它将读取和显示它的内容,并成为它的编辑器。
以下是模型/视图小部件及其相应的标准小部件的概述。
部件 | 标准小部件 (基于项目的便利类) | 模型/视图视图类 (用于外部数据) |
---|---|---|
QListWidget | QlistView | |
QtableWidget | Qtableview | |
QtreeWidget | Qtreeview | |
QColumnView将树作为列表层次结构显示 | ||
QComboBox作为传统小部件工作 | QComboBox也可以用作视图类 |
在表单和模型之间有适配器可以派上用场。
我们可以直接在表本身中编辑存储在表中的数据,但是在文本字段中编辑数据要舒服得多。没有直接对应于模型/视图的数据与视图分离的小部件来操作一个值而不是数据集(QLineEdit 、QCheckBox…),因此我们需要一个适配器来将表单连接到数据源。
QDataWidgetMapper是一个很好的解决方案,因为它将表单小部件映射到表行,并且使为数据库表构建表单变得非常容易。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XRDOzZPr-1607992128752)(https://doc.qt.io/qt-5/images/widgetmapper.png)]
另一个适配器示例是QCompleter。Qt有QCompleter,用于在Qt小部件中提供自动补全功能,如QComboBox和(如下所示)QLineEdit。QCompleter使用一个模型作为它的数据源。
如果你想开发一个模型/视图应用程序,你应该从哪里开始?我们建议从一个简单的示例开始,并逐步扩展它。这使得理解体系结构变得容易得多。在调用IDE之前,尝试理解模型/视图架构的细节对许多开发人员来说是不太方便的。从具有演示数据的简单模型/视图应用程序开始要容易得多。试试吧!只需用您自己的数据替换下面示例中的数据。
下面是7个非常简单和独立的应用程序,它们展示了模型/视图编程的不同方面。源代码可以在examples/widgets/tutorials/modelview目录中找到。
我们从使用 QTableView 显示数据的应用程序开始。稍后我们将添加编辑功能。
(file source: examples/widgets/tutorials/modelview/1_readonly/main.cpp)
// main.cpp
#include
#include
#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更容易使用。
// mymodel.h
#include
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;
};
行数和列数由MyModel::rowCount()和MyModel::columnCount()提供。当视图必须知道单元格的文本是什么时,它调用方法MyModel::data()。用参数索引指定行和列信息,角色设置为Qt::DisplayRole。其他角色将在下一节中介绍。在我们的示例中,生成了应该显示的数据。在真实的应用程序中,MyModel将有一个名为MyData的成员,它作为所有读写操作的目标。
这个小示例演示了模型的被动性质。模型不知道何时使用它或需要哪些数据。它只是在视图每次请求数据时提供数据。
当模型的数据需要更改时,会发生什么? 视图如何意识到数据已更改并需要再次读取? 模型必须发出信号,表明单元格的范围发生了变化。这将在第2.3节中演示。
除了控制视图显示什么文本之外,模型还控制文本的外观。稍微改变模型,得到如下结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b9w8Mwxz-1607992128754)(https://doc.qt.io/qt-5/images/readonlytable_role.png)]
实际上,除了data()方法之外,不需要改变任何东西来设置字体、背景颜色、对齐方式和复选框。下面是产生上述结果的data()方法。不同之处在于,这次我们使用参数int role根据其值返回不同的信息。
(file source: 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 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参数用于让模型知道被请求的属性:
enum Qt::ItemDataRole | Meaning | Type |
---|---|---|
Qt::DisplayRole | 文本 | QString |
Qt::FontRole | 字体 | QFont |
BackgroundRole | 单元格背景的画刷 | QBrush |
Qt::TextAlignmentRole | 文本对齐方式 | enum Qt::AlignmentFlag |
Qt::CheckStateRole | QVariant()强制使用复选框, 复选框设置Qt::Checked 或Qt::Unchecked |
enum Qt::ItemDataRole |
请参阅Qt名称空间文档以了解关于Qt::ItemDataRole enum功能的更多信息。
现在我们需要确定使用独立模型如何影响应用程序的性能,因此让我们跟踪视图调用data()方法的频率。为了跟踪视图调用模型的频率,我们在data()方法中放置了一条调试语句,该语句将登录到错误输出流上。在我们的小示例中,data()将被调用42次。每次将光标悬停在字段上时,data()将再次被调用——每个单元格调用7次。这就是为什么在调用data()和缓存昂贵的查找操作时,确保数据可用非常重要。
我们仍然有一个只读表,但这次内容每秒都在变化,因为我们显示的是当前时间。
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秒,并连接其超时信号。
MyModel::MyModel(QObject *parent)
:QAbstractTableModel(parent)
,timer(new QTimer(this))
{
timer->setInterval(1000);
connect(timer, &QTimer::timeout , this, &MyModel::timerHit);
timer->start();
}
对应的槽如下:
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()时,这将自动发生。
头部内容是通过模型设置的,因此我们要重新实现 headerData()方法
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()也有一个 role 参数,其含义和date() 方法中的role一样
在本例中,我们将构建一个应用程序,通过重复在表格单元格中输入的值来自动填充窗口标题。为了方便地访问窗口标题,我们将QTableView放在QMainWindow中。
模型决定编辑功能是否可用。为了启用编辑功能,我们只需要修改模型。这是通过重新实现以下虚拟方法来实现的:setData()和flags()。
// mymodel.h
#include
#include
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()信号,它可以将修改后的文本传输到窗口标题。
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()。索引参数告诉我们哪个字段已经被编辑,value 提供编辑过程的结果。角色将始终设置为Qt::EditRole,因为单元格只包含文本。如果有一个复选框,并且用户权限设置为允许选中该复选框,那么还将使用角色设置为Qt::CheckStateRole进行调用。
Qt::ItemFlags MyModel::flags(const QModelIndex &index) const
{
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}
单元格的各种属性可以使用flags()进行调整。
返回Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled就足以向编辑器显示可以选择单元格
如果编辑一个单元格所修改的数据多于特定单元格中的数据,则模型必须发出dataChanged()信号,以便读取已更改的数据。
您可以将上面的示例转换为具有树视图的应用程序。简单地用QTreeView替换QTableView,这会产生一个读/写树。不需要对模型进行任何更改。树不会有任何层次结构,因为模型本身没有任何层次结构。
QListView、QTableView和QTreeView都使用了一个模型抽象,即一个合并的列表、表和树。这使得在同一个模型中使用多个不同类型的视图类成为可能。
这是我们的示例模型到目前为止的样子:
我们想呈现一个真实的树。为了创建模型,我们已经将数据封装在上面的示例中。这次我们使用QStandardItemModel,它是分层数据的容器,也实现了QAbstractItemModel。要显示树,QStandardItemModel必须用QStandardItems填充,它能够保存项目的所有标准属性,如文本、字体、复选框或笔刷。
![1](E:\Users\Desktop\1.png) // modelview.cpp
#include "mainwindow.h"
#include
#include
#include
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();
}
我们只需实例化一个QStandardItemModel,并向构造函数中添加两个QStandardItems。然后我们可以创建一个分层的数据结构,因为QStandardItem可以包含其他QStandardItems。节点在视图中被折叠和展开。
我们希望访问所选项目的内容,以便将其输出到窗口标题和层次结构级别中。
让我们创建几个项目:
#include "mainwindow.h"
#include
#include
#include
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, treeView(new QTreeView(this))
, standardModel(new QStandardItemModel(this))
{
setCentralWidget(treeView);
QStandardItem *rootNode = standardModel->invisibleRootItem();
//defining a couple of items
QStandardItem *americaItem = new QStandardItem("America");
QStandardItem *mexicoItem = new QStandardItem("Canada");
QStandardItem *usaItem = new QStandardItem("USA");
QStandardItem *bostonItem = new QStandardItem("Boston");
QStandardItem *europeItem = new QStandardItem("Europe");
QStandardItem *italyItem = new QStandardItem("Italy");
QStandardItem *romeItem = new QStandardItem("Rome");
QStandardItem *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()信号。
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() != QModelIndex()) {
seekRoot = seekRoot.parent();
hierarchyLevel++;
}
QString showString = QString("%1, Level %2").arg(selectedText)
.arg(hierarchyLevel);
setWindowTitle(showString);
}
我们通过调用treeView->selectionModel()->currentIndex()来获得与选择相对应的模型索引,并通过使用模型索引来获得字段的字符串。然后我们只计算项目的层级级别。顶级项没有父项,父()方法将返回一个默认构造的QModelIndex()。这就是为什么我们使用parent()方法迭代到顶层,同时计算迭代期间执行的步骤。
选择模型(如上所示)可以被检索,但是也可以用QAbstractItemView::setSelectionModel进行设置。这就是为什么有3个视图类与同步选择是可能的,因为只有一个选择模型的实例被使用。要在3个视图之间共享选择模型,请使用selectionModel()并使用setSelectionModel()将结果分配给第二个和第三个视图类。
使用模型/视图的典型方法是包装特定数据,使其可用于视图类。然而,Qt还为公共底层数据结构提供了预定义的模型。如果可用的数据结构中有一种适合您的应用程序,那么预定义的模型可能是一个不错的选择。
类 | 说明 |
---|---|
QStringListModel | 存储字符串列表 |
QStandardItemModel | 存储任意层次项 |
QFileSystemModel | 封装本地文件系统 |
QDirModel | 已过时,推荐使用QFileSystemModel |
QSqlQueryModel | 封装SQL结果集 |
QSqlTableModel | 封装一个SQL表 |
QSqlRelationalTableModel | 用外键封装SQL表 |
QSortFilterProxyModel | 对另一个模型进行排序和/或筛选 |
到目前为止的所有例子中,数据都以文本或单元格中的复选框的形式显示,并被编辑为文本或复选框。提供这些表示和编辑服务的组件称为代理。我们只是刚刚开始使用代理,因为视图使用了默认的代理。但是,假设我们想要有一个不同的编辑器(例如,滑动条或下拉列表),或者假设我们想要将数据表示为图形。让我们看一个叫做星型代理的例子,在这个例子中星型用来显示评级:
视图有一个setItemDelegate()方法,该方法替换默认的代理并安装一个自定义代理。可以通过创建继承自QStyledItemDelegate的类来编写新的代理。为了编写一个显示星号且没有输入功能的代理,我们只需要覆盖2个方法。
// 这个代理不完整,还需相应的编辑器,现在只做了解用,完整用法可参考 Star Delegate Example
class StarDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
StarDelegate(QWidget *parent = 0);
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文档中关于代理的其他引用:
模型的被动特性为程序员提供了新的挑战。模型中的不一致可能导致应用程序崩溃。由于模型受到来自视图的大量调用的影响,因此很难找出哪个调用导致应用程序崩溃,以及哪个操作引入了问题。
Qt Labs提供了一种叫做ModelTest的软件,它可以在程序运行时检查模型。每次更改模型时,ModelTest都会扫描模型并使用assert报告错误。这对于树模型尤其重要,因为它们的层次性为细微的不一致留下了许多可能性。
与视图类不同,ModelTest使用范围外的索引来测试模型。这意味着您的应用程序在使用ModelTest时可能会崩溃,即使它在没有ModelTest的情况下可以完美地运行。因此,在使用ModelTest时,您还需要处理超出范围的所有索引。
模型/视图编程在Qt的文档和一些好书中都有相当广泛的介绍。
下面的列表提供了上面列出的前三本书中包含的示例程序的概述。其中一些可以作为开发类似应用程序的非常好的模板。
Example name | View class used | Model used | Aspects covered | |
---|---|---|---|---|
Team Leaders | QListview | QStringListModel | book1,10.6 | |
Directory Viewer | QTreeView | QDirModel | book1,10.7 | |
Color Names | QListView | QSortFilterProxyModel applied to QStringListModel | book1,10.8 | |
Currencies | QTableView | 基于 QAbstractTableModel | 只读 | book1,10.10 |
Cities | QTableView | 基于 QAbstractTableModel | 读/写 | book1,10.12 |
Boolean Parser | QTreeView | 基于 QAbstractItemModel | 只读 | book1,10.14 |
Track Editor | QTableWidget | 提供自定义编辑器的自定义委托 | book1,10.15 | |
Four directory views | QListView QTableView QTreeView | QDirModel | 演示多视图的使用 | Book2, 8.2 |
Address Book | QListView QTableView QTreeView | 基于QAbstractTableModel | 读/写 | Book2, 8.4 |
Address Book with sorting | QSortfilterProxyModel | 引入排序和筛选功能 | Book2, 8.5 | |
Address Book with checkboxes | 在模型/视图中引入复选框 | Book2, 8.6 | ||
Address Book with transposed grid | 基于QAbstractProxyModel | 引入自定义模型 | Book2, 8.7 | |
Address Book with drag and drop | 引入拖放支持 | Book2, 8.8 | ||
Address Book with custom editor | 引入定制的代表 | Book2, 8.9 | ||
Views | QListView QTableView QTreeView | QStandardItemModel | 只读 | book3,5-3 |
Bardelegate | QTableView | 基于QAbstractItemDelegate的表示的自定义委托 | book3,5-5 | |
Editdelegate | QTableView | 自定义委托,用于基于QAbstractItemDelegate进行编辑 | book3,5-6 | |
Singleitemview | Custom view based on QAbstractItemView | 自定义视图 | book3,5-7 | |
listmodel | QTableView | 基于 QAbstractTableModel | 只读 | book3,5-8 |
treemodel | QTreeView | 基于QAbstractItemModel | 只读 | book3,5-10 |
edit integers | QListView | 基于 QAbstractListModel | 读/写 | book3,5-37,5-11 |
sorting | QTableView | QSortFilterProxyModel applied to QStringListModel | 演示了排序 | book3,5-12 |
Qt 5.0提供了19个模型/视图示例。示例可以在项目视图示例页面上找到。
Example name | View class used | Model used | Aspects covered |
---|---|---|---|
Address Book | QTableView | QAbstractTableModel QSortFilterProxyModel | 使用QSortFilterProxyModel从一个数据池生成不同的子集 |
Basic Sort/Filter Model | QTreeView | QStandardItemModel QSortFilterProxyModel | |
Chart | Custom view | QStandardItemModel | 设计与选择模型合作的自定义视图 |
Color Editor Factory | QTableWidget | 用一个新的自定义编辑器增强标准委托以选择颜色 | |
Combo Widget Mapper | QDataWidgetMapper to map QLineEdit, QTextEdit and QComboBox | QStandardItemModel | 演示QComboBox如何充当视图类 |
Custom Sort/Filter Model | QTreeView | QStandardItemModel QSortFilterProxyModel | 子类QSortFilterProxyModel用于高级排序和过滤 |
Dir View | QTreeView | QFileSystemModel | 演示如何将模型分配给视图的非常小的例子 |
Editable Tree Model | QTreeView | Custom tree model | 使用树的综合示例,演示了使用底层自定义模型编辑单元格和树结构 |
Fetch More | QListView | Custom list model | 动态变化模型 |
Frozen Column | QTableView | QStandardItemModel | |
Interview | Multiple | Custom item model | 多个视图 |
Pixelator | QTableView | Custom table model | 自定义委托的实现 |
Puzzle | QListView | Custom list model | 模型/视图与拖放 |
Simple DOM Model | QTreeView | Custom tree model | 自定义树模型的只读示例 |
Simple Tree Model | QTreeView | Custom tree model | 自定义树模型的只读示例 |
Simple Widget Mapper | QDataWidgetMapper to map QLineEdit, QTextEdit and QSpinBox | QStandardItemModel | 基本的QDataWidgetMapper用法 |
Spin Box Delegate | QTableView | QStandardItemModel | 使用自旋框作为单元格编辑器的自定义委托 |
Spreadsheet | QTableView | 定制的代表 | |
Star Delegate | QTableWidget | 全面的自定义委托示例。 |
还提供了模型/视图技术的参考文档。