说明:这篇博客基本都是翻译于Qt官方的Model/View Tutorial教程,无法理解的地方建议转到原文,同时,由于译者水平有限,如有差错欢迎指出。

原文:http://qt-project.org/doc/qt-5/modelview.html

转载注明出处:http://www.cnblogs.com/bestheart/p/3707584.html

须知符号:(:…译者自己的理解…*)


每一个UI开发者都应该知道Model/View编程,这个教程的目的就是简单易懂的介绍这个主题。

表格(table),列表(list) 和树(tree)组件是在GUIs中频繁使用的组件。这些组件有两种不同的方法能访问它们的数据。传统方法涉及的组件包含一些存储数据的内部容器,这种方法非常直观,但是,在许多较大的应用程序中,这种方法将会引发数据同步性问题。 第二种方法就是我们要说的model/view编程了,在这种方法中,组件不需要维护内部的数据容器,组件可以通过一个标准化的接口访问外部数据,从而避免数据复制(:因为第一种方法为了解决数据同步问题不得不频繁的复制数据*)。后一种方法初看起来挺复杂的并且不容易掌握,但是一旦你认真理解了,model/view编程将后惠无穷。

以下,我们将会学习到一些基本的Qt技术,例如:

1.标准组件和model/view组件的不同。

2.窗体和模型间的适配器。

3.开发一个简单的model/view程序。

4.中间主题,例如:

a.Tree 视图

b.选择

c.委托

d.模型测试调试

你也会学习到,能否用model/view更简单的编写你的应用程序,或者,传统的组件是否一样好。

这篇教程包含了一些例子和源代码,你可以把它们集成到自己的工程当中,例子的源代码在:

examples/widgets/tutorials/modelview

你也可以访问reference documentation了解更详细地信息。


1. 简介

Model/View是一种(在处理数据集的组件)从视图中分离数据的技术。标准的组件不从视图中分离数据,这就是为什么Qt 4(:Qt 5*)有两种不同类型的组件。这两种组件看起来一样,但是它们的数据交互方式不同。

1.1 标准组件

让我们来细看一下标准的table 组件吧。Table 组件是一个二维的数组,它的数据元素可以被用户改变。Table 组件通过读写自己的数据元素将其集成到程序流中。这种方法非常的直观,同时在许多应用程序中也是非常的有用,但是用标准的table组件展示和编辑一个数据库表格将会遇到麻烦。数据的两份拷贝不得不协调:一份在组件外,一份在组件内部。开发者必须自己为两个版本的数据同步负责。除此之外,紧密相连的两份数据将使单元测试变得更加艰难。

1.2 Model/View 的营救

Model/view 使用了一种更加通用的体系结构来解决这个问题。Model/view 消除了发生在标准组件间的数据一致性问题。Model/view 也使几个视图拥有一份数据变得更加容易,因为一个模型能够被嫁接给许多视图。最重要的不同是model/view 组件在表格单元中不存储数据,实际上,组件直接操作你的数据。因为视图类不知道你的数据结构,所以需要提供包装从而让数据遵守QAbstractItemModel接口,视图通过这个接口读写数据。任何一个实现QAbstractItemModel类的实例都称为模型。

1.3 概观Model/View 组件

下面是model/view 组件和它们相应的标准组件的概况。

1.4 窗体和模型间使用适配器

下面将简述窗体和模型间的适配器是怎样派上用场的。

我们可以在表格当中直接编辑存储在表格中的数据,但是在文本域中编辑数据将会更加符合我们的习惯。然而model/view 没有直接的相应部分能够为操作一个值而不是数据集的组件分离数据和视图(:也就是说,我们上面所述的MV是通过接口来分离数据集和视图的,但是这里我们要分离的是视图和单个数据,这就要用到适配器了*),所以我们需要一个适配器来把窗体连接到数据源。

QDataWidgetMapper 是一个主要的解决方法,它从窗体部件映射到表格行,同时,使为数据库表格创建窗体变得更加容易。

另一个适配器的例子是QCompleter。Qt 利用QCompleter在Qt部件中提供自动匹配,例如部件QComboBox和QLineEdit,如下所示。QCompleter使用模型作为它的数据源


2. 一个简单的Model/View 程序

如果想开发一个model/view 程序,从哪开始呢?建议从一个简单的例子开始,然后一步步扩展它。这样会更加容易理解M/V体系结构。对许多开发者来说,在引入IDE之前尝试理解model/view 体系通常是不适合的。从一个简单的且有样本数据的model/view 应用程序开始将会更加容易。开始吧!小伙伴们,只需要简单的用你自己的数据替换下面的例子数据。

下面将有7个简单的相互独立的应用程序,它们展示了model/view 编程的不同方面,源代码在Qt安装目录examples/widgets/tutorials/modelview

2.1 一个只读Table

我们从一个使用QTableView来展示数据的应用程序开始。我们在后面将会增加编辑功能。(文件源代码: examples/widgets/tutorials/modelview/1_readonly/main.cpp)

// main.cpp
#include <QtWidgets/QApplication>
#include <QtWidgets/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将会调用已经接受了指针的函数setModel来发现两件事:

  1. 应该显示多少行和多少列?
  2. 每个单元应该打印什么内容?

模型需要一些代码来响应上述内容。

因为我们有一个表格数据集,所以更应该从QAbstractTableModel开始,因为它将比更加普遍的QAbstractItemModel模型更容易使用

(file source: examples/widgets/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/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指定行和列的信息,同时参数role被设定为Qt::DisplayRole,其他的roles由下一部分来处理。在我们的例子当中,显示的数据使我们产生的,但在现实的程序中,MyModel 应该有一个成员MyData,它为读写操作服务。

这个例子展现了一个被动的模型,不知道什么时候这个模型应该使用,也不知道需要什么数据,它仅仅在视图需要的时候提供数据。

当模型的数据需要改变的时候会是什么情况?当数据改变需要重新读取时,视图是怎么实现的?模型需要发送一个信号表明哪些表格单元改变了,这些将会在2.3部分说明。

2.2 用roles扩展只读模型

除了控制显示什么文本外,模型也能控制文本的外观。我们只需要简单改变一下模型,就能得到下面的结果。

实际上,除了改变data()函数(设置字体,背景颜色,对齐方式,和复选框)外,我们什么也不用做。下面就是修改后的data()函数,它可以产生上面的结果。与2.1的不同之处在于,此时我们使用了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 calledqDebug() << 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参数可以通知模型哪一个属性被请求。

参考Qt 域名空间文档可以学习更多关于Qt::ItemDataRole枚举的功能。

现在我们需要知道怎样使用分离的模型来影响程序的外观,所以跟踪data()函数的调用频率非常重要,为了跟踪视图多长时间调用一次模型,我们可以在data()函数中放置一条debug语句,该语句可以记录错误输出流。例子中,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){if (row == 0 && col == 0){return QTime::currentTime().toString();}}return QVariant();
}

貌似我们还没有让时钟滴答起来,是的,每秒都需要通知视图时间已经改变了,从而需要重新读取显示。这可以用一个定时器来实现,在构造函数里,我们设置定时器间隔为一秒,同时连接它的timeout信号。

(源文件: examples/widgets/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();
}

下面是相应的槽函数。

void MyModel::timerHit()
{//we identify the top left cellQModelIndex 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()

(file source: examples/widgets/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()也有一个参数role,这个参数的意义和MyModel::data()中的是一样的。

2.5 最小编辑实例

在这个例子中,我们将要创建一个应用程序,它可以利用输入到表格单元值来自动填充窗口标题。为了能够容易访问窗口标题,可以把QTableView 放在QMainWindow里。

模型决定了是否有能力编辑,我们必须修改模型以使编辑可用,这个可以同时重新实现虚函数setData() 和 flags()做到。

(file source: 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);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 &);
};

我们使用二维数组QStringm_gridData 来存储数据,这样m_gridData就成为MyModel的核心了。MyModel其余的函数就像一个包装,它们使 m_gridData 适应QAbstractItemModel的接口。顺便介绍下editCompleted() 信号,它使传递修改的文本到窗口标题成为可能。

(file source: examples/widgets/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_gridDatam_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()函数,参数index告诉用户编辑哪一个区域,value 提供了编辑程序的结果。Role将总是被设置成Qt::EditRole,因为我们的单元格仅仅包含文本。如果需要显示复选框同时用户权限允许选择复选框,就可以把role设置成Qt::CheckStateRole来调用。

(file source: 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()信号以使被改变的数据可以重读。

Model/View 教程相关推荐

  1. Qt Model/View教程

    修正版已转移到 Qt中文文档 目录 修正版已转移到 [Qt中文文档](https://www.qtdoc.cn/Src/M/Model_View_Tutorial/Model_View_Tutoria ...

  2. QT Model/View 编程:MVC模型视图编程:实例实现(二)

    目录 样例001:现有模型中使用视图Using views with an existing model 样例002:使用模型索引 样例003:使用模型 样例004:使用模型的多个视图 样例005:委 ...

  3. 这可能是第二好的自定义 View 教程之属性动画

    上期文章镇楼: 这可能是第二好的自定义 View 教程之绘制 凯哥的文章确实写的细而好呀,这不,活生生把 面试系列 先放一放,继续讲解我们的动画. 为啥是第二好? 一看就是没看 前面的文章 的.这里就 ...

  4. Qt Model/View 学习笔记 (四)

     创建新的Models 介绍 model/view组件之间功能的分离,允许创建model利用现成的views.这也可以使用标准的功能 图形用户接口组件像QListView,QTableView和Q ...

  5. Qt学习笔记-----Model/View架构之自定义Model

    Model/View Framework中提供了模型model的抽象基类QAbstractItemModel, 如果需要自定义模型就需要继承这个类并且实现一些必要的函数. 此外,Qt中又提供了QAbs ...

  6. Qt学习笔记-----Model/View架构

    为了实现数据的存储和表现分离,Qt提供了Model/View架构,包括三个部分,分别是模型(Model),视图(View),委托(delegate). Model用于访问底层数据,也就是说为其他组件访 ...

  7. QT Basic 014 Model/View programming (模型、视图编程)

    前言:本文不是纯文本翻译,加入了对概念的理解,纯文本翻译,请看文后的一个链接. Model/View Programming Introduction to Model/View Programmin ...

  8. php orm教程,Laravel ORM 数据model操作教程

    随机查询 $data=Move::where('release',1) ->where('is_hot',1) ->where('is_status',1) ->orderBy(\D ...

  9. (一) Qt Model/View 的简单说明

    目录: (一) Qt Model/View 的简单说明 .预定义模型 (二)使用预定义模型 QstringListModel例子 (三)使用预定义模型QDirModel的例子 (四)Qt实现自定义模型 ...

最新文章

  1. 如何让自己时刻冷静的方法_4个方法,教你如何真正爱自己
  2. java和python哪个好就业2020-JAVA和Python哪个好就业?
  3. iOS - UIButton 开发总结
  4. MySQL带BETWEEN AND关键字的查询
  5. android imageview 图片切换动画,在Android中以动画方式将ImageView移动到不同的位置...
  6. SQL数据层面操作(DML)
  7. sql注入python_Python--sql注入
  8. 洛谷P1141 01迷宫【bfs】
  9. SSL P2712 跳格子
  10. 上海落户条件—海归落户上海
  11. 计算机术语pc是什么意思,pc是什么意思
  12. python按某列拆分excel表格_利用Python+Pandas实现从一个excel表中提取列形成新表
  13. 同步光网络(SONET,Synchronous Optical Networking)简介
  14. Apache运维中常用功能配置笔记梳理
  15. Java并发编程工具类:CountDownLatch、CyclicBarrier、Semaphore
  16. 如何利用计算机班级成绩分析,北京自考计算机应用基础课成绩分析报告
  17. 图说大型网站的技术架构
  18. 阿里云服务器自动备份
  19. 双曲线方程y=1/x的对称轴变为直角坐标系的坐标轴是什么样的方程?
  20. 关于抖音网红推广,你想知道的50个问题都在这里!

热门文章

  1. http学习笔记(三):报文
  2. spring 框架概述
  3. C/C++变量存储区域
  4. asp.net入门详细介绍
  5. BizTalk学习笔记系列之二:实例说明如何使用BizTalk
  6. python有什么功能-Python 3.9有什么新功能?
  7. python程序设计报告-20192416 实验四《Python程序设计》综合实践报告
  8. python画动图-Python绘制动态水球图过程详解
  9. python基础知识面试题-python的一些基本概念知识和面试题
  10. python画笑脸-python 利用turtle库绘制笑脸和哭脸的例子