每一个 UI 开发者都应该知道模型/视图编程,而这个指南的目的就是要带给你一个简单易懂的关于这一话题的介绍。
表格、列表和树型窗口部件是 GUI 开发中经常会用到的窗口部件。这些窗口部件有两种不同的方式来获取数据。传统的方式是窗口部件本身包含用于存储数据的内置容器。这种方式非常符合直观感受,然而,在许多复杂的应用中,这将导致数据的同步问题。第二种方式是模型/视图编程,窗口部件无需维护内部的数据容器。它们通过标准的接口获取外部数据,也因此避免了数据的重复。这在一开始可能会显得复杂,然而一旦你更仔细地观察之后,它不但很容易理解,而且它所具有的很多优点也会逐渐变得清晰明了。
在下面的过程中,我们会学习 Qt 提供的基本技术,例如:
- 标准窗口部件与模型/视图窗口部件的区别
- 窗体与模型之间的适配器
- 开发一个简单的模型/视图应用
- 预定义模型
- 进阶话题:
- 树型视图(tree views)
- 选择(selection)
- 代理(delegates)
- 用模型测试来调试
你还会学习到:对于你的新应用来说,是用模型/视图编程更容易,还是用传统的窗口部件就能够很好地工作。
这个指南包含了示例代码,让你编辑并整合到你的项目中。指南的源代码放在了 Qt 的 examples/tutorials/modelview 目录中。
更多详细的信息,请参考 reference documentation。
如果你之前完全没接触过 Qt,请先阅读 How to Learn Qt。
1. 介绍
模型/视图是一种用于分离窗口部件的数据与视图的技术。标准窗口部件并没有被设计为可以从视图中分离数据,这也是为什么 Qt4 拥有两种不同的窗口部件类型。两种窗口部件类型看起来相同,但它们与数据的交互方式不同。
标准窗口部件使用它内部的数据,数据是该窗口部件的一部分。
视图类操作外部的数据(即模型)。
1.1 标准窗口部件
我们进一步来观察标准的表格窗口部件(table widget)。表格窗口部件是一个二维数组,该数组的元素可被用户改变。通过读取和写入表格窗口部件中的数据元素,表格窗口部件能够被整合到程序流中。这个方法非常直观,而且在很多应用中都很有用,但是使用标准表格窗口部件来显示和编辑一张数据库表就有问题了。两份数据的副本需要被协调:一份在窗口部件的外部,一份在窗口部件的内部。开发者需要同步这两个版本。除此之外,数据的展示与其本身的高度耦合让编写单元测试变得更加困难。
1.2 模型/视图来解围
模型/视图的出现,提供了一种解决方案:使用一个更加灵活强大的架构。模型/视图消除了标准窗口部件中可能出现的数据一致性问题。模型/视图也令多个视图使用同一份数据变得更加容易,因为一个模型可以被传递给多个视图。最重要的区别是模型/视图窗口部件无需存储表格背后的数据。实际上,它们直接操作你的数据。由于视图类并不知道你的数据的结构,你需要提供一个包装器来使你的数据符合 QAbstractItemModel 接口。视图使用该接口来读取和写入你的数据。该类任何实现了 QAbstractItemModel 的实例,我们都称之为模型(model)。
1.3 模型/视图窗口部件概览
以下是模型/视图窗口部件的概览,以及它们对应的标准窗口部件。
窗口部件 | 标准窗口部件 | 模型/视图 视图类 |
---|---|---|
![]() |
QListWidget | QListView |
![]() |
QTableWidget | QTableView |
![]() |
QTableWidget | QTableView |
![]() |
QColumnView 以列表层次结构的方式显示一棵树 | |
![]() |
QComboBox 能够像视图类也能够像传统的窗口部件一样工作 |
1.4 在窗体和模型之间使用适配器
我们可以很方便地拥有窗体和模型之间的适配器。
我们能够直接编辑存储在表格自身中的数据,但更舒服的方式是在文本字段中编辑数据。如果窗口部件操作的数据是单个值(QLineEdit, QCheckBox ...)而非数据集,则没有直接与模型/视图相等价的方式来分离数据与视图,所以我们需要一个适配器来将窗体连接到数据源。
QDataWidgetMapper 是一个非常棒的解决方案,因为它从窗口部件映射到列表的行,建立用于数据库表的窗体也变得非常容易。
另一个适配器的示例是 QCompleter。Qt 中的 QCompleter 用于实现 Qt 窗口部件的自动补全,例如 QComboBox 和下图所示的 QLineEdit。QCompleter 使用模型来作为数据源。
2. 一个简单的模型/视图应用
如果你想要开发一个模型/视图应用,应该从哪儿开始?我们推荐从一个简单的示例开始,并一步一步地扩展它。这会让理解架构变得更加容易。对于很多开发者来说,试图在使用 IDE 之前详细地理解模型/视图架构,已经被证实存在诸多不便。而从一个带有示例数据的简单模型/视图应用开始,则简单多了。试一试!只需将下面示例中的数据替换成你自己的即可。
2.1 一个只读的表格
我们从一个使用 QTableView 来展现数据的应用开始。我们将稍后添加编辑功能。
(源文件:examples/tutorials/modelview/1_readonly/main.cpp)
// main.cpp
#include <QtGui/QApplication>
#include <QtGui/QTableView>
#include "mymodel.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTableView tableView;
MyModel myModel(0);
tableView.setModel( &myModel );
tableView.show();
return a.exec();
}
我们使用通常的 main()
函数:
这是一个有趣的部分:我们创建了 MyModel 的一个实例,并且使用 tableView.setModel(&myModel); 来传递它的一个指针给 tableView。tableView 会调用它所接收的指针的方法,从而找出两个信息:
- 应该显示多少行、多少列。
- 每个单元格应该显示什么内容。
我们需要对模型编写一些代码来对此作出反应。
我们有一个表格数据集,所以让我们从 QAbstractTableModel 开始,因为比起更加通用的 QAbstractItemModel 它更容易使用。
(源文件:examples/tutorials/modelview/1_readonly/mymodel.h)
// mymodel.h
#include <QAbstractTableModel>
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
MyModel(QObject *parent);
int rowCount(const QModelIndex &parent = QModelIndex()) const ;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
};
QAbstractTableModel 要求三种抽象方法的具体实现。
(源文件:examples/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 参数指定,role 被设定为 Qt::DisplayRole。其它 role 会在下一节中提到。在我们的示例中,生成了应该被显示的数据。在一个真正的应用中,MyModel 会有一个叫做 MyData 的成员,作为所有读取和写入的操作对象。
这个小示例解释了模型的被动特性。模型不知道它将何时被使用,也不知道需要哪个数据。它只是在每次视图发出请求时提供数据。
当模型的数据需要被修改时将会发生什么?视图如何意识到数据已经被改变并且需要重新读取?模型需要发出一个信号,表明哪些范围内的单元格已经发生了改变。这些内容将会在 2.3 节中介绍。
2.2 用role来扩展只读示例
除了控制视图应该显示哪些文本,模型还需要控制文本的外观。当我们稍微改变模型之后,得到了以下的结果:
实际上,要设置字体、背景颜色、对齐方式和复选框,只需要修改 data() 方法。下面是产生上图结果的 data() 方法。区别在于这次我们使用 int 型的 role 参数来根据它的值返回不同的信息。
(源文件:examples/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);
break;
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)
{
QBrush redBackground(Qt::red);
return redBackground;
}
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;
}
}
return QVariant();
}
通过单独调用 data() 方法,我们向模型请求每一项格式化属性。我们使用 role 参数来告知模型请求的是哪个属性。
enum Qt::ItemDataRole | 含义 | 类型 |
---|---|---|
Qt::DisplayRole | 文本 | QString |
Qt:FontRole | 字体 | QFont |
BackgroundRole | 单元格背景色刷子 | QBrush |
Qt::TextAlignmentRole | 文本对齐 | enum Qt::AlignmentFlag |
Qt::CheckStateRole | 使用 QVariant() 来抑制复选框,用 Qt::Checked 或 Qt::Unchecked 来设定复选框 | enum Qt::ItemDataRole |
要学习更多关于 Qt::ItemDataRole enum 的功能,请参考 Qt 命名空间文档。
现在我们需要确定一个单独的模型是如何影响应用性能的,因此我们就来跟踪视图调用 data() 方法的频繁程度。为了跟踪视图调用模型的频繁程度,我们在 data() 方法中放置一条调试语句,将数据打印到错误输出流。在我们的小示例中,data() 会被调用 42 次。每次你将光标移动到该区域上方,data() 就会被再次调用 —— 每个单元格7次。为什么确保你的数据在 data() 被调用时可用,并且耗时的查找操作已经被缓存是如此的重要?这就是原因。
2.3 表格单元里的时钟
我们拥有的仍然是一个只读的表格,但这次内容会每秒钟改变一次,因为我们显示的是当前时间。
(源文件:examples/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)
{
if (row == 0 && col == 0)
{
return QTime::currentTime().toString();
}
}
return QVariant();
}
要让时钟走动,还缺少点什么。我们需要每秒钟告诉视图时间已经被改变,并且需要被重新读取。我们通过一个计时器来实现这一功能。在构造函数中,我们将它的间隔设置为1秒,并且连接它的超时信号。
(源文件:examples/tutorials/modelview/3_changingmodel/mymodel.cpp)
MyModel::MyModel(QObject *parent)
:QAbstractTableModel(parent)
{
// selectedCell = 0;
timer = new QTimer(this);
timer->setInterval(1000);
connect(timer, SIGNAL(timeout()) , this, SLOT(timerHit()));
timer->start();
}
以下是对应的槽:
(源文件:examples/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);
}
我们通过发送 dataChanged() 信号,让视图将数据再次读取到左上角的单元格内。注意到我们并没有显式地连接 dataChanged() 信号到视图。当我们调用 setModel() 的时候这会自动发生。
2.4 设置行和列的表头
表头能够通过一个视图方法来隐藏:tableView->verticalHeader()->hide();
然而,表头的内容通过模型来设定,所以我们重新实现 headerData() 方法:
(源文件:examples/tutorials/modelview/4_headers/mymodel.cpp)
QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole)
{
if (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() 具有相同含义的 role 参数。
2.5 最小的编辑示例
在这个示例中我们将创建一个应用,该应用会通过重复被输入到表格单元里的值来自动生成窗口标题。为了能轻易地获取窗口标题,我们在 QMainWindow 中放置 QTableView。
模型决定编辑功能是否可用。要启用编辑功能,我们只需要修改模型即可。我们可以通过重新实现以下的虚方法来完成:setData() 和 flags()。
(源文件:examples/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);
int rowCount(const QModelIndex &parent = QModelIndex()) const ;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole);
Qt::ItemFlags flags(const QModelIndex & index) const ;
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/tutorials/modelview/5_edit/mymodel.cpp)
bool MyModel::setData(const QModelIndex & index, const QVariant & value, int role)
{
if (role == Qt::EditRole)
{
//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;
}
setData() 方法会在每次用户编辑单元格时被调用。位置参数告诉我们哪个区域被修改了,而值提供了修改的结果。由于我们的单元格只包含文本,所以 role 总是被设定为 Qt::EditRole。
(源文件:examples/tutorials/modelview/5_edit/mymodel.cpp)
Qt::ItemFlags MyModel::flags(const QModelIndex & /*index*/) const
{
return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled ;
}
单元格的不同属性可以通过 flags() 来调整。
要让一个编辑器的单元格能够被选中,只需要返回 Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled 就够了。
如果编辑一个单元格时,不仅仅修改了该单元格的数据,还修改了其它数据,则模型需要发送一个 dataChanged() 信号来让这些被修改的数据重新被读取。
3. 进阶话题
3.1 TreeView
你可以用一个树型视图将上面的示例转换成一个应用。只需要将 QTableView 替换成 QTreeView,这会产生一棵读取/写入树。我们无需修改模型。这个树不会有任何层次结构,因为模型本身没有任何层次结构。
QListView、QTableView 和 QTreeView 都使用模型抽象概念,即合并后的列表、表格和树。这使得针对同一个模型使用多种不同类型的视图类有了可能。
我们的示例模型目前看起来是这样的:
我们想要展现一棵真实的树。为了创建一个模型,我们已经在上面的示例中包装了我们的数据。这次我们使用 QStandardItemModel,一个具有层次结构数据的容器,该容器也实现了 QAbstractItemModel。要显示一棵树,QStandardItemModel 必须通过 QStandardItems 产生,因为后者能够保留子项的所有标准属性,例如文本、字体、复选框或者刷子。
(源文件:examples/tutorials/modelview/6_treeview/mainwindow.cpp)
// modelview.cpp
#include <QTreeView>
#include <QStandardItemModel>
#include <QStandardItem>
#include "mainwindow.h"
const int ROWS = 2;
const int COLUMNS = 3;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
treeView = new QTreeView(this);
setCentralWidget(treeView);
standardModel = new QStandardItemModel ;
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)
{
QList<QStandardItem *> rowItems;
rowItems << new QStandardItem(first);
rowItems << new QStandardItem(second);
rowItems << new QStandardItem(third);
return rowItems;
}
我们简单地实例化了一个 QStandardItemModel 并且向构造函数添加了几个 QStandardItems。这样一来我们能够创建一个具有层次结构的数据结构,因为一个 QStandardItem 能够容纳其它 QStandardItems。视图里的结点能够被折叠和展开。
3.2 使用选择功能(Selections)
我们想要获取一个被选中子项的内容,以便与层次结构的级别一同输出到窗口标题。
所以我们来创建几个子项:
(源文件:examples/tutorials/modelview/7_selections/mainwindow.cpp)
#include <QTreeView>
#include <QStandardItemModel>
#include <QItemSelectionModel>
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
treeView = new QTreeView(this);
setCentralWidget(treeView);
standardModel = new QStandardItemModel ;
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, SIGNAL(selectionChanged (const QItemSelection &, const QItemSelection &)),
this, SLOT(selectionChangedSlot(const QItemSelection &, const QItemSelection &)));
}
视图在一个单独的选择模型中管理选择项,该模型可通过 selectionModel() 方法来获取。我们获取选择模型是为了将一个槽连接到它的 selectionChanged() 信号。
(源文件:examples/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() != QModelIndex())
{
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个视图类,并保持选择同步的方法,因为只使用了选择模型的一个实例。使用 selectionModel() 在 3 个视图间共享一个选择模型,使用 setSelectionModel() 将结果赋给第二和第三个视图。
3.3 预定义模型
使用模型/视图的典型方法是包装特殊的数据,以使它能够被视图类使用。然而,Qt 还为常见的底层数据结构提供了预定义模型。如果这些可用的数据结构中的某一个适合于你的应用,那么一个预定义模型将会是很好的选择。
模型 | 作用 |
---|---|
QStringListModel | 存储一个字符串列表 |
QStandardItemModel | 存储任意层次结构的子项 |
QFileSystemModel QDirModel | 封装本地文件系统 |
QSqlQueryModel | 封装一个 SQL 结果集合 |
QSqlTableModel | 封装一个 SQL 表 |
QSqlRelationalTableModel | 封装一个带有外键的 SQL 表 |
QSortFilterProxyModel | 对另一个模型进行排序和(或)过滤 |
3.4 代理(Delegates)
在目前的所有示例中,数据在单元格中是以文本或复选框的形式展现和编辑的。提供这些展现和编辑服务的部件被称为代理。我们才刚开始使用代理,因为视图使用一个默认的代理。但想象一下,我们想要一个不一样的编辑器(例如一个滑块或下拉列表),或我们想要以图表的形式展现数据。我们来看看一个叫做 Star Delegate 的示例,其中星星被用来显示评级:
这个视图有一个 setItemDelegate() 方法,该方法用于取代默认代理,并安装一个自定义代理。可以通过创建一个继承自 QStyledItemDelegate 的类来编写新的代理。为了编写一个显示星星且没有输入功能的代理,我们只需要重载 2 个方法。
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 文档中其它关于代理的参考资料:
- 微调框(spin box)代理示例
- QAbstractItemDelegate 类参考
- QSqlRelationalDelegate 类参考
- QStyledItemDelegate 类参考
- QItemDelegate 类参考
4. 附加优秀资源
4.1 书籍
模型/视图编程在 Qt 文档中被大量提及,在一些优秀的书籍中也有提到。
- C++ GUI Programming with Qt 4 / Jasmin Blanchette, Mark Summerfield, Prentice Hall, 2nd edition, ISBN 0-13-235416-0.
- The Book of Qt4, The Art of Building Qt Applications / Daniel Molkentin, Open Source Press, ISBN 1-59327-147-6.
- Foundations of Qt Development / Johan Thelin, Apress, ISBN 1-59059-831-8.
- Advanced Qt Programming / Mark Summerfield, Prentice Hall, ISBN 0-321-63590-6.
以下列表提供了上面列出的前三本书中一些示例程序的概览。它们当中有一些是开发类似应用的极其优秀的模版。
示例名称 | 使用的视图类 | 使用的模型 | 涉及的方面 | |
---|---|---|---|---|
团队领导者 | QListview | QStringListModel | 第1本书,第10章,图10.6 | |
目录查看器 | QTreeView | QDirModel | 第1本书,第10章,图10.7 | |
颜色名称 | QListView | 应用于 QStringListModel 的 QSortFilterProxyModel | 第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 | QDirModel | 演示多个视图的使用 | 第2本书,第 8.2章 |
地址本 | QListView 、QTableView 、QTreeView | 基于 QAbstractTableModel 的自定义模型 | 读取/写入 | 第二本书,第8.4章 |
可排序地址本 | 介绍排序和过滤功能 | 第二本书,第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 | 应用于 QStringListModel 的 QSortFilterProxyModel | 排序演示 | 第3本书,第5章,图5-12 |
4.2 Qt 文档
Qt 4.7 带有 17 个示例和 2 个模型/视图的演示。示例可以在 Item Views Examples 页面找到。
示例名称 | 使用的视图类 | 使用的模型 | 涉及的方面 |
---|---|---|---|
地址本 | QTableView | QAbstractTableModel QSortFilterProxyModel | 使用 QSortFilterProxyModel 来通过一个数据池生成不同的子集 |
基本排序/过滤模型 | QTreeView | QStandardItemModel QSortFilterProxyModel | |
图表 | 自定义视图 | QStandardItemModel | 设计与选择模型协同工作的自定义视图 |
颜色编辑器工厂 | QTableWidget | 使用新的自定义颜色选择编辑器来增强标准代理 | |
组合框映射器 | 用于映射 QLineEdit 、QTextEdit 和 QComboBox 的 QDataWidgetMapper | QStandardItemModel | 展示一个 QComboBox 如何能够被作为视图类使用 |
自定义排序/过滤模型 | QTreeView | QStandardItemModel QSortFilterProxyModel | 用于高级排序和过滤的子类 QSortFilterProxyModel |
目录视图 | QTreeView | QDirModel | 用于演示如何将一个模型赋值给一个视图的小示例 |
可编辑树型模型 | QTreeView | 自定义树型模型 | 树的综合使用示例,演示通过一个底层自定义模型来编辑单元格和树型结构 |
获取更多 | QListView | 自定义列表模型 | 动态改变模型 |
冻结列 | QTableView | QStandardItemModel | |
Pixelator | QTableView | 自定义表格模型 | 自定义代理的实现 |
谜题 | QListView | 自定义列表模型 | 带拖放功能的模型/视图 |
简单DOM模型 | QTreeView | 自定义树型模型 | 自定义树型模型的只读示例 |
简单树型模型 | QTreeView | 自定义树型模型 | 自定义树型模型的只读示例 |
简单窗口部件映射器 | 用于映射 QLineEdit QTextEdit 和 QSpinBox 的 QDataWidgetMapper | QStandardItemModel | QDataWidgetMapper 的基本用法 |
微调框代理 | QTableView | QStandardItemModel | 使用微调框作为单元格编辑器的自定义代理 |
星星代理 | QTableWidget | 自定义代理综合示例 |
演示 与 示例 类似,只不过没有提供代码的完整指导。演示具有比示例更加丰富的特性。
- Interview 展现了三个不同的视图共享相同的模型和选择
- Spreadsheet 解释了将表格视图作为 spreadsheet 使用的方法,根据它所容纳的数据的类型来使用自定义代理渲染每一个子项。
此外,还可查阅视图/模型技术的参考文档。
英文原文:Model/View Tutorial
作者:Wray Zheng
原文:http://www.codebelief.com/article/2017/04/qt-model-view-tutorial/
看过写的最好的Qt 模型数据框架解析