第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!

相關文章
相關標籤/搜索