一直想学习Qt Model/View,最终还是看的官方教程,现在将官方教程重新在梳理下。
每个UI开发人员都应该了解Model/View编程!可见Model/View在UI编程中的重要性!
那它为什么这么重要呢?
Table,、List和Tree widgets是GUI中经常使用的组件。 这些小部件可以通过两种不同的方式访问其数据。 传统方式部件使用内部容器进行存储数据。,这种方法非常直观,但是,在许多特别的应用程序中,它会导致数据同步问题。 第二种方法是模型/视图编程,其中小部件不维护内部数据容器。 他们通过标准化接口访问外部数据,因此避免了数据重复。 乍一看,这似乎很复杂,但是一旦仔细研究,不仅容易掌握,而且模型/视图编程的许多好处也变得更加清晰。
整个教程的目录如下:
标准部件和模型/视图部件之间的区别
表单和模型之间的适配器
开发一个简单的模型/视图应用程序
预定义模型
中级主题:
Tree views
Selection
Delegates
Debugging with model test
一、 概述
模型/视图是一种用于将数据与处理数据集的小部件中的视图分离的技术。 标准窗口小部件并非旨在将数据与视图分离,这就是为什么Qt具有两种不同类型的窗口小部件的原因。 两种类型的小部件外观相同,但是它们与数据的交互方式不同。
1. 标准部件
Table Widget是用户可以更改的数据元素的2D部件。 可以通过读写表小部件提供的数据元素将表小部件集成到程序中。 此方法非常直观,在许多应用程序中很有用,但是使用标准表窗口部件显示和编辑数据库表可能会出现问题。 数据的两个副本必须协调一致:一个在小部件外部;另一个在小部件内部。 开发人员必须负责同步两个数据副本。 除此之外,数据的紧密耦合使编写单元测试更加困难。
2. Model/View
Model/View使用了更加灵活的体系结构来提供解决方案。Model/View消除了标准小部件可能发生的数据一致性问题, 而且Model/View还可以让同一数据源在多个视图上进行显示变得更加方便;因为一个Model可以传递给许多Views。 最重要的区别是Model/View部件不在表单内部存储数据。 实际上,Model/View直接对您的数据进行操作。 由于视图类不知道数据的结构,因此需要提供包装器以使数据符合QAbstractItemModel接口【译者注:这就是为什么要setMode】。 View使用该接口进行读取和写入数据,实现QAbstractItemModel的类的任何实例都称为模型【译者注:什么是Model】。 一旦View接收到指向模型的指针,它将读取并显示其内容并成为其编辑器【译者注:setModel后,View自动读取数据并显示】。
二、 一个简单的Model/View应用程序
如果要开发Model/View应用程序,应该从哪里开始? 我们建议从一个简单的示例开始【译者注:我表示非常赞同!】,并逐步扩展它,这使得了解架构变得容易得多。 事实证明,在调用集成好的接口前尝试详细了解Model/View体系结构对于许多开发人员来说并不方便。 从具有演示数据的简单Model/View应用程序开始要容易得多。 试试看! 只需将以下示例中的数据替换为您自己的数据即可。
以下是7个非常简单和独立的应用程序,它们展示了模型/视图编程的不同方面。 可以在examples/widgets/tutorials/modelview目录中找到源代码。
1. 只读Table
我们从使用QTableView来显示数据的应用程序开始。之后我们将添加编辑功能。
只读table,效果如下:
我们创建MyModel的实例并使用tableView.setModel(&myModel), 将其指针传递给tableView ,tableView将调用它收到的指针获得以下信息:
应显示多少行和多少列
每个单元格应显示什么内容
Model需要一些代码来对此做出响应。我们有一个表数据集,因此让我们从QAbstractTableModel开始,因为它比更通用的QAbstractItemModel更加易于使用。【译者注:以后会更加了解这两个类的】
mymodel.h 代码:
#include <QAbstractTableModel>
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
MyModel(QObject *parent);
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE ;
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
};
QAbstractTableModel需要实现三种抽象方法。
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节中演示。
总结:
之前由于项目需要,使用过Qt的文件系统模型,当时直接用的现成的程序,那会儿就很不明白为什么一定要setModel,设置完后又会自己显示。教程看到这之后,终于明白了。所以我比较喜欢知道为什么这么做、这么做应该会有一个什么样的结果。