【Qt筆記】模型

在 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()建立一個空索引使用時,咱們得到的就是模型中最頂級項;
  • 數據項包含了不一樣角色的數據。爲獲取特定角色的數據,必須指定這個角色。
相關文章
相關標籤/搜索