在 model/view 架構中,model 提供一種標準接口,供視圖和委託訪問數據。在 Qt 中,這個接口由QAbstractItemModel
類進行定義。無論底層數據是如何存儲的,只要是QAbstractItemModel
的子類,都提供一種表格形式的層次結構。視圖利用統一的轉換來訪問模型中的數據。可是,須要提供的是,儘管模型內部是這樣組織數據的,可是並不要求也得這樣子向用戶展現數據。數組
下面是各類 model 的組織示意圖。咱們利用此圖來理解什麼叫「一種表格形式的層次結構」。架構
如上圖所示,List Model 雖然是線性的列表,也有一個 Root Item(根節點),之下才是呈線性的一個個數據,而這些數據實際能夠看做是一個只有一列的表格,可是它是有層次的,由於有一個根節點。Table Model 就比較容易理解,只是也存在一個根節點。Tree Model 主要面向層次數據,而每一層次均可以都不少列,所以也是一個帶有層次的表格。函數
爲了可以使得數據的顯示同存儲分離,咱們引入模型索引(model index)的概念。經過索引,咱們能夠訪問模型的特定元素的特定部分。視圖和委託使用索引來請求所須要的數據。由此能夠看出,只有模型本身須要知道如何得到數據,模型所管理的數據類型可使用通用的方式進行定義。索引保存有建立的它的那個模型的指針,這使得同時操做多個模型成爲可能。指針
QAbstractItemModel *model = index.model();
模型索引提供了所須要的信息的臨時索引,能夠用於經過模型取回或者修改數據。因爲模型隨時可能從新組織其內部的結構,所以模型索引極可能變成不可用的,此時,就不該該保存這些數據。若是你須要長期有效的數據片斷,必須建立持久索引。持久索引保證其引用的數據及時更新。臨時索引(也就是一般使用的索引)由QModelIndex
類提供,持久索引則是QPersistentModelIndex
類。code
爲了定位模型中的數據,咱們須要三個屬性:行號、列號以及父索引。下面咱們對其一一進行解釋。對象
咱們前面介紹過模型的基本形式:數據以二維表的形式進行存儲。此時,一個數據能夠由行號和列號進行定位。注意,咱們僅僅是使用「二維表」這個名詞,並不意味着模型內部真的是以二維數組的形式進行存儲;所謂「行號」「列號」,也僅僅是爲方便描述這種對應關係,並不真的是有行列之分。經過指定行號和列號,咱們能夠定位一個元素項,取出其信息。此時,咱們得到的是一個索引對象(回憶一下,經過索引咱們能夠獲取具體信息):索引
QModelIndex index = model->index(row, column, ...);
模型提供了一個簡單的接口,用於列表以及表格這種非層次視圖的數據獲取。不過,正如上面的代碼暗示的那樣,實際接口並非那麼簡單。咱們能夠經過文檔查看這個函數的原型:接口
QModelIndex QAbstractItemModel::index(int row, int column, const QModelIndex &parent=QModelIndex()) const
這裏,咱們僅僅使用了前兩個參數。經過下圖來理解一下:文檔
在一個簡單的表格中,每個項均可以由行號和列號肯定。所以,咱們只需提供兩個參數便可獲取到表格中的某一個數據項:字符串
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);
相似的,咱們來看看下面的示意圖:
圖中,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()
建立一個空索引使用時,咱們得到的就是模型中最頂級項;