第15.22节 PyQt(Python+Qt)入门学习:Model/View架构详解

1、简介

在PyQt和Qt中,Model/View架构是图形界面开发时用于管理数据和界面展示方式的关系。由该体系架构引入的功能分离使得开发人员可以更灵活地定制展示数据项的呈现方式,并提供标准模型接口支持普遍的数据源与预约义好的项视图(item views)一块儿使用。html

2、Model/View架构概述

2.一、引言

模型-视图-控制器(Model-View-Controller,简称MVC)是一种源于Smalltalk在构建用户界面时 普遍使用的设计模式。在《Design Patterns》一书中,Gamma等人这样描述到:“MVC由三种对象组成。模型Model是应用程序对象,视图View是其屏幕表示,控制器Controller定义用户界面对用户输入的反应方式。在MVC以前,用户界面设计倾向于将这些对象组合在一块儿。MVC将它们解耦以增长灵活性和重用性。python

若是将MVC架构中的视图和控制器对象组合在一块儿,结果就是Model/View体系结构。这仍是将数据的存储方式与展示给用户的方式分开,可是基于相同的原则提供了一个更简单的框架。这种分离使得能够在几个不一样的视图中显示相同的数据,并实现新类型的视图,而无需更改底层数据结构。web

为了容许灵活地处理用户输入,在PyQt和Qt中引入了代理Delegate的概念,代理容许自定义数据项的呈现和编辑方式。不过老猿不许备就代理的概念展开进行介绍。数据库

2.二、架构模型

2.2.1 架构模型图

完整的Model/View架构模型以下:
在这里插入图片描述编程

2.2.二、架构模型各组件功能

  • 模型Model与数据源通讯,为体系结构中的其余组件提供数据接口。与数据源通讯的方式取决于数据源的类型(如文件、数据库、消息等)以及模型的实现方式。
  • 视图View从模型Model中根据必定条件(如行号、列号等)获取模型索引,模型索引是一个指向数据项的引用。经过模型Model的模型索引,视图View能够从数据源检索数据项。
  • 在标准视图中,代理Delegate展示数据项,编辑项时,代理Delegate直接使用模型索引与模型Model通讯。

通常状况下Model/View能够分为上述三组:模型Models、视图Views和代理delegates。这些组件中的每个都是由抽象类定义的,这些抽象类提供公共接口,在某些状况下还提供特性的默认实现。抽象类应该子类化,经过子类以便提供其余组件所须要的所有功能,同时特殊状况下这也能够针对特定场景编写专门的组件。设计模式

2.2.三、架构模型各组件通讯机制

模型、视图和代理使用信号和插槽相互通讯:数据结构

  • 来自模型Model的信号通知视图View有关数据源所保存数据的更改
  • 来自视图View的信号提供了有关用户与所显示项目的交互的信息
  • 来自代理delegate的信号在数据项编辑期间用于告诉模型Model和视图View编辑器的状态

3、模型Models

全部项模型(item models )类都是从基类QAbstractItemModel 类及其子类派生的,QAbstractItemModel 类定义了一个供视图views 和代理delegates 访问数据的接口。数据自己没必要存储在模型中,它能够保存在由单独的类、文件、数据库或其余应用程序组件提供的数据结构或存储库中。架构

QAbstractItemModel 提供了一个数据接口,该接口足够灵活地处理以列表、表和树的形式展示数据的视图。下图是三种模型对应数据层级示意:框架

在这里插入图片描述

然而,当为列表和相似表的数据结构实现新模型时,QAbstractListModel和QAbstractTableModel类是更好的起点,由于它们提供了公共函数的适当默认实现。这些类中的每个均可以被子类化,以提供支持特殊类型列表或表的模型。编辑器

PyQt和Qt提供了一些现成的模型,可用于处理数据项:

  • QStringListModel用于存储QString项的简单列表。
  • QStandardItemModel管理更复杂的项树结构,每一个项均可以包含任意数据。
  • QFileSystemModel提供有关本地文件系统中的文件和目录的信息。
  • QSqlQueryModel、QSqlTableModel和QSqlRelationalTableModel用于使用Model/View对应约定协议实现数据库访问。

若是这些标准模型不知足应用的需求,能够将QAbstractItemModel 、QAbstractListModel或QAbstractTableModel子类化,以建立自定义模型。

4、视图Views

视图负责数据的展示,PyQt和Qt为不一样类型的视图提供了完整的实现类:

  • QListView展示一个列表,每一个列表就是一个项
  • QTableView以表格形式展示表模型的数据
  • QTreeView以树形方式展现分层列表模型(多层列表,每层与上层的某个节点有父子关系)中的数据

这些类中的每个都基于QAbstractItemView抽象基类,尽管这些类已实现基类的全部 抽象方法能够直接使用,但它们也能够被子类化以提供自定义视图使用。

5、代理Delegates

QAbstractItemDelegate 是Model/View架构中代理的抽象基类。代理为视图中的项提供展示和编辑功能。默认的代理实现由QStyledItemDelegate提供,PyQt和Qt中预约义好的标准视图将其用做默认代理。QStyledItemDelegate使用当前样式来绘制视图中的项。Qt建议在实现自定义代理或使用Qt样式表时使用QStyledItemDelegate做为基类。

对于代理,老猿在PyQt相关内容中不进行展开介绍。

6、使用模型和视图

6.一、两个标准模型

PyQt和Qt提供的两个标准模型是QStandardItemModel和QFileSystemModel。QStandardItemModel是一个多用途模型,可用于表示列表list、表table和树tree类型视图所需的各类不一样数据结构,模型能够保存数据项。QFileSystemModel是一个维护文件目录内容信息的模型,它自己不包含任何数据项,而只是表示本地文件系统上的文件和目录。

QFileSystemModel 提供了一个随时可用的模型来进行实验,而且能够很容易地配置为使用现有的数据。使用此模型,咱们能够演示如何设置用于现成视图的模型,并探索如何使用模型索引(model indexes)操做数据。 QListView 和 QTreeView这两个视图是最适合使用QFileSystemModel的视图。

6.二、模型索引(Model indexes)

为了确保数据的展示与访问方式保持分离,引入了模型索引的概念。经过模型能够得到的每一条数据都由模型索引表示。视图和代理使用模型索引来请求要显示的数据项。

所以,模型只须要知道如何获取数据,而模型管理的数据类型能够至关广泛。模型索引包含指向建立它们的模型对象,这能够防止在使用多个模型时出现混淆。

Qt中Model/View中的Model Index是一个类,该类用于定位Model/View中数据模型中的数据。

Model Index对应类为QModelIndex,用于在项视图( item views)、代理(delegates)和选择模型( selection models)使用来定位Model中的数据项。

模型索引引用模型中的数据项,包含一个指向建立模型索引的Model的指针,这样能够避免使用多个Model时引发混淆,模型索引包含有定位数据项在模型中的位置所需的全部信息,包括索引位置给定的行和列位置,而且可能还有父索引,这些经过使用row()、column()和parent()来获取。模型中的每一个顶级项目都用一个没有父索引的模型索引来表示——在这种状况下,parent() 将返回一个无效的模型索引,至关于一个用QModelIndex()无参数形式构造的索引。

为了获取相应数据项的模型索引,能够调用QAbstractItemModel.index() ,调用时必须指定Model的三个属性:行数,列数,父项的模型索引。特殊状况下,引用模型中的顶级项时,使用QModelIndex()做为父索引。

  • 下图中的表模型中数据A、B、C的模型索引获取方法代码以下:

在这里插入图片描述

indexA = self.model.index(0, 0, QtCore.QModelIndex())
        indexB = self.model.index(1, 1, QtCore.QModelIndex())
        indexC = self.model.index(2, 1, QtCore.QModelIndex())
  • 下图中的树模型中数据A、B、C的模型索引获取方法代码以下:
    在这里插入图片描述
indexA = self.model.index(0, 0, QtCore.QModelIndex())
        indexC = self.model.index(2, 1, QtCore.QModelIndex())
        indexB = self.model.index(1, 0, indexA)

QModelIndex对象由模型使用QAbstractItemModel.createIndex() 函数建立。可使用QModelIndex构造函数构造无效的模型索引。当引用模型中的顶级项时,无效索引一般用做父索引。

model()函数返回索引引用的Model(类型为QAbstractItemModel),child()函数用于访问给定行和列对应索引下保存的子项。sibling()函数用于在模型中遍历与索引相同级别的数据项。

注意:模型索引为数据项提供了临时参照,经过它能够用来提取或修改Model中的数据。模型索引在得到后应该当即使用,因为Model常常会从新组织内部的结构,使得模型索引失效,所以不该保存模型索引。若是须要一个对数据项的长期参照,必须建立一个永久的模型索引。这样会为不断更新的Model信息提供一个参照。临时模型索引由QModelIndex类提供,而永久模型索引则由QPersistentModelIndex类提供。

6.三、项角色(Item Roles)

在PyQt中,模型能够针对不一样的组件(或者组件的不一样部分,好比存储数据、界面展现数据、按钮的提示等)提供不一样的数据。例如,Qt.DisplayRole用于视图的文本显示。一般来讲,模型中的数据项包含一系列不一样的数据角色,数据角色定义在 Qt.ItemDataRole 枚举中,包括下列枚举值:

Qt.DisplayRole:文本表格中要渲染显示的数据,当存储的内部字典值要显示为可理解的文字含义数据时对应数据与实际存储数据会不一致
Qt.EditRole:编辑器中正在编辑的数据,老猿认为这也应该是实际存储的数据
Qt.ToolTipRole:数据项的工具提示的显示数据
Qt.WhatsThisRole:项为"What’s This?"模式显示的数据
Qt.DecorationRole:数据被渲染为图标等装饰(数据为QColor/ QIcon/ QPixmap类型)
Qt.StatusTipRole:数据显示在状态栏中(数据为QString类型)
Qt.SizeHintRole:数据项的大小提示,将会应用到视图(数据为QString类型)
Qt.CheckStateRole:数据项前面的checkbox选择状态,当数据项构建时使用了setCheckable(True)时会发生做用
Qt.TextAlignmentRole:数据项对齐方式,当设置了数据项的对齐格式时有效

几个常量的值:
Qt.DisplayRole=0
Qt.DecorationRole=1
Qt.EditRole=2
Qt.ToolTipRole=3
Qt.StatusTipRole=4
Qt.WhatsThisRole=5
Qt.TextAlignmentRole=7
Qt.CheckStateRole=10
Qt.SizeHintRole=13

下图是几种经常使用数据角色的示意图
在这里插入图片描述
经过为每个角色提供恰当的数据,模型能够告诉视图和委托如何向用户显示内容。不一样类型的视图能够选择忽略本身不须要的数据,也能够添加所须要的额外数据。

7、关于排序

在Model/View体系架构中,有两种方法能够进行排序;选择哪一种方法取决于底层模型。

  • 若是模型是可排序的,即模型类实现了QAbstractItemModel.sort()函数,如QTableView和QTreeView都提供一个API,容许以编程方式对模型数据进行排序。此外,还能够经过将QHeaderView.sortIndicatorChanged()信号链接到QTableView .sortByColumn()槽函数或QTreeView.sortByColumn()槽函数来启用交互式排序(即容许用户经过单击视图的标题对数据进行排序)。
  • 另外一种方法是,若是模型没有所需的接口,或者想使用列表视图(list View)来显示数据,则在视图中显示数据以前,使用代理模型来转换模型的结构。

8、代理模型Proxy Models

8.一、概述

在Model/View框架中,单个模型提供的数据项能够由任意数量的视图共享,而且每一个视图可能以彻底不一样的方式表示相同的信息。自定义视图和代理是为同一数据提供彻底不一样展现结果的有效方法。但应用程序一般须要为相同数据的已处理版本提供常规视图,例如为列表数据提供不一样排序的展示视图。

尽管将排序和筛选操做做为视图的内部方法来执行看起来可行,可是排序和筛选操做代价高,若是存在多个视图展现相同的数据时,每一个视图数据排序按不一样方式排序,若是每一个视图实现相似的方法,这种操做代价高昂。

另外一种方法就是在模型自己对数据进行排序,这致使每一个视图都必须显示根据最近的排序或刷选操做处理后的数据项,一样代价高。

为了解决这个问题,Model/View框架使用代理模型来管理在各个模型和视图之间交互的信息。代理模型是一些组件,从视图的角度来看,它们的行为相似于普通Model,并表明该视图访问源模型中的数据。Model/View框架使用的信号和槽机制确保不管在其自身和源模型之间放置了多少代理模型,每一个视图都会获得适当的更新。

老猿理解代理模型就是提供在其余的model和view之间排序和过滤数据的支持功能使用的的,在代理模型中能够对项进行排序和筛选,这种方法容许一个model采用和其视图功能匹配的要求从新组织,但不须要在数据和源模型上作任何处理,也不须要复制内存中的数据,能够有效提升效率。

8.二、使用代理模型

代理模型能够插入到现有模型和任意数量的视图之间。PyQt和Qt提供了一个标准的代理模型QSortFilterProxyModel,它一般是直接实例化和使用的,但也能够从其派生子类来提供自定义的筛选和排序行为。

QSortFilterProxyModel类能够按如下方式使用:

1. 定义代理模型对象

语法:proxyModel = QSortFilterProxyModel((QObject parent)

2. 设置代理模型的数据源模型

语法:代理模型.setSourceModel(数据源模型)

其中代理模型就是第一步定义的模型,数据源模型即前面第三部分介绍的Model,为真正访问数据的模型。

3. 设置视图对应模型为代理模型

语法:视图.setModel(proxyModel )

8.三、代理模型小结

从以上语法看到,代理模型自己对外是个Model,但自身的数据源也是个Model。
因为代理模型继承自QAbstractItemModel,所以它们能够链接到任何类型的视图,而且能够在视图之间共享。它们还可用于从其余代理模型得到信息,相似代理模型到数据Model之间象管道同样排列使用。

QSortFilterProxyModel类被设计为实例化并直接在应用程序中使用,也能够经过特殊派生的子类实现所需的比较操做,从而建立更专门的代理模型。

QSortFilterProxyModel的具体过滤和刷选的方法请参考类相关的方法介绍,在此不进行展开说明。

9、Model/View便利类

为了使依赖于PyQt或Qt基于项的视图和表类的应用程序受益,从标准视图类派生了许多便利类,这些便利类不建议使用于派生子类。

这些类包括QListWidget、QTreeWidget和QTableWidget。这些类比视图类更不灵活,不能与任意模型一块儿使用。PyQt或Qt建议使用Model/View方法处理项视图(item views)中的数据,除非强烈须要一组基于项的类(item-based)。

若是但愿在仍使用基于项的接口类时利用Model/View方法提供的功能,请考虑使用视图类,例如使用QStandardItemModel做为模型Model的QListView、QTableView和QTreeView视图类。

20200123日补充:

本节部分还缺一个与Model/View架构相关的类的整体介绍,在此请你们参考《PyQt学习随笔:Qt中Model/View相关的主要类及继承关系》。

10、后记

本节主要介绍了:

  • Model/View架构中模型、视图、模型索引、代理等相关的概念

  • 模型索引(Model indexes )以独立于任何底层数据结构的方式提供有关模型中数据项的位置的信息给代理和视图使用,模型索引是在其余组件(如视图和代理)的请求下由模型构造的

  • 项的构成要素包括行和列编号以及其父项的模型索引

  • 角色用于区分与项关联的表明同一数据中的要素但展示不一样用途的数据

    本文主要介绍Model/View开发架构的相关概念,相关内容主要从Qt官网文章翻译而成,结合老猿的理解作了一些补充。没有所有写新的内容是由于概念引用原文比较成体系。

本节应该是Model/View相关章节的首节来介绍,但老猿也是边学习边摸索,可不能简单将Qt官网文档简单翻译,所以才在此部分进行介绍。

另外老猿关于PyQt的付费专栏《使用PyQt开发图形界面Python应用》只须要9.9元,该部分与第十五章的内容基本对应,但一样内容在付费专栏上整体来讲更详细、案例更多。本节内容在付费专栏的《第十四章、Model/View开发:Model/View架构程序设计模式》。若是有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。

老猿Python,跟老猿学Python!