Qt的模型/視圖編程方法(model/view programming)

 

Qt的模型/視圖編程方法(model/view programming)

 

由於最近做的一個程序需要用一個視圖顯示所定義的數據,翻了下Qt的widget,有QTreeWidget,QTableWidget,QListWidget。但是這些widget內部包含了存儲這些數據的container,也就是說用戶產生的數據爲了能夠以列表或者樹目錄的形式顯示出來,必須將其拷貝一份。這就造成了數據的冗餘,對於數據不是很多的情況犧牲一點點空間也無妨,但是對於數據庫等大型數據來講,這便是無法原諒的了。

翻了翻Qt 的幫助文檔,Qt提供了一個框架完美得解決了這個問題:Model/View Programming。這種方法採用了將視圖和數據分離的策略,視圖只負責顯示,不提供數據存儲,而model中存儲了自定義的數據,這樣僅一份數據可以在多個視圖中顯示。這個思想類似於C++中容器和算法,一個存儲數據,一個使用數據,它們之間的接口就是遍歷器。

本文算是學習筆記,網上的幫助文檔加上自己實踐花了1個多星期,在此總結一下。

Qt4.7在線幫助文檔:Model/View Programming

模型/視圖編程介紹

 

Qt 4引入了一系列基於模型/視圖構架的類,用於管理數據及其呈現之間的關係。由這個構架帶來的功能的分離能夠使得開發者更加靈活地自定義數據的呈現方式。Qt也提供了一些標準的接口,能夠使大部分數據可以用已有的視圖來顯示。在幫助文檔中主要介紹了這些模塊的大概,基本概念和基本函數的用法。給出了每一個部件的用法,也提供了一些例子。
模型/視圖構架
模型-視圖-控制器(MVC)是一個基本的設計模式。這種設計模式定義了3個部件。模型即應用實體,視圖就是屏幕呈現,控制器定義了用戶接口和對用戶輸入的反饋。在MVC之前,用戶界面的設計將這些模塊糅合到了一起。MVC從功能上將3個部件分開,從而提供了更多的靈活性和複用性。
如果講視圖和控制器結合到一起,結果就是模型/視圖構架。這種構架仍然將數據的存儲和用戶的呈現分開,能夠使同一數據用不同的視圖來顯示,而不用改變內在的數據結構。爲了能夠靈活把握用戶輸入,Qt引入了代理(delegate)的概念。代理能夠在視圖中靈活定義用戶將用哪種方式輸入數值,是用LineEdit直接輸入,還是用ComboBox選擇,還是用SpinBox調節。 


模型與數據源想關聯,並且提供了接口供構架中的其他模塊訪問數據,至於如何訪問數據,決定於數據的類型和模塊是如何實現與數據的相關的。視圖從模型中獲得數據的索引(index,專有類QModelIndex),這些索引是對數據的引用。在視圖中,當數據元素需要編輯時,代理便負責提供編輯的接口,代理直接與模型通信,並使用模型提供的索引。

模型,視圖和代理通過信號(signals)和槽(slots)相互通信:來自模塊中的信號通知視圖數據源中數據的改變。來自視圖的信號提供了用戶交互和數據顯示方式的信息。當用戶編輯數據時,代理髮出信號,提示模型和視圖編輯的狀態等信息。
先來看一下最基本的三個類,Model/View框架中其他的類都是派生自這三個基本類。
QAbstractItemModel       這個類就是Model的抽象接口
QAbstractItemView        這個是視圖的抽象接口
QAbstractItemDelegate  View和model的“中間接口”,當你需要在視圖中編輯item時,這個類就派上用場了。
 

模型

模型提供了接口給視圖和代理,但有一點要清楚,它並不提供數據的存儲。數據並不存儲在模型中,而存儲在由一些數據結構中或者類所定義的另外的容器中,例如文件,數據庫,或者別的應用程序部件中。
QAbstractItemModel提供了足夠的接口供表格,樹形目錄,列表等顯示數據。但要用特定視圖顯示數據時,最好從QAbstractListModel、QAbstractTableModel中派生,它們提供了對一些常用函數更合適的默認實現。這些類通過子類化之後,能提供更加自定義的,更加特定的列表,表格視圖。(一般子類化這三個抽象類)

 

Qt提供了一些已經完成的模型類來處理數據:

l  QStringListModel:用列表來處理QString的數據。

l  QStandardItemModel管理了複雜的樹形結構。

l  QFileSystemModel提供了本地文件系統中的文件和目錄信息。

l  QSqlQueryModel,QSqlTableModel,QSqlRelationalTableModel是用來存取數據庫的。

 

視圖

已經定義好的視圖有3個:QListView,QTableView,QTreeView。從名稱可以看出來它們各自的功能,以後會一一介紹。這三個類都基於抽象類QAbstractItemView。

 

代理

代理的抽象類是QAbstractItemDelegate,基於它的標準實現是QStyledItemDelegate,這個代理被用在以上3個標準視圖中。然而還有一個代理QItemDelegate,它與QStyledItemDelegate是完全獨立的兩個代理類。因爲QStyleItemDelegate使用當前風格繪製元素,所以當你用自定義代理或者使用Qt樣式表的時候,最好是子類化這個。

 

使用模型和視圖

下面將介紹如何使用模型/視圖模式。 

兩個標準模型

Qt已經爲我們實現了2種模型,QStandardItemModel和QFileSystemModel。QStandardItemModel是一個多用途的模型,可以用來表示多種不同類型的數據,可以顯示在列表,表格,樹視圖中。這個模型存儲了數據元素。

QFileSystemModel是一個維持了內容路徑的模型,它自己不存儲任何數據元素,僅僅表示了本地文件系統中的文件和路徑。它拿來就能用,並能非常容易地設置好藥顯示的數據,用這個模型可以示範一下如何搭建一個可用使用的視圖,同時也可以看看如何操作模型中數據的索引。 

使用已經存在的模型搭建視圖

QListView和QTreeView比較適合用來顯示路徑信息。下圖中左邊用了樹狀圖,右邊是列表視圖。並且兩個視圖共享了用戶的選擇(即選擇一個視圖中的一個項目,另一個視圖中的項目會同時被選中),選擇項目牽扯到另外一些類,後面有詳細探討。


代碼很簡單:

這裏index()的用法是專門針對QFileSystemModel的,給它一個路徑,它返回一個QModelIndex值。這個類後面會討論,專門用於對模型中每個項目的索引操作。

int main(int argc, char *argv[])
{
  QApplication app(argc, argv);
  QSplitter *splitter = new QSplitter;//QSplitter用戶分割兩個widget
  QFileSystemModel *model = new QFileSystemModel;
  model->setRootPath(QDir::currentPath());
  QTreeView *tree = new QTreeView(splitter);
  tree->setModel(model); //爲視圖設置模型
  tree->setRootIndex(model->index(QDir::currentPath()));
  QListView *list = new QListView(splitter);
  list->setModel(model); 
  list->setRootIndex(model->index(QDir::currentPath()));
  splitter->setWindowTitle("Two views onto the same file system model");
  splitter->show();
  return app.exec();
}

模型類

到這裏可能你還對模型視圖框架中的基本概念還不是很清楚,這數據如何在model中存儲呢?視圖到底如何從Model中獲取數據?讓我來看看基本概念。 

首先模型爲視圖和代理提供了一個標準接口用於存取數據,它就是QAbstractItemModel類。無論數據項是用什麼數據結構存儲的,QAbstractItemModel的子類(作爲一個抽象類,你總要子類化一下才能用吧)總是用一種抽象的表結構來表示各個數據項。下面的圖表示了數據的存儲結構,可以發現數據都是以行、列的形式表示的,list和tree也可以有多個列。由此可以發現,其實ModelIndex對象裏面應該有一個行號和一個列號用於表示其中一個數據項目。


當用setModel()設置好一個視圖的model之後,模型和視圖之間的聯繫通過信號和槽聯繫,當數據改變後,不管是在Model中改變的還是在視圖中改變的,它們都能發出信號通知對方。

既然Model index與模型中數據表示息息相關,那就先講這個模型索引。

模型索引(model index)

爲什麼要引入一個index來連接視圖和模型呢?視圖直接操作模型中的數據嗎?Qt爲了使它們分工明確,視圖只管顯示,模型只管存儲,於是引入一個model index來存取數據提供給視圖。於是視圖類中只要定義一個接口函數,其參數爲一個QModelIndex就行了,我不管裏面的數據是什麼樣子的。代理類(delegate)也一樣。

這樣做的結果就是,只需要模型知道數據如何獲得就可以了,另外一點就是模型操作的數據類型可以一般化定義,就是QVarient類(可以表示很多不同的Qt標準化類型)。Model index數據結構中也有一個其指代的模型的指針,這樣使得有多個模型要顯示時不至於混亂。

QAbstractItemModel *model = index.model();//獲取index指代的模型

Model index獲得的數據信息只是臨時的,因爲model可以實時地瞭解內部數據結構,但是index作爲一個買了東西就走的顧客,可不知道你這商店裏面到底有什麼類型東西。於是Qt提供了一個回頭客——QPersistentModelIndex,據說可以更新model中的信息,這個以後再說。一般的index由類QModelIndex表示。

Index要知道數據項目的信息,有3個屬性是要先知道的,那就是數據的行數,列數,還有項目的父項目的index。對於list或者table,父節點可能不是那麼要緊,但是對於樹形結構,就必須要一個父節點才能很好地顯示數據了。一般來講,最頂層的數據元素,它的父節點一遍可以用QModelIndex來表示,即:

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());

以上是下圖table model中數據索引的獲得方法。三個參數分別是行,列和父項目索引。行號和列號都是從0開始,這很符合2維數組的下標表示方法。但是很多數據又不一定是行列形式的,數據結構多種多樣,如何才能將多種多樣的數據結構轉變成這種易於顯示的表格結構呢?在如何使用model中將會介紹。

這是index()函數格式:

QModelIndex index = model->index(row, column, parent);

 

對於tree的寫法也很簡單,只要換一個父親節點的索引就可以了:

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 0, indexA);
QModelIndex indexC = model->index(2, 1, QModelIndex());


數據元素顯示方式(Item roles)


一個模型裏面的數據可以用很多不同的表示方式顯示出來,比如Qt::DisplayRole是用於顯示字符串數據的,不管你的真實數據是什麼,它都自動變成一個字符串顯示在view中。標準的顯示方式由Qt::ItemDataRole表示。常用的還有一種Qt::DecorationRole,用於顯示顏色QColor,圖標QIcon和小圖片QPixmap。當然你可以自定義Role了,不過這個以後再講。

我們可以直接通過兩個參數獲得我們想要的數據:index和Role:

QVariant value = model->data(index, role);

 

總結這部分:

l  模型索引提供視圖和代理數據元素的位置信息,並且使之與數據依賴的數據獨立。

l  數據通過行,列及父節點3個參數定位。

l  模型索引由需要模型中數據的部件如視圖和代理構建產生。

l  當指定一個父節點且這個父節點有效,並用index()函數索引一個數據元素時,你將獲得這個父節點的自節點元素。當父節點無效時,你只會獲得最高層數據元素。

l  Role區分了同一個數據元素不同類型的數據。

 

如何使用模型索引,講在下一篇講到...

發佈了28 篇原創文章 · 獲贊 9 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章