#(45):模型
在前面兩章的基礎之上,我們將開始介紹 model 的通用概念。
在 model/view 架構中,model 提供一種標準接口,供視圖和委托訪問數據。在 Qt 中,這個接口由`QAbstractItemModel`類進行定義。不管底層數據是如何存儲的,只要是`QAbstractItemModel`的子類,都提供一種表格形式的層次結構。視圖利用統一的轉換來訪問模型中的數據。但是,需要提供的是,盡管模型內部是這樣組織數據的,但是并不要求也得這樣子向用戶展示數據。
下面是各種 model 的組織示意圖。我們利用此圖來理解什么叫“一種表格形式的層次結構”。
[](http://files.devbean.net/images/2013/02/model-intro.png)
如上圖所示,List Model 雖然是線性的列表,也有一個 Root Item(根節點),之下才是呈線性的一個個數據,而這些數據實際可以看作是一個只有一列的表格,但是它是有層次的,因為有一個根節點。Table Model 就比較容易理解,只是也存在一個根節點。Tree Model 主要面向層次數據,而每一層次都可以都很多列,因此也是一個帶有層次的表格。
為了能夠使得數據的顯示同存儲分離,我們引入模型索引(model index)的概念。通過索引,我們可以訪問模型的特定元素的特定部分。視圖和委托使用索引來請求所需要的數據。由此可以看出,只有模型自己需要知道如何獲得數據,模型所管理的數據類型可以使用通用的方式進行定義。索引保存有創建的它的那個模型的指針,這使得同時操作多個模型成為可能。
~~~
QAbstractItemModel *model = index.model();
~~~
模型索引提供了所需要的信息的**臨時索引**,可以用于通過模型取回或者修改數據。由于模型隨時可能重新組織其內部的結構,因此模型索引很可能變成不可用的,此時,就不應該保存這些數據。如果你需要長期有效的數據片段,必須創建**持久索引**。持久索引保證其引用的數據及時更新。臨時索引(也就是通常使用的索引)由`QModelIndex`類提供,持久索引則是`QPersistentModelIndex`類。
為了定位模型中的數據,我們需要三個屬性:行號、列號以及父索引。下面我們對其一一進行解釋。
我們前面介紹過模型的基本形式:數據以二維表的形式進行存儲。此時,一個數據可以由行號和列號進行定位。注意,我們僅僅是使用“二維表”這個名詞,并不意味著模型內部真的是以二維數組的形式進行存儲;所謂“行號”“列號”,也僅僅是為方便描述這種對應關系,并不真的是有行列之分。通過指定行號和列號,我們可以定位一個元素項,取出其信息。此時,我們獲得的是一個索引對象(回憶一下,通過索引我們可以獲取具體信息):
~~~
QModelIndex index = model->index(row, column, ...);
~~~
模型提供了一個簡單的接口,用于列表以及表格這種非層次視圖的數據獲取。不過,正如上面的代碼暗示的那樣,實際接口并不是那么簡單。我們可以通過文檔查看這個函數的原型:
~~~
QModelIndex QAbstractItemModel::index(int row,
int column,
const QModelIndex &parent=QModelIndex()) const
~~~
這里,我們僅僅使用了前兩個參數。通過下圖來理解一下:
[](http://files.devbean.net/images/2013/02/table-model-index-intro.png)
在一個簡單的表格中,每一個項都可以由行號和列號確定。因此,我們只需提供兩個參數即可獲取到表格中的某一個數據項:
~~~
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
~~~
函數的最后一個參數始終是 QModelIndex(),接下來我們就要討論這個參數的含義。
在類似表格的視圖中,比如列表和表格,行號和列號足以定位一個數據項。但是,對于樹型結構,僅有兩個參數就不足夠了。這是因為樹型結構是一個層次結構,而層次結構中每一個節點都有可能是另外一個表格。所以,每一個項需要指明其父節點。前面說過,在模型外部只能用過索引訪問內部數據,因此,`index()`函數還需要一個 parent 參數:
~~~
QModelIndex index = model->index(row, column, parent);
~~~
類似的,我們來看看下面的示意圖:
[](http://files.devbean.net/images/2013/02/tree-model-index-intro.png)
圖中,A 和 C 都是模型中的頂級項:
~~~
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
~~~
A 還有自己的子項。那么,我們就應該使用下面的代碼獲取 B 的索引:
~~~
QModelIndex indexB = model->index(1, 0, indexA);
~~~
由此我們看到,如果只有行號和列號兩個參數,B 的行號是 1,列號是 0,這同與 A 同級的行號是 1,列號是 0 的項相同,所以我們通過 parent 屬性區別開來。
以上我們討論了有關索引的定位。現在我們來看看模型的另外一個部分:數據角色。模型可以針對不同的組件(或者組件的不同部分,比如按鈕的提示以及顯示的文本等)提供不同的數據。例如,`Qt::DisplayRole`用于視圖的文本顯示。通常來說,數據項包含一系列不同的數據角色,這些角色定義在`Qt::ItemDataRole`枚舉中。
我們可以通過指定索引以及角色來獲得模型所提供的數據:
~~~
QVariant value = model->data(index, role);
~~~
通過為每一個角色提供恰當的數據,模型可以告訴視圖和委托如何向用戶顯示內容。不同類型的視圖可以選擇忽略自己不需要的數據。當然,我們也可以添加我們所需要的額外數據。
總結一下:
* 模型使用索引來提供給視圖和委托有關數據項的位置的信息,這樣做的好處是,模型之外的對象無需知道底層的數據存儲方式;
* 數據項通過行號、列號以及父項三個坐標進行定位;
* 模型索引由模型在其它組件(視圖和委托)請求時才會被創建;
* 如果使用`index()`函數請求獲得一個父項的可用索引,該索引會指向模型中這個父項下面的數據項。這個索引指向該項的一個子項;如果使用`index()`函數請求獲得一個父項的不可用索引,該索引指向模型的最頂級項;
* 角色用于區分數據項的不同類型的數據。
下面回到前面我們曾經見過的模型`QFileSystemModel`,看看如何從模型獲取數據。
~~~
QFileSystemModel *model = new QFileSystemModel;
QModelIndex parentIndex = model->index(QDir::currentPath());
int numRows = model->rowCount(parentIndex);
~~~
在這個例子中,我們創建了`QFileSystemModel`的實例,使用`QFileSystemModel`重載的`index()`獲取索引,然后使用`rowCount()`函數計算當前目錄下有多少數據項(也就是行數)。前面一章中迷迷糊糊的代碼,現在已經相當清楚了。
為簡單起見,下面我們只關心模型第一列。我們遍歷所有數據,取得第一列索引:
~~~
for (int row = 0; row < numRows; ++row) {
QModelIndex index = model->index(row, 0, parentIndex);
~~~
我們使用`index()`函數,第一個參數是每一行行號,第二個參數是 0,也就是第一列,第三個參數是 parentIndex,也就是當前目錄作為父項。我們可以使用模型的`data()`函數獲取每一項的數據。注意,該函數返回值是`QVariant`,實際是一個字符串,因此我們直接轉換成`QString`:
~~~
QString text = model->data(index, Qt::DisplayRole).toString();
// 使用 text 數據
}
~~~
上面的代碼片段顯示了從模型獲取數據的一些有用的函數:
* 模型的數目信息可以通過`rowCount()`和`columnCount()`獲得。這些函數需要制定父項;
* 索引用于訪問模型中的數據。我們需要利用行號、列號以及父項三個參數來獲得該索引;
* 當我們使用`QModelIndex()`創建一個空索引使用時,我們獲得的就是模型中最頂級項;
* 數據項包含了不同角色的數據。為獲取特定角色的數據,必須指定這個角色。
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)