QT開發(三十七)——Model/View官方文檔

QT開發(三十七)——Model/View官方文檔

    本文翻譯自QT官方文檔QT 4.8 Model/View Programming

一、Model/View框架簡介

    Qt4推出了一組新的項視圖類,使用Model/View框架來管理數據與表示層的關係。Model/View框架帶來的功能上的分離給了開發人員更大的彈性來定製數據項的表示,並且提供一個標準的model接口,使得更多的數據源可以被項視圖類使用。本文簡要介紹了Model/View架構,對涉及的概念做了簡單的概述,闡述了項視圖系統。架構中的每一個組件都將一一作出解釋,同時將用實例對如何使用這些類進行說明。

1、Model/View框架簡介

    Model-View-Controller(MVC), 是從Smalltalk發展而來的一種設計模式,常被用於構建用戶界面。在設計模式中對MVC模式的描述如下:MVC由3種對象組成:模型是應用程序對象,視圖是它的屏幕顯示方式,控制器定義用戶接口對用戶輸入反應的方式。在MVC設計模式之前,用戶界面設計傾向於三者揉合在一起,MVC對它們進行了解耦,提高了靈活性與重用性。

    MVC 由三種對象組成Model負責維護數據(如管理數據庫),View負責顯示與用戶交互(如各種界面),Controller將控制業務邏輯。如果把View與Controller結合在一起,結果就是Model/View框架。Model/View框架依然是把數據存儲與數據表示進行了分離,與MVC都基於同樣的思想,但更簡單。數據存儲與數據顯示的分離使得在幾個不同的View上顯示同一個數據成爲可能,也可以重新實現新的View,而不必改變底層的數據結構。爲了更靈活的對用戶輸入進行處理,引入了Delegate,使得數據項的傳遞與編輯可以進行定製。

wKiom1hCXxyieU1IAABTNuA6La8117.png

    Model負責與數據源通訊,並提供接口給結構中的別的組件使用。通訊的實質依賴於數據源的類型與Model實現的方式。

    View從Model獲取模型索引,模型索引是數據項的引用。通過把模型索引提供給Model,View可以從數據源中獲取數據。

    在標準的Views中,Delegate渲染數據項,當某個數據項被編輯時,Delegate通過模型索引與Model直接進行交互。Model/View相關類可以被分成上面所提到的三組:Models,Views,Delegates。這些組件通過抽象類來定義,提供了共同的接口,在某些情況下,還提供了默認的實現。抽象類意味着需要子類化,以便爲其他組件提供完整的功能,同時也可以用來實現定製的組件。

    Models、ViewsDelegates之間通過信號-槽機制來進行通訊:

    從Model發出的信號通知View關於數據源中的數據發生的改變。
    從View發出的信號提供了有關被顯示的數據項與用戶交互的信息。
    從Delegate發射的信號被用於在編輯時通知Model和View關於當前編輯器的狀態信息。

    Model/View框架中,所有模型類具有共同的抽象基類QAbstractItemModel,所有視圖類具有共同的抽象基類QAbstractItemView,所有委託類具有共同的抽象基類QabstractItemDelegate。

2、Models

    所有的Models都基於QAbstractItemModel類,QAbstractItemModel類定義了用於Views和Delegates訪問數據的接口。數據本身不必存儲在Model,可存儲在一個數據結構或另外的類、文件、數據庫或別的程序組件中。
    QAbstractItemModel提供給數據一個接口,非常靈活,基本滿足Views的需要,無論數據用以下任何樣的形式表現,如tables,lists,trees。然而,當重新實現一個Model時,如果Model基於table或list形式的數據結構,最好從QAbstractListModelQAbstractTableModel開始做起,因爲它們提供了適當的常規功能的缺省實現。這些類可以被子類化以支持特殊的定製需求。子類化model的過程在Create New Model部分討論
    QT提供了一些現成的Models用於處理數據項:
    QStringListModel:用於存儲簡單的QString列表。
    QStandardItemModel :管理複雜的樹型結構數據項,每項都可以包含任意數據。
    QDirModel :提供本地文件系統中的文件與目錄信息。
    QSqlQueryModelQSqlTableModelQSqlRelationTableModel:用來訪問數據庫。
當標準Model不能滿足需要時,可以子類化QAbstractItemModel,

QAbstractListModel或是QAbstractTableModel來定製。

3Views

    不同的View都完整實現了各自的功能:QListView把數據顯示爲一個列表,QTableView把Model中的數據以表格的形式表現,QTreeView用具有層次結構的列表來顯示Model中的數據。QListViewQTableViewQTreeView都基於QAbstractItemView抽象基類,都完整的進行了實現,但都可以用於子類化以便滿足自定義視圖的需求。

4Delegates

    QAbstractItemDelegate是Model/View架構中的用於Delegate的抽象基類。

    從Qt4.4開始,默認委託實現由 QStyledItemDelegate提供,被作爲Qt標準視圖的默認委託來使用,但是,QStyledItemDelegate 和 QItemDelegate是爲視圖項提供繪製和編輯器的兩個獨立的委託。他們之間的不同在於,QStyledItemDelegate使用當前的樣式來繪製它的項。因此在實現自定義委託或使用Qt樣式表時,我們建議使用QStyledItemDelegate作爲基類。

5、排序

    在model/view架構中,有兩種方法可以實現排序,選擇哪種方法依賴於底層Model。如果model是可排序的,即模型重新實現了QAbstractItemModel::sort()函數,QTableView與QTreeView都提供了API,允許以編程的方式對Model數據進行排序。此外,可以通過把QHeaderView::sortIndicatorChanged()信號與 QTableView::sortByColumn()槽或QTreeView::sortByColumn()槽的分別進行連接,也可以進行互動式的排序(比如,允許用戶單擊表頭來對數據進行排序)。

    如果模型沒有所要求的接口,或想用列表視圖Listview來顯示數據,另一個方法是就在視圖顯示數據之前使用代理模型(PROXY MODEL)來轉換模型的結構。

6、便利類

    爲了使應用程序可以使用QT基於項的視圖和表格類,從標準的視圖類中衍生出了一些項視圖的便利類。他們的目的不是用於子類化的,他們的存在只是爲了給QT3中相應的類提供一個類似的接口。這些類包括QListWidgetQTreeWidget和QTableWidget,他們分別提供了QT3中的QListBoxQListView、和QTable相似的行爲。

    這些類沒有視圖類那麼靈活,也不能用於任意模型。除非你強烈需要一套基於項的便利類,否則我們推薦你在處理項視圖數據時使用模型/視圖的方法。
    如果希望利用模型/視圖方法所提供的特性,同時又想使用基於項的接口,那就考慮把QStandardItemModel類與視圖類如QListViewQTableView和 QTreeView等搭配使用。

7、Models Views的使用

    QT提供了兩個標準的Models:QStandardItemModel和QFileSystemModel    QStandardItemModel是一個多用途的Model,可用於表示list,table,treeViews所需要的各種不同的數據結構。QStandardItemModel本身持有數據。QFileSystemModel維護目錄內容的相關信息,本身不持有數據,只是對本地文件系統中的文件與目錄的簡單顯示。QFileSystemModel是一個現成的Model,很容易進行配置以用於現存的數據。使用QFileSystemModel可以很好地展示如何給一個現成的View設定Model,研究如何用Model indexes來操縱數據。

    QListView與QTreeView很適合與QFileSystemModel搭配使用。下面的例子在樹形視圖與列表視圖顯示了相同的信息。兩個視圖共享用戶選擇,用戶選中的項在兩個視圖中都會被高亮。

    先創建一個QFileSystemModel以供使用,再創建多個Views去顯示目錄的內容。Model的創建與使用都在main()函數中完成:

wKioL1hCX0LTzNBsAAC5QyJjGRQ977.png

#include <QtGui/QApplication>
#include <QSplitter>
#include <QFileSystemModel>
#include <QTreeView>
#include <QListView>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QSplitter *splitter = new QSplitter;
    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 a.exec();
}

    設置View顯示Model中的數據,需要調用setModel(),並把Model參數傳遞
 setRootIndex()設置Views顯示哪個目錄的信息,需要提供一個model index參數,index()函數把一個目錄做爲參數,得到需要的model index

二、Model

1Model簡介

Model/View構架中,Model爲View和Delegates使用數據提供了標準接口。

    Model裏面並不真正存儲數據(數據少的話也可以直接存儲在Model裏),只是負責從諸如磁盤文件、數據庫、網絡通訊等獲得源數據,並提供給View,View對數據進行修改,然後再通過Model更新源數據。在QT中,標準接口通過QAbstractItemModel類被定義。QT內置了多種標準模型:

    QStringListModel:存儲簡單的字符串列表。

    QStandardItemModel:可以用於樹結構的存儲,提供了層次數據。

    QFileSystemModel:本地系統的文件和目錄信息。

    QSqlQueryModel、QSqlTableModel和QSqlRelationalTableModel:存取數據庫數據。

    自定義模型:繼承QAbstractItemModel創建新的Model。QAbstractListModel或QAbstractTableModel提供了一些基本的實現,繼承QAbstractListModel或QAbstractTableModel可能是更好的選擇。

不管數據在底層以何種數據結構存儲,所有QAbstractItemModel的子類都將以包含項表格的層次結構來呈現這些數據。視圖使用這個約定來存取模型中數據 的項,但是他們向用戶顯示信息的方法不會受到限制。數據發生改變時,model通過信號槽機制來通知關聯的views。

wKiom1hCX2fhp7gJAABaIKq9UGQ488.png

2、模型索引

    爲了確保數據顯示與數據訪問分離,引入了模型索引的概念。通過Model獲取的數據項可以通過模型索引顯示。Views和Delegates都使用索引來訪問數據項,然後顯示出來。因此,只有Model需要了解如何獲取數據,並且Model管理的數據類型可以定義地非常廣泛。模型索引包含一個指向創建它們的Model的指針,這樣可以避免使用多個Model時引起混淆
 QAbstractItemModel *model = index.model();

    模型索引爲信息塊提供了臨時參照,通過它可以用來提取或修改Model中的數據。由於Model經常會重新組織內部的結構,使得模型索引失效,因此不應保存模型索引。如果需要一個對信息塊的長期參照,必須創建一個永久的模型索引。這樣會爲不斷更新的Model信息提供一個參照。臨時模型索引由QModelIndex類提供,而永久模型索引則由QPersistentModelIndex類提供。

    爲了獲取相應數據項的模型索引,必須指定Model的三個屬性:行數,列數,父項的模型索引。

3、行數和列數

    在Model最基本的形式中,Model可以使用簡單的表格進行存取,表格中的項根據行號和列號確定。但這並不意味底層數據以數組結構儲存,使用行號和列號只是部件之間相互通信的一個約定。我們可以提取任何指定行號和列號的Model項的信息,同時得到一個代表這個項的索引。

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

    Model爲簡單且單一層次數據結構如列表和表格提供接口的模型不需要提供任何其他的信息,但是,就像上面的例子所標明的一樣,當要獲得一個模型索引時我們要提供更多的信息。

wKioL1hCX43TD9UnAAA04VNna-U552.png

    圖表顯示了一個基本的table Model,表格的每一項用一對行列數來定位。通過行列數,可以獲取代表一個數據項的模型索引。

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

    Model的頂層項總是通過指定QModelIndex()函數來作爲他們的父項參照

4、父項

    當在一個表格或列表視圖中使用數據時,模型提供的表格型數據接口是較爲理想的。行列號系統正好映射到Views顯示項的方法。然而,諸如樹型視圖的結構要求Model對其內部項要有更靈活的接口。因此,每一個項也可能是另外一個表的父項,同樣地,樹型視圖的頂級項也可以包含另一個列表。
    當需要Model項一個索引時,我們必須提供一些關於這個項的父項的一些信息。在Model外,引用一個項的唯一方法就是通過模型索引,所以一個父項的模型索引也必須要提供。

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

wKiom1hCX6uAyTwzAAApW-xsRT4888.png

    圖表顯示了一個樹Model的表示法,樹Model的項由父項,行和列號定位。項”A”和項”C”代表Model中的頂層成員。

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

    項“A”有一個子成員,項“B”的Model index可以用以下的代碼獲得:

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

5、數據項的角色

    Model中的項可以爲其他部件扮演不同的角色,允許爲不同的情形提供各種不同的數據。例如,Qt::DisplayRole就是用於存取一個可以在視圖中以文字顯示的字符串。通常情況下,項包含各種不同的角色的數據,標準的角色由Qt::ItemDataRole定義。通過傳遞對應項的模型索引給Model,並指定一個角色以取得我們想要的數據的類型,我們就可以從Model中取得項的數據:

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

wKioL1hCX8yhWbt9AACX8lStoK4767.png

    角色爲Model指明哪一種數據被參照。視圖以不同的方式顯示角色,所以爲每一種角色提供合適的信息是很重要的。Qt::ItemDataRole裏定義的標準角色涵蓋了項數據最常用的用途。通過爲每個角色提供合適的項數據,Model就可以爲視圖和委託提供關於怎樣向用戶顯示項的提示。各種不同的視圖都有根據需要中斷或忽略這些信息的自由,同時也可以爲特定的程序要求定義另外的角色。

6、總結

    模型索引以一種獨立於任何底層數據結構之外的方法,爲視圖和委託提供關於模型中項的定位信息。

    項以他們的行號和列號,以及他們的父項的模型索引作爲參照

    模型索引應其他部件如視圖和委託的要求由模型來構造。

    當使用index()函數請求一個索引時,如果指定一個有效的模型索引作爲父項索引,則返回的索引指向模型裏這個父項下面的一個項。這個索引獲得該項的子項的一個參照。

    當使用index()函數請求一個索引時,如果指定一個無效的模型索引爲父項索引,則返回的索引指向模型裏的一個頂級項。

    角色識別一個項中的各種不同類型的相關數據。

7、Model indexes使用

    爲了演示如何使用模型索引從模型中獲得數據,我們創建了一個沒有視圖的QFileSystemModel,把文件名和目錄名顯示在一個部件中。雖然這個例子並沒有顯示使用模型的常用方法,但是說明了使用模型索引時Model所使用的約定。我們用下面的方法構建一個文件系統模型:

QFileSystemModel *model = new QFileSystemModel;
  QModelIndex parentIndex = model->index(QDir::currentPath());
  int numRows = model->rowCount(parentIndex);

    在這個例子中,我們創建了一個默認的QFileSystemModel,使用這個Modelindex()函數提供的一個特定的實現獲得一個父索引,同時使用rowCount()函數計算出這個模型的行數。
    爲簡單起見,我們只關注模型第一列的數據。我們按順序逐行檢查,獲得每行第一個項的模型索引,然後讀取存儲在Model項裏的數據。

for (int row = 0; row < numRows; ++row)

{
     QModelIndex index = model->index(row, 0, parentIndex);

}

    爲了獲得一個模型索引,我們指定行號,列號(第一列爲0),以及我們想要的所有數據項的父項模型索引。儲存於每個項中的文本可以用Modeldata() 函數獲得。我們指定一個模型索引以及DisplayRole來取得一個字符串形式的項數據。

QString text = model->data(index, Qt::DisplayRole).toString();

從模型中提取數據的一些基本原則:

    A、Model的大小可以用rowCount() 和 columnCount()得到。這兩個函數通常要指定一個父模型索引。

    B、模型索引用於存取Model裏的項。指定項必須要有行號,列號以及父模型索引。

    C、要存取Model的頂級項,就用QModelIndex()函數指定一個空的模型索引作爲父模型索引。

    D、項包含不同角色的數據。要獲得一個特定角色的數據,必須要爲Model提供模型索引和角色。

    通過實現 QAbstractItemModel提供的標準接口可以創建新的模型。

8、自定義Model

    QAbstractItemModel定義了Model的標準接口。QAbstractItemModel及其派生類均以表格的形式提供訪問數據。

    自定義Model需要繼承QAbstractItemModel並重寫下列函數:

QVariant QAbstractItemModel::data(const QModelIndex & index,
int role = Qt::DisplayRole) const

    訪問數據的接口,QModelIndex是存儲Model表格的索引,index.row()和index.column()可以得到索引中指向的行或列。

    role是一個枚舉代表了數據的渲染方式,QVariant是變體型可以被轉換爲任意Qt兼容的數據類型。

bool QAbstractItemModel::setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole)

dataChanged信號

void QAbstractItemModel::dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector & roles = QVector ())

    在數據被改變後由setData()方法發送dataChanged()信號,通知視圖刷新數據。使用兩個QModelIndex通知刷新的範圍。

rowCount() / columnCount()

返回模型的行數 / 列數。

headerData()

返回表頭信息。

三、View

1View簡介

    在Model/View架構中,View從Model中獲得數據項然後顯示給用戶。數據顯示的方式不必與Model提供數據的表示方式相同,可以與底層存儲數據項的數據結構完全不同。內容與顯式的分離是通過由QAbstractItemModel提供的標準模型接口,由QAsbstractItemview提供的標準視圖接口和用來表示數據項的模型索引共同實現的。View負責管理從Model中讀取的數據的外觀佈局。
    它們自己可以去渲染每個數據項,也可以利用Delegate來既處理渲染又進行編輯。
除了顯示數據,Views也處理數據項的導航,參與有關於數據項選擇的部分功能。View也實現一些基本的用戶接口特性,如上下文菜單與拖拽功能。View也爲數據項提供了默認編程功能,也可搭配Delegate實現自定義的編輯器。

    View創建時不必需要Model,但在View能顯示一些真正有用的信息之前必須提供一個Model。View通過使用選擇來跟蹤用戶選擇的數據項,這些數據項可以由單個View獨立維護,也可以由多個View共享。像QTableView和QTreeView這樣的視圖,除數據項之外也可顯示標題(Headers),標題部分通過QHeaderView來實現。標題與View一樣總是訪問包含他們的同樣的Model。通常使用QAbstractItemModel::headerData()函數從Model中獲取數據,標題通常以表格的形式顯示在標籤中。爲了爲View提供更多特定的標籤,新標題需要子類化QHeaderView

2View使用

    Qt提供了三個現成的View 類,能夠以用戶熟悉的方式顯示Model中的數據。QListView能夠以列表的形式將Model中的數據項顯示,或是以經典的圖標視圖形式顯示。QTreeView能夠將Model中的數據項作爲具有層次結構的列表的形式顯示,允許以緊湊的深度嵌套的結構進行顯示。QTableView能夠架構Model中數據項以表格的形式展現,更像是一個電子表格應用程序的外觀佈局。

wKioL1hCX_XQhKrWAACt9TupChs939.png

    以上這些標準View的默認行爲足以應付大多的應用程序,它們提供了基本的編輯功能,也可以定製特殊的需求。

3、單個Model使用

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QStringList numbers;
numbers << "One" << "Two" << "Three" << "Four" << "Five";

QAbstractItemModel *model = new QStringListModel(numbers);
QListView *view = new QListView;
view->setModel(model);
view->show();
return app.exec();
}

    View會表示通過Model的接口來訪問Model中的數據內容。當用戶試圖編輯數據項時,View會使用默認Delegate來提供一個編輯組件。

wKioL1hCYBLxgFkCAAAZ8YuDFpk641.png

    上圖顯示QListView如何顯示字符串列表模型的內容。由於模型是可編輯的,視圖會自動允許列表中的每一項使用默認的委託編輯。

4單個模型對多個視圖使用

    爲多個Views提供相同的Model是非常簡單的事情,只要爲每個View設置相同的Model。以下代碼創建了兩個表格視圖,每個視圖使用同樣的簡單的表格模型。

QTableView *firstTableView = new QTableView;

QTableView *secondTableView = new QTableView;

 firstTableView->setModel(model);

 secondTableView->setModel(model);

    在Model/View架構中,信號-槽機制的使用意味着Model中發生的改變會傳遞到所有連接的View中,保證了不管我們使用哪個View,訪問的都是同樣的一份數據。

wKiom1hCYGaDIzgCAABRh4ixVxE710.png

    上圖展示了同一Model上的兩個不同的Views,每個視圖中包含一點數量的選中項。儘管在不同的View中顯示的Model中的數據是一致的,每個View都維護它們自己的內部選擇模型,但有時候在某些情況下,共享一個選擇模型也是需要的。

5、處理數據項的選擇

    多個View中數據項選擇處理機制由QItemSelectionModel類提供。所有標準的View默認都構建自己的選擇模型,以標準的方式與它們交互。視圖使用的選擇模型可以用selectionModel()函數獲得,通過setSelectionModel()函數可以設置選擇模型。當我們要在一個Model上提供多個一致的Views時,視圖中對選擇模型的控制能力是非常有用的。通常來講,除非子類化一個Model或View,不必直接操作選擇的內容。

    多視圖間共享選擇

    雖然視圖默認提供的選擇模式很方便,當我們在同一Model使用多個視圖時,在多個視圖間方便地顯示Model數據和用戶選擇是需要的。由於View允許自身內部的選擇模式可以被設置,使用以下代碼可以在多個視圖間實現一樣的選擇:

secondTableView->setSelectionModel(firstTableView->selectionModel());

    第二個視圖設置了與第一個視圖一樣的選擇模式。兩個視圖操作同樣的選擇模式,保持了數據與選中項的同步。

wKiom1hCYJKQS52oAABRh4ixVxE877.png

    以上例子顯示,同樣類型的兩個視圖顯示了同一個模型的數據。然而,如果使用兩個不同類型的視圖,在每個視圖顯示的選中項將會不同。例如,在一個表格視圖中持續選擇可能會在數學視圖中顯示的是高亮。

四、Delegate

1Delegate類簡介

    不同於MVC模式,模型/視圖設計並不包含用於處理與用戶交互的完全獨立的部件。 通常,視圖負責把模型數據顯示給用戶,以及處理用戶的輸入。爲了讓這種輸入有靈活性,這種交互由Delegates來完成。這些部件在視圖中提供輸入功能,同時負責傳遞視圖中的單個項。控制Delegates的標準接口在 QAbstractItemDelegate類中定義。

    QQAbstractItemDelegate則是所有Delegate的抽象基類。自Qt 4.4之後,默認的Delegate實現是QStyledItemDelegate。但QStyledItemDelegate和QItemDelegate都可以作爲視圖的編輯器,二者的區別在於,QStyledItemDelegate使用當前樣式進行繪製。在實現自定義委託時,推薦使用 QStyledItemDelegate作爲基類

    Delegates通過實現 paint() 和 sizeHint()函數來傳遞他們本身的內容。但是,簡單的基於部件的Delegates可以子類化QItemDelegate 類而不是 QAbstractItemDelegate類,這樣就可以利用這些函數的默認實現。

    Delegates的編輯器可以通過使用部件來管理編輯的過程來實現,也可以通過直接處理事件來實現。第一種方法在這一節的後面會講到,在Spin Box Delegate這個例子中也是使用的這種方法。
    Pixelator這個例子演示瞭如何建立一個專門用於表格視圖的自定義委託。

2、Delegate使用

    QT提供的標準視圖使用QItemDelegate的實例提供編輯功能。Delegates接口的默認實現以通常的樣式將項傳遞給每一個標準視圖:QListView, QTableView, 和 QTreeView。所有的標準角色都由標準視圖的默認Delegates處理。

    視圖所使用的Delegates可以用itemDelegate() 函數返回。setItemDelegate()函數允許你爲一個標準視圖安裝一個自定義的Delegates,當爲自定義的視圖設定一個Delegates時必須要用到這個函數。

    下面要實現的Delegates用一個QSpinBox來提供編輯功能,主要是想用於顯示整數的Model。雖然我們基於這個目的建立了一個基於整數的自定義模型,但是我們可以很容易地以QStandardItemModel代替,因爲自定義委託控制數據的輸入。我們構造一個表格視圖以顯示模型裏的內容,同時會使用自定義的Delegates來進行編輯。

wKioL1hCYMKwaLygAAAXhhkkKt0626.png

    我們用QItemDelegate類來子類化這個Delegate,因爲我們不想去寫那些自定義的顯示函數。然而,我們必須提供下面的函數以管理編輯器部件:

class SpinBoxDelegate : public QItemDelegate
{    
      Q_OBJECT  
      public:     SpinBoxDelegate(QObject *parent = 0);
      QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
      void setEditorData(QWidget *editor, const QModelIndex &index) const;
    
 void setModelData(QWidget *editor, QAbstractItemModel *model,
                       const QModelIndex &index) const;
      void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};

    注意,構建Delegate時編輯器部件是沒有建立的。只有需要時我們才構建一個編輯器部件。

3、提供編輯器

    當表格視圖需要提供編輯器時,就向Delegate請求提供一個適合當前被修改項的編輯器部件。createEditor()函數提供了Delegate用於建立一個合適部件需要的所有東西:

QWidget *SpinBoxDelegate::createEditor(QWidget *parent,

    const QStyleOptionViewItem &/* option */,

    const QModelIndex &/* index */) const

{

    QSpinBox *editor = new QSpinBox(parent);

    editor->setMinimum(0);

    editor->setMaximum(100);

 

    return editor;

}

    注意,我們不需要保留一個指向編輯器部件的指針,因爲當不再需要編輯器的時候,視圖會負責銷燬它。
    我們把Delegate默認的事件過濾器安裝在編輯器上,以確保它提供用戶所期望的標準編輯快捷鍵。額外的快捷鍵也可以增加到編輯器上以允許更復雜的行爲。

    通過調用我們稍後定義的函數,視圖確保能正確地設定編輯器的數據和幾何尺寸大小。根據視圖提供的模型索引,我們可以建立不同的編輯器。比如,我們有一列數據是整數,一列數據是字符,那根據當前被編輯的列,我們可以返回一個SpinBox 或 QLineEdit。
    Delegate必須提供一個函數以便將Model數據複製到編輯器裏。在這個例子中,我們讀取儲存在displayrole裏的數據,並相應的把這個值設定在編輯器spin box中。

void SpinBoxDelegate::setEditorData(QWidget *editor,

                                    const QModelIndex &index) const

{

    int value = index.model()->data(index, Qt::EditRole).toInt();

 

    QSpinBox *spinBox = static_cast<QSpinBox*>(editor);

    spinBox->setValue(value);

}

    在這個例子中,我們知道編輯器部件是一個spin box,但是在Model中,我們可能會因應不同的數據類型提供不同的編輯器,在存取它的成員函數前,我們需要將這個部件轉換成適合的類型。

4、提交數據給模型

    當用戶在spin box中完成編輯數值時,通過調用 setModelData()函數,視圖要求Delegate將被編輯後的值儲存到Model中。

void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,

                                   const QModelIndex &index) const

{

    QSpinBox *spinBox = static_cast<QSpinBox*>(editor);

    spinBox->interpretText();

    int value = spinBox->value();

 

    model->setData(index, value, Qt::EditRole);

}

    由於視圖爲Delegate管理編輯器部件,所以我們只要以編輯器提供的內容更新模型。在這個例子中,我們確保spinbox的內容是最新的,同時通過指定的indexspinbox包含的值更新到模型中。

    當Delegate完成編輯時,標準的QItemDelegate類會發出closeEditor()信號通知視圖。視圖會確保關閉和銷燬編輯器部件。在這個例子中,我們只提供簡單的編輯功能,所以我們不需要發出這個信號。

    所有的數據操作都是通過QAbstractItemModel提供的接口進行的。這使得Delegate最大程度地獨立於它所操控的數據類型。但是爲了使用某些類型的編輯器部件,一些假設是必須要做的。在這個例子中,我們假設Model包含的數據全部是整數值,但是我們仍然可以將這個Delegate用於不同種類的Model,因爲QVariant可以爲沒有考慮到的數據提供合理的默認值。

5、更新編輯器幾何外形

    編輯器的幾何外形由Delegate負責管理。當創建編輯器時,以及視圖中項的大小及位置改變時,都必須設定編輯器的幾何外形。幸好,在視圖中,View 選項對象中提供了所有必需的幾何尺寸信息。

void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,

    const QStyleOptionViewItem &option, const QModelIndex &/* index */) const

{

    editor->setGeometry(option.rect);

}

    在這個例子中,我們只使用了View選項提供的項矩形幾何尺寸信息。傳遞多個要素項的Delegate不會直接使用項矩形。它會定位與項中的其它要素編輯器的位置。

6、編輯提示

    編輯後,Delegate應該爲其他部件提供關於編輯過程結果的提示,同時提供協助後續編輯操作的提示。這可以通過發射closeEditor()信號以及一個合適的提示來實現。這個過程由默認的QItemDelegate事件過濾器負責,在構造spinbox時,事件過濾器就已安裝了。

    Spinbox的行爲可以稍作調整以使它更易用。在QItemDelegate提供的默認事件過濾器中,如果用戶按回車來確認他們在spinbox中的選擇,Delegate就把值提交給Model並關閉spinbox。通過在spinbox中安裝自定義的的事件過濾器,可以改變這種行爲,並提供適合我們需要的編輯提示。例如,我們可以用EditNextItem提示來發射closeEditor()信號,來自動地開始視圖中下一個項的編輯。

    另一種不需要使用事件過濾器的方法就是提供我們自己的編輯器部件,或者是爲了方便可以子類化QSpinbox。這種可選方法讓我們對編輯器的行爲有更多的控制,而這需要以寫額外的代碼作爲代價。如果要自定義一個標準Qt編輯器部件的行爲,在Delegate中安裝事件過濾器通常是較容易的。
    Delegate不必一定要發射這些提示,但相對於那些發射提示以支持通用編輯動作的Delegate,他們跟程序的整合性就會降低,所起的作用也很小。

五、項視圖的選擇處理

1、項視圖的選擇處理簡介

    項視圖類中使用的選擇模型較QT3中有了很多的改進,爲基於Model/View架構的選擇提供了更全面的描述。雖然項視圖提供的處理選擇的標準類已經是很充足,但可以創建專門的選擇模型以適應自定義的模型和視圖的需要。

    視圖中關於選中項的信息存儲在QItemSelectionModel類的一個實例中,用於保存數據項的模型索引信息,並且獨立於任何視圖。由於一個模型可以用於很多視圖,所以項視圖共用選擇也是可行的,應用程序就可以以一致的方式顯示多個視圖。

    選擇由多個選擇範圍組成。僅僅通過記錄選中項的開始模型索引與結束模型索引範圍,維護大量項的選擇信息。爲了描述選擇,構建多個非連續選擇數據項。

    選擇是選擇模型保留模型索引的集合。最近選擇的數據項被稱爲current selection。即使在使用之後,應用程序可以通過使用某種類型的選擇命令來修改選擇的效果。

    當前項與選中項

    在視圖中,有一個當前項和一個選中項兩種獨立狀態。一個項有可能同時是當前項與選中項。例如,當使用鍵盤導航時,視圖負責確保當中總是有一個當前項。下面的表格列出了當前項與被選擇項的不同之處:

    A、只能有一個當前項,可以有多個選中項;

    B、當前項會隨着鍵盤或鼠標按鈕點擊而改變;當用戶交互作用於項時,項選擇狀態的設定或者撤銷要看幾個預定義的模式而定—如單選,多選等。

    C、如果編輯鍵F2按下或選項被雙擊,當前項將被編輯;當前項被用作一個支撐點來定義一個選擇或反向選擇(或兩者的混合)的範圍。

    D、當前項由焦點矩形標註,選中的項由選擇矩形標註。

    在處理選擇時,考慮使用QItemSelectionModel來記錄一個項模型中所有項的選擇狀態是很有幫助的。只要建立一個選擇模型,項的集合就可以選擇,撤銷選擇,或者反向選擇,而不需要知道哪些項已經被選擇。任何時候都可以提取所有被選擇項的索引,同時通過信號和槽機制,其他的部件也可以知道選擇 模型的變動。

2、選擇模型的使用

    標準視圖類提供了大多數應用程序都可以使用的默認選擇模型。一個視圖所使用的選擇模型可以通過視圖的selectionModel()函數獲取,用setSelectionModel()函數可以在多個視圖中共用同一選擇模型,所以一般不需要構造新的選擇模型。

    指定一個模型,同時爲QItemSelection指定一對模型索引,就可以創建一個選擇。使用索引指向指定模型中的項,並把解釋成一個選中項方塊中左上角和右下角的項。要把選擇應用於Model裏的項,必須把選擇提交給選擇模型。可以通過多種方法實現,每一種方法在顯示選擇模型的選擇都有不同的效果。

    選擇項

    爲了演示選擇的一些基本特徵,我們構建了一個共有32個項的自定義表格模型的實例,並且用一個表格視圖顯示它的數據。

   TableModel *model = new TableModel(8, 4, &app);
  QTableView *table = new
 QTableView(0);
  table->setModel(model);
  QItemSelectionModel*
 selectionModel = table->selectionModel();

    提取視圖的默認選擇模型以備後用。我們不修改模型裏的任何項,而是選擇一些顯示在視圖左上角的項。要達到這個效果,我們要提取選擇區域中左上角及右下角相應的模型索引:

   QModelIndex topLeft;
  QModelIndex bottomRight;
  topLeft =
 model->index(0, 0, QModelIndex());
  bottomRight =
 model->index(5, 2, QModelIndex());

    要選擇模型中的這些項,並看到視圖上相應的變化,我們需要構建一個選擇對象,並把它應用於選擇模型:

  QItemSelection selection(topLeft, bottomRight);
  selectionModel->select(selection,QItemSelectionModel::Select);

    通過使用一選擇標識組合定義的命令就可以將選擇應用於選擇模型。在本例中,不管項的原來的狀態是怎樣,使用的標識會使選擇對象記錄的項都包含在選擇模型中。選擇的結果由視圖顯示。

wKioL1hCYOiT_NKZAAChrwbVEWo069.png

    項的選擇可以通過由選擇標識定義的不同操作進行修改。由此而產生的選擇結果可能有一個複雜的結構,但能通過選擇模型被有效地呈現。

    讀取選擇狀態

    儲存在選擇模型裏的模型索引可以通過selectedIndexes()函數讀取。selectedIndexes()函數返回一個未排序的模型索引列表,只要我們知道這些模型索引屬於哪一個Model,就可以歷遍這些選擇項。

QModelIndexList indexes = selectionModel->selectedIndexes();

QModelIndex index;

foreach(index, indexes)

{

    QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());

    model->setData(index, text);

}

    以上代碼使用QTforeach關鍵字來歷遍及修改選擇模型返回的索引所對應的項。

選擇模型發射信號以表明選擇的變動。這些信號通知其它部件關於項模型中整體選擇及當前焦點項的改變。我們可以把selectionChanged()信號 連接到一個槽,當選擇改變時,就可以檢查模型中被選擇或撤銷選擇的項。這個槽的呼叫要用到兩個QItemSelection對象:一個包含新選擇項對應的 索引列表,另一個包含新撤銷選擇項的對應索引列表。以下代碼我們提供一個接受selectionChanged() 信號的槽,用一個字符串填滿選擇的項,並清除撤銷選擇項的內容。

void MainWindow::updateSelection(constQItemSelection &selected,
     const QItemSelection&deselected)
{
     QModelIndex index;
     QModelIndexList items =selected.indexes();
     foreach (index, items) {
    
 QString text =QString("(%1,%2)").arg(index.row()).arg(index.column());
    
 model->setData(index, text);
     }
     items =
 deselected.indexes();
     foreach (index, items)
        model->setData(index, "");
}

    將currentChanged()信號連接到一個使用兩個模型索引爲參數的槽函數,我們就能夠保持對當前焦點項的跟蹤。這兩個索引分別對應前一個焦點項和當前焦點項。下面的代碼我們提供一個接受currentChanged()的槽,並使用提供的信息更新QMainWindow的狀態欄:

void MainWindow::changeCurrent(const QModelIndex ¤t,

    const QModelIndex &previous)

{

    statusBar()->showMessage(

        tr("Moved from (%1,%2) to (%3,%4)")

            .arg(previous.row()).arg(previous.column())

            .arg(current.row()).arg(current.column()));

}

    用戶通過這些信號對選擇進行監控,但是我們也可以直接更新選擇模型。

    更新選擇

    選擇命令是通過QItemSelectionModel::SelectionFlag定義的一個選擇標識組合來執行的。當任何一個select()函數被調用時,每一個選擇標識就會通知選擇模型應怎樣更新選擇項的內部記錄。最常用的標識就是Select,它指示選擇模型把指定的項記錄爲選中的狀態。 Toggle標識使選擇模型轉換指定項的狀態,選擇原來沒有選中的項,撤銷對當前選中項的選擇。Deselect標識撤銷對指定項的選擇。

    選擇模型裏的單個項可以通過建立一個選擇項,然後把這些選擇項應用到選擇模型來更新。在下面的代碼中,我們把第二個選擇項應用於前面的表格模型,然後用Toggle指令來轉換指定項的選擇狀態。

QItemSelection toggleSelection;
  topLeft =model->index(2, 1, QModelIndex());
  bottomRight =model->index(7, 3, QModelIndex());
  toggleSelection.select(topLeft, bottomRight);
  selectionModel->select(toggleSelection, QItemSelectionModel::Toggle);

這個操作的結果顯示在下面的表格視圖中,直觀地顯示了我們達到的效果:

wKiom1hCYQqDSqB4AAChy_UUTfQ450.png

    默認情況下,選擇指令只對模型索引指定的單個項進行操作。然而,用於描述選擇指令的標識可以跟額外的標識搭配使用,以改變整行和整列的選擇。例如,如果用一個索引調用select(),但是跟一個有Select和Rows組合的指令使用,該項所在的整行都會被選擇。下面的代碼演示了Rows和 Columns的使用:

 QItemSelection columnSelection;

    topLeft = model->index(0, 1, QModelIndex());

    bottomRight = model->index(0, 2, QModelIndex());

    columnSelection.select(topLeft, bottomRight);

    selectionModel->select(columnSelection,

    QItemSelectionModel::Select | QItemSelectionModel::Columns);

    QItemSelection rowSelection;

    topLeft = model->index(0, 0, QModelIndex());

    bottomRight = model->index(1, 0, QModelIndex());

    rowSelection.select(topLeft, bottomRight);

    selectionModel->select(rowSelection,

    QItemSelectionModel::Select | QItemSelectionModel::Rows);

    雖然只指定了四個索引給選擇模型,但Columns 和 Rows 選擇標識的使用意味着選擇了兩列和兩行。下圖顯示了這兩個選擇的結果:

wKioL1hCYS-iSMXOAACiEgtwmmg048.png

    應用於本例模型的命令全部都是涉及累積模型中項的選擇的。其實它還可以清除選擇,或者是以新的選擇替換當前的選擇。

    要用一個新選擇替換當前的選擇,就要用Current標識跟其它選擇標識搭配使用。使用這個標識的指令通知選擇模型用select()函數裏指定的模型索引集合來替換當前的模型索引集合。在開始增加新的選擇前,如果要清除所有的選擇,就要用Clear標識跟其它選擇標識搭配使用。它有重設選擇模型的索引集合的效果。

    選擇模型的所有項

    爲了選擇模型裏的所有項,必須爲模型的每一層建立一個選擇,以涵蓋該層的所有項。我們通過一個給定的父索引並提取左上角和右下角相應的索引來實現這個操作。

QModelIndex topLeft = model->index(0, 0, parent);

QModelIndex bottomRight = model->index(model->rowCount(parent)-1,

        model->columnCount(parent)-1, parent);

    以這些索引和模型構建一個選擇,到時相應的項就會在選擇模型中被選中。

QItemSelection selection(topLeft, bottomRight);

selectionModel->select(selection, QItemSelectionModel::Select);

    對模型所有的層都要執行這樣的操作,對於頂層項,我們以通常的方法定義父項索引:

QModelIndex parent = QModelIndex();

    對於等級層次類型的模型,使用hasChildren() 函數可以確定一個指定的項是否是另一層次項的父項。

六、自定義Model

    模型與視圖的部件在功能上的分離,使得可以創建模型並能利用現有的視圖。這種方法讓我們可以使用標準的圖形用戶界面部件,如QListView, QTableView, 和QTreeView,來呈現多樣化來源的數據。

    QAbstractItemModel類提供了一個足夠靈活的接口,它支持以層次結構安排信息的數據源,爲數據的插入,移動,修改或排序提供了可能性。它同時對拖放操作也提供支持。

    QAbstractListModel 和 QAbstractTableModel 類爲較簡單的無層次數據結構的接口提供支持,同時用作簡單列表和表格模型的起點(模型基類)會比較容易使用。

    本節中,我們建立一個簡單的只讀模型來了解模型/視圖結構的基本原則。然後我們改寫這個簡單模型使得用戶可以修改項。
    對於更爲複雜的模型,請看例子Simple Tree Model 關於子類化QAbstractItemModel 的要求在 Model Subclassing Reference子類化模型參考的文檔中會有更加詳細的描述。

1、模型設計

    當爲一個現有的數據類型創建一個新的模型時,考慮哪一種類型的模型適合於爲數據提供接口是很重要的。如果數據結構可以用一個列表或表格的項來表示,可以子類化QAbstractListModel 或QAbstractTableModel,因爲這兩個類爲很多函數提供了合適的默認實現。

    然而,如果底層的數據結構只能以層次樹結構來表示,那就必須子類化 QAbstractItemModel類,在Simple Tree Model 例子中使用的就是這種方法。
    在本節中,我們實現一個基於字符串列表的簡單模型,所以QAbstractListModel是一個理想的基類。

    無論底層的數據結構如何組織,在自定義的模型中提供QAbstractItemModel的接口函數是很好的思路,這可以很好的訪問底層的數據結構。這樣使得操作帶數據的模型更容易,而且可以使用標準API與其他通用的Model/View組件進行交互。

2、只讀模型

    我們這裏實現的模型是一個簡單的、無層次的、基於標準的QStringListModel類的只讀數據模型。它包含一個QStringList字符串列表作爲內部數據源,並且只實現所需要的東西作爲功能性模型。爲了使實現更加容易,我們子類化 QAbstractListModel,因爲它爲列表模型定義了合理的默認行爲,並且有比QAbstractItemModel更簡單的接口。

    當實現一個模型時,很重要的一點是QAbstractItemModel本身並不存儲任何數據,只提供一個視圖用於存取數據的接口。對於一個微型只讀模型,只需要實現幾個函數就可以,因爲大多數接口都有默認的實現。類的聲明如下:

class StringListModel :public QAbstractListModel
{
     Q_OBJECT

public:
     StringListModel(constQStringList &strings, QObject *parent = 0)
         : QAbstractListModel(parent),stringList(strings) {}

     int rowCount(constQModelIndex &parent = QModelIndex()) const;
     QVariant data(constQModelIndex &index, int role) const;
     QVariant headerData(intsection, Qt::Orientation orientation,
                         int role = Qt::DisplayRole)const;

private:
     QStringList stringList;
};

    除了模型的構造函數,我們只需要實現兩個函數:rowCount()返回模型裏的行數,data()返回指定模型索引對應項的數據。設計良好的模型同時還要實現headerData(),以便讓樹型或表格視圖顯示它們的表頭信息。

    需要注意的是這是一個非層次結構的模型,所以我們不必擔心父-子項關係。如果我們的模型是層次結構的,那我們還必須實現index() and parent()函數。字符串列表儲存在內部的私有成員變量stringList中。

3、模型的維度

    我們想要模型的行數跟字符串列表中字符串的個數一樣,在實現rowCount()函數時要記住這一點。

int StringListModel::rowCount(const QModelIndex &parent) const
{
     return stringList.count();
}

    因爲模型是非層次結構的,因此我們可以忽略父項對應的模型索引。從QAbstractListModel派生出來的模型默認只保留一欄,所以我們不必重新實現 columnCount()函數。

4、模型的表頭和數據

    對於視圖中的項,我們要返回字符串列表中的字符串。data()函數就是負責返回對應索引參數項的數據的:

QVariant StringListModel::data(const QModelIndex &index, int role) const
{
     if (!index.isValid())
         return QVariant();
     if (index.row() >=stringList.size())
         return QVariant();
     if (role ==Qt::DisplayRole)
         return stringList.at(index.row());
     else
         return QVariant();
}

    如果提供的模型索引有效,行數在字符串列表的項範圍內,且要求的角色是支持的,那我們只返回一個有效的QVariant。
    一些視圖,如QTreeView 和 QTableView,表頭可以跟項數據一起顯示。如果我們的模型顯示在帶表頭的在視圖中,我們想讓表頭顯示行號和列號。我們可以通過子類化headerData() 函數來提供表頭的相關信息。

QVariantStringListModel::headerData(int section, Qt::Orientation orientation,
                    int role) const
{
     if (role !=Qt::DisplayRole)
         return QVariant();
     if (orientation ==Qt::Horizontal)
         returnQString("Column %1").arg(section);
     else
         returnQString("Row %1").arg(section);
}

    再次強調,只有是我們支持的角色才返回一個有效的QVariant。當要確定返回的確切數據時,表頭的方向也是要考慮的。並不是所有的視圖在顯示項數據時都顯示錶頭,那些不顯示錶頭的視圖可能已設定了隱藏表頭。但是,我們還是建議實現headerData()函數以便提供模型數據的相關信息。

    一個項可以有幾個角色,依據指定的角色可以給出不同的數據。在我們現在創建的模型中,項只有一個角色DisplayRole,所以不管指定什麼角色,我們都返回角色指定相應項的數據。然而,我們可以重新利用DisplayRole角色提供的數據用在其它角色上,如ToolTipRole角色,視圖可以以工具提示的形式顯示項的信息。

5、可編輯模型

    只讀模型顯示瞭如何向用戶呈現簡單的選項,但是對於大多數的應用程序,一個可編輯的列表模型會更有用。我們可以通過修改爲只讀模型而實現的 data()函數,以及實現另外兩個函數flags() 和 setData(),來把只讀模型改爲可以編輯項的模型。下面的函數聲明要加到類定義中:

Qt::ItemFlags flags(constQModelIndex &index) const;
  bool setData(constQModelIndex &index, const QVariant &value,
                  int role =Qt::EditRole);

6、可編輯模型的標識

    在創建編輯器之前,委託會檢查項是否可以編輯。模型必須讓委託知道它的項是可以編輯的。我們可以通過爲模型裏的每一個項返回正確的標識來實現這個要求。在這個例子中,我們把所有的項激活並把它們設定爲可選擇及可編輯的:

Qt::ItemFlagsStringListModel::flags(const QModelIndex &index) const
{
     if (!index.isValid())
         returnQt::ItemIsEnabled;
     returnQAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}

    我們不需要知道委託怎樣執行實際的編輯過程。我們只需要爲委託提供一個在模型中設定數據的方法。這個方法可以通過setData()函數來實現:

bool StringListModel::setData(const QModelIndex &index,
               const QVariant &value, int role)
{
     if (index.isValid()&& role == Qt::EditRole) {
         stringList.replace(index.row(),value.toString());
         emitdataChanged(index, index);
         return true;
     }
   return false;
}

    在這個模型中,對應模型索引的字符串項被提供的值所代替。然而,在我們修改字符串列表之前,我們必須保證索引是有效的,項是正確的類型,並且角色是支持的角色。通常,角色我們要用EditRole,因爲它是標準項委託所使用的角色。然而,對於邏輯值,可以使用角色 Qt::CheckStateRole,並設定標識爲Qt::ItemIsUserCheckable;這樣就可以用一個複選框來編輯這個值。對於所有的角色,模型中的底層數據都是一樣的,這只是爲了讓模型與標準部件結合使用時更加容易。

    當設定數據以後,模型必須要讓視圖知道某些數據已經被改動。這個可以通過發射 dataChanged()信號來完成。因爲只有一個項的數據有更改,所以信號中所指定的項範圍僅限於一個模型索引。
    同時,data()函數也要更改以增加對 Qt::EditRole角色的檢測

QVariant StringListModel::data(constQModelIndex &index, int role) const
{
     if (!index.isValid())
         return QVariant();
     if (index.row() >=stringList.size())
         return QVariant();
     if (role ==Qt::DisplayRole || role == Qt::EditRole)
         returnstringList.at(index.row());
     else
         return QVariant();
}

7、插入、刪除行

    在一個模型中,行數和列數是可以改變的。在字符串列表模型中,只有行數改變有意義,所以我們只需要重新實現插入和刪除行的函數。這些函數在類定義中的聲明如下:

bool insertRows(intposition, int rows, const QModelIndex &index = QModelIndex());
  bool removeRows(intposition, int rows, const QModelIndex &index = QModelIndex());

    由於字符串列表模型中行數對應的字符串列表中的字符串數量,所以insertRows()函數在字符串列表中指定的位置之前插入幾個空的字符串。插入的字符串的個數跟所指定的行數是相等的。

    父索引通常是用來決定行應加在模型的什麼位置。在本例中,我們只有一個頂層的字符串列表,所以我們插入空字符串到這個列表就可以。

bool StringListModel::insertRows(int position, int rows, const QModelIndex&parent)
{
     beginInsertRows(QModelIndex(),position, position+rows-1);
     for (int row = 0; row< rows; ++row) {
        stringList.insert(position, "");
     }
     endInsertRows();
     return true;
}

    模型首先調用beginInsertRows()函數以通知其它部件行數將要改變。這個函數指定了所插入行的首行和末尾行的行號,以及父項的模型索引。當改變了字符串列表後,再調用endInsertRows()函數以完成操作並通知其它部件模型的維度已經改變,同時返回true值表明插入行的操作成功。

    從模型中刪除行的函數寫起來也很簡單。通過給出位置和行數就可以從模型中刪除指定的行。爲了簡化我們的實現,我們忽略了父索引,只從字符串列表中刪除對應的項。

bool StringListModel::removeRows(int position, int rows, const QModelIndex&parent)
{
    beginRemoveRows(QModelIndex(), position, position+rows-1);
     for (int row = 0; row< rows; ++row) {
        stringList.removeAt(position);
     }
     endRemoveRows();
     return true;
}

    beginRemoveRows()函數必須要在刪除任何底層數據之前調用,並且要指定刪除行的首行和末尾行。這樣就可以讓其它部件在數據失效前存取數據。行被刪除後,模型發射endRemoveRows()信號以完成操作,並通知通知其它部件模型的維度已經改變。

    通過使用QListView 類將模型的項展現在一個垂直列表表格中,我們就可以顯示這個模型或其它模型提供的數據。對於字符串列表模型,視圖同時也提供了一個默認的編輯器,以便對項進行操作。

    在模型子類化參考這節文檔中詳細地討論了子類化QAbstractItemModel的要求,同時爲那些必須實現的虛函數提供指引,以便在不同類型的模型中賦予各種特性。

七、項視圖便利類

    Qt4 同時也引入了一些標準部件以提供傳統的基於項的容器部件。這些部件的作用跟Qt3裏的項視圖類相似,但爲了使用底層的模型/視圖框架以改善性能以及方便維護,已經對這些類進行了重寫。這些舊的項視圖類在兼容庫中仍然是可用的

    這些基於項的部件已經按他們的用途進行命名:QListWidget提供項的一個列表,QTreeWidget顯示多層次樹形結構, QTableWidget提供單元格項的一個表格。每一個類都繼承基類 QAbstractItemView的行爲,這個基類實現了項選擇和表頭管理等一些常用的行爲。

1、列表組件

    單層次列表的項用一個 QListWidget和一些QListWidgetItems來顯示。列表部件的構造跟任何其它部件一樣:

QListWidget *listWidget = new QListWidget(this);

    列表項可以在構造的時候直接的添加到列表部件中:

new QListWidgetItem(tr("Sycamore"), listWidget);
  new
 QListWidgetItem(tr("Chestnut"), listWidget);
  new
 QListWidgetItem(tr("Mahogany"), listWidget);

    列表項在構造時也可以不指定父列表部件,而是在之後添加到列表中:

QListWidgetItem *newItem = new QListWidgetItem;
  newItem->setText(itemText);
  listWidget->insertItem(row, newItem);

    列表裏的每個項都可以顯示文字標識和一個圖標。表現文字的顏色和字體也可以可以改變,以便爲項提供一個自定義的外觀。工具提示, 狀態欄提示, 以及“這是什麼”等幫助功能都可以很容易地設定,以確保這個列表可以完全地跟應用程序融合在一起。

newItem->setToolTip(toolTipText);
  newItem->setStatusTip(toolTipText);
  newItem->setWhatsThis(whatsThisText);

    默認情況下,列表裏的項按照他們創建時的順序來顯示。列表項可以按照Qt::SortOrder指定的規則進行排序,以產生一個按升序或降序排列的列表:

listWidget->sortItems(Qt::AscendingOrder);
  listWidget->sortItems(Qt::DescendingOrder);

2、樹形組件

    樹形或層次結構型的列表項由QTreeWidget 和QTreeWidgetItem類提供。樹形部件裏的每個項都可以有它們自己的子項,同時也可以顯示多列信息。樹形部件的創建跟其它部件一樣:

QTreeWidget *treeWidget = new QTreeWidget(this);

    在把項加到樹形部件之前,必須先設定列數。例如,我們可以定義兩列,然後創建一個表頭,以便在每一列的頂部提供一個標識:

treeWidget->setColumnCount(2);
  QStringList headers;
  headers<< tr("Subject") << tr("Default");
  treeWidget->setHeaderLabels(headers);

    爲每一列建立標識最容易的方法就是提供一個字符串列表。對於更復雜的表頭,可以構造一個樹形項,按照你的要求進行佈置,並把它作爲樹形部件的表頭來使用。

    樹形部件的頂級項以樹形部件作爲它們的父部件來構造。它們可以以任意的順序插入,或者在構造每個項時,你可以通過指定前一個項來確保以特定的順序列出這些項:

 QTreeWidgetItem *cities = new QTreeWidgetItem(treeWidget);
 cities->setText(0,tr("Cities"));
 QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
 osloItem->setText(0, tr("Oslo"));
 osloItem->setText(1, tr("Yes"));
 QTreeWidgetItem *planets = new QTreeWidgetItem(treeWidget, cities);

    樹形部件處理頂層項的方法跟處理其它層次的項的方法稍有不同。樹形組件的頂層項可以通過調用樹形部件的takeTopLevelItem()函數來進行刪除,而其它低層次的項要通過調用它們父項的 takeChild()函數來進行刪除。頂層項的插入用insertTopLevelItem()函數,而其它層次的項要用父項的insertChild()函數來插入。

    在樹型部件的頂層項及其它層級的項之間移動是很容易的。我們只需檢測這些項是否是頂層項,這個信息由每個項的parent()函數提供。比如,我們可以刪除樹形部件裏的當前項,而不用去管它的具體位置是什麼:

QTreeWidgetItem *parent = currentItem->parent();
  intindex;
  if(parent) {
     index = parent->indexOfChild(treeWidget->currentItem());
     delete parent->takeChild(index);
  } else{
     index =treeWidget->indexOfTopLevelItem(treeWidget->currentItem());
     delete treeWidget->takeTopLevelItem(index);
     }

    把項插入到樹形部件的某個位置也是使用一樣的模式:

QTreeWidgetItem *parent = currentItem->parent();
  QTreeWidgetItem *newItem;
  if(parent)
     newItem = new QTreeWidgetItem(parent,treeWidget->currentItem());
  else
     newItem = new QTreeWidgetItem(treeWidget, treeWidget->currentItem());

3、表格組件

    表格部件跟電子表單程序裏用到的相似,都是用QTableWidget和 QTableWidgetItem構造。它們提供一個帶表頭的可滾動表格,而把項置於其中來使用。
    在構建表格部件時可以指定行數和列數,未定義大小的表格部件也可以在需要的時候再加上行數和列數。

QTableWidget *tableWidget;
  tableWidget = new QTableWidget(12, 3, this);

項可以在添加到表格的指定位置之前,在表格之外單獨構造:

QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(pow(row, column+1)));
 
 tableWidget->setItem(row, column, newItem);

通過在表格外構造項,並把它們作爲表頭使用,就可以添加表格的水平和垂直表頭:

QTableWidgetItem *valuesHeaderItem = newQTableWidgetItem(tr("Values"));
  tableWidget->setHorizontalHeaderItem(0, valuesHeaderItem);

注意,表格的行號和列號是從0開始的。

4、共有特性

    有幾個基於項的特性是每個項視圖簡便類共有的,它們都可以通過每個類的相同接口來進行使用。接下來的章節中我們將通過使用不同組件的幾個例子來說明這些特性。對於所使用到得函數,可以通過查看 Model/View Classes列表中的每個部件來獲得更詳細的內容。

A、隱藏項

    有時需要在一個項視圖組件中隱藏項是有用的,而不是刪除項。以上所有部件的項都可以隱藏和重現。可以通過調用isItemHidden()函數知道一個項是否被隱藏,同時用setItemHidden()函數可以將項隱藏。
因爲這個操作是基於項的,所以相同的函數適用於所有便利類。

B、選擇

    項的選擇方式由部件的選擇模式(QAbstractItemView::SelectionMode)控制。這個屬性控制用戶是否可以選擇一個或多個項,如果是多項選擇,選擇是否必須是一個連續的項範圍。以上所有組件的選擇模式的工作方式都是一樣的。

    單項選擇:當用戶需要在一個部件中選擇一個單一項時,默認的SingleSelection模式是最適合的。這種模式的當前項和選中項是一樣的。

wKiom1hCYWHzbOWhAAAwNlcvuoA129.png

    多項選擇:使用這種模式,用戶可以在不改變當前選擇的情況下切換這個部件中任意項的選擇狀態,跟非排他性的複選框單獨切換的方式很相似的。

wKiom1hCYXzBUZhGAABLqJWUNME095.png

    擴展選擇:需要選擇許多相鄰項的部件,如電子製表,就要使用到擴展選擇模式ExtendedSelection。使用這種模式,部件裏連續範圍的項可以同時用鼠標和鍵盤進行選擇。如果使用修改鍵,也可以創建複雜的選擇,如涉及到部件裏跟其它選中的的項不相鄰的多個項的選擇。如果用戶在選擇一個項時沒有使用修改鍵,那原來的選擇就會被清除。

wKiom1hCYZ7xmX2kAABEOsBa5Fo256.png

    部件中被選中的項可以用 selectedItems()函數來讀取,它提供一個可以遍歷的相關項的列表。例如,我們可以用下面的代碼找出一個選擇項列表中所有數值的總和:

QList<QTableWidgetItem*> selected = tableWidget->selectedItems();
  QTableWidgetItem *item;
  intnumber = 0;
  doubletotal = 0;
  foreach(item, selected)

{
    bool ok;
    double value = item->text().toDouble(&ok);
    if(ok && !item->text().isEmpty())

   {
      total += value;
      number++;
   }
 }

    對於單選模式,當前項是被選中的。而對於多選模式和擴展模式,當前項不一定在選擇範圍內,這取決於用戶選擇的方式。

 

C、搜索

    無論是作爲一個開發人員還是作爲一項面向用戶的服務,能夠在一個項視圖中找出項是很有幫助的。所有3種項視圖便利類提供了一個通用的findItems()函數,使得這個功能可以儘可能的一致和簡單。
    項是以Qt::MatchFlags中的一個值所指定的規則,按照項的文字進行搜索的。我們可以用findItems()函數得到一個符合要求的項的列表。

QTreeWidgetItem *item;
  QList<QTreeWidgetItem *> found = treeWidget->findItems(
        itemText, Qt::MatchWildcard);
  foreach(item, found) {
     treeWidget->setItemSelected(item, true);
     [i][color=#8b0000]// Showthe item->text(0) for each item.[/color][/i]
  }

    上面的代碼得到的結果是,樹形部件中的項如果包含搜索字符串中的文字就會被選中。這個模式同樣可以應用於列表部件和表格部件。

八、項視圖的拖放

    模型/視圖框架完全支持Qt的拖放基礎。列表、表格、樹形部件中的項可以在視圖間拖動,數據可以以MIME類型的格式進行導入和導出。

    標準視圖自動支持內部的拖放,他們的項可以被移動以改變他們的顯示順序。默認情況下,這些視圖是不能進行拖放操作的,因爲他們被設定成最簡單最常用的用法。如果要拖動項,則要開啓視圖的一些屬性,並且項本身也必須是允許拖動的。

    相比那些完全支持拖放的模型,只允許項從一個視圖中導出而不允許數據放入到其中的模型是很少的。
    更多關於在新模型中啓用拖放支持的內容,請看子類化模型參考這一章節。

1便利類視圖的使用

    QListWidgetQTableWidget和 QTreeWidget的每一種類型的項都默認配置有一套不同的標識。比如,QListWidgetItem或 QTreeWidgetItem的初始標識是啓用的enabled, 可複選的checkable,可選擇的selectable,同時可以作爲拖放操作的數據源;QTableWidgetItem可以進行編輯操作,並且可以作爲拖放操作的目標。

    雖然所有的標準項都有一或兩個標識用於拖放操作,但是你還是要在視圖本身設置不同的屬性,利用內置的拖放支持。

    要啓用項的拖動,就要把視圖的dragEnabled 屬性設定爲true

    要允許用戶將內部或外部項拖放到視圖中,則要把視圖的viewport()的 acceptDrops屬性設定爲true。

    要顯示當前拖動的項將被放在什麼地方,則要設定視圖的showDropIndicator屬性。它會提供關於項在視圖中的放置位置的持續更新信息。

    例如,我們可以用以下代碼在列表部件中啓用拖放功能。

QListWidget*listWidget = new QListWidget(this);
listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
listWidget->setDragEnabled(true);
listWidget->viewport()->setAcceptDrops(true);
listWidget->setDropIndicatorShown(true);

    結果就得到一個可以讓項在視圖裏進行復制的列表部件,甚至可以讓用戶在包含相同類型數據的視圖間拖動項。這兩種情況,項是被複制而不是移動。

    如果用戶要在視圖間移動項,那我們就要設定列表部件的拖放模式dragDropMode。

listWidget->setDragDropMode(QAbstractItemView::InternalMove);

2Model/View類使用

    建立一個可以拖放的基於模型的視圖跟簡便類視圖是一樣的模式。例如,可以用建立QListWidget一樣的方法建立一個QListView:

QListView *listView = newQListView(this);
listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
listView->setDragEnabled(true);
listView->setAcceptDrops(true);
listView->setDropIndicatorShown(true);

    因爲視圖所顯示的數據的存取是由模型控制的,所以模型也要提供對拖放操作的支持。模型支持的動作可以通過重新實現QAbstractItemModel::supportedDropActions()函數來指定。下面的代碼實現複製和移動的操作:

Qt::DropActionsDragDropListModel::supportedDropActions() const
{
     return Qt::CopyAction |Qt::MoveAction;
}

    雖然可以指定一個Qt::DropActions裏的值的任意組合,但是還是要寫模型來支持他們。例如,爲了讓一個列表模型能正確地支持 Qt::MoveAction動作,模型必須重新實現QAbstractItemModel::removeRows()函數,不管是直接還是間接從他的 基類繼承實現。

3、開啓項的拖放

    通過重新實現QAbstractItemModel::flags()函數來提供合適的標識,模型指示視圖哪些項可以拖動,哪些項接受放置。

    例如:通過確保返回的標識包含Qt::ItemIsDragEnabled 和 Qt::ItemIsDropEnabled,一個基於QAbstractListModel的簡單列表模型就可以使每個項都可以拖放:

Qt::ItemFlagsDragDropListModel::flags(const QModelIndex &index) const
{
     Qt::ItemFlags defaultFlags= QStringListModel::flags(index);
     if (index.isValid())
         returnQt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
     else
         returnQt::ItemIsDropEnabled | defaultFlags;
}

    注意,項可以被放置在模型的頂層,而拖動操作只對合法項有效。

    在以上的代碼中,因爲這個模型是從QStringListModel中衍生出來的,所以我們要調用它的flags()函數實現以包含一套默認的標識。

4導出數據的編碼

    在拖放操作中,當項的數據從一個模型中導出時,它們會被編碼成對應一個或多個MIME類型的適當的格式。模型通過重新實現返回一個標準MIME類型的列表QAbstractItemModel::mimeTypes()函數,來聲明它們可以供項使用的MIME類型。
例如,一個只提供純文本的模型要提供以下的實現:

QStringListDragDropListModel::mimeTypes() const
{
     QStringList types;
     types <<"application/vnd.text.list";
     return types;
}

    模型同時還要提供對公開格式的數據進行編碼的代碼。這個可以通過重新實現QAbstractItemModel::mimeData()函數提供一個QMimeData對象來實現,就像在其它的拖放操作裏一樣

    以下代碼的功能是索引參數相關的項數據將被編碼爲純文本並被存儲在QMimeData對象

QMimeData*DragDropListModel::mimeData(const QModelIndexList &indexes) const
{
     QMimeData *mimeData = new QMimeData();
     QByteArray encodedData;
     QDataStream stream(&encodedData, QIODevice::WriteOnly);
     foreach (QModelIndex index, indexes) {
         if (index.isValid()){
             QString text =data(index, Qt::DisplayRole).toString();
             stream <<text;
         }
     }
    mimeData->setData("application/vnd.text.list",encodedData);
     return mimeData;
}

    由於向函數提供了一個模型索引的鏈表,所以在層次結構和非層次結構中使用這種方法一般來說是足夠了的。
    注意,自定義的數據類型必須聲明爲meta objects,並且要爲它們實現流操作。詳細內容請看QMetaObject類裏的描述。

5、向模型中插入釋放的數據

    任何指定模型處理釋放數據的方法要看它的類型(列表,表格或樹形)以及它向用戶顯示其內容的方法而定。通常,累積釋放數據的方法是最適合模型底層數據存儲的方法。

    不同類型的模型會用不同的方法處理釋放的數據。列表和表格模型只提供一個存儲項的平面結構。因此,當數據被釋放到視圖中的一個現有的項上面時,它們可以插入新的行(和列),或者使用提供的數據覆蓋掉模型裏項的內容。樹形模型一般是向他們的底層數據增加包含新數據的子項,因此它的行爲會比用戶所能想到的更有可預見性。

    釋放數據的處理通過重新實現模型的QAbstractItemModel::dropMimeData()函數來實現。例如,一個處理簡單字符串列表的模型可以提供一個實現來分別處理放置於現有項之上的數據以及放置於模型頂層的數據(例如,放置到一個無效的項上面)。

    模型首先要確保操作應該作用於的數據是以可用的格式提供的,並且它在模型裏的目標是有效的:

bool DragDropListModel::dropMimeData(const QMimeData *data,
     Qt::DropAction action,int row, int column, const QModelIndex &parent)
{
     if (action ==Qt::IgnoreAction)
         return true;
     if (!data->hasFormat("application/vnd.text.list"))[/font]
         return false;
     if (column > 0)
         return false;

    如果提供的數據不是純文本,或給出的用於放下的列號是無效的,則這個簡單的單列字符串列表模型可以將此操作標誌爲失敗。

    根據數據是否被放置在一個現有的項上面作爲判斷,插入模型的數據將作不同的處理。在這個簡單例子中,我們允許把數據放在現有項之間,列表第一個項之前,和最後一個項之後。

    當一個放下操作發生時,如果父項相對應的模型索引是有效的,意味着放下操作發生在一個項上面,如果是無效的,則意味着放下操作發生在視圖中對應於模型頂層的某個位置。

int beginRow;
     if (row != -1)
         beginRow = row;

    我們先檢查指定的行號看它是否可以用來將項插入到模型中,不管父項的索引是否有效:

else if (parent.isValid())
         beginRow =parent.row();

    如果父項索引是有效德爾,則放下操作發生在一個項上。在這個簡單的列表模型中,我們找出項的行號,並用這個值把放下的項插入到模型的頂層。

else
    beginRow =rowCount(QModelIndex());

    當放下動作發生在視圖的某個位置,同時行號又是不可使用的,那我們就把項添加在模型的頂層項。
    在層次結構模型中,當放下動作發生在一個項上時,把要插入的項作爲該項的子項插入到模型中會更好。在這裏講的簡單例子中,模型只要一層,因此這個方法是不適合的。

6、解碼導入數據

    每個dropMimeData()的實現同時也必須對數據進行解碼, 並把它插入到模型的底層數據結構中。
    對應一個簡單的字符串列表模型,編碼後的項可以被解碼並匯入到 QStringList::

  QByteArray encodedData =data->data("application/vnd.text.list");
     QDataStreamstream(&encodedData, QIODevice::ReadOnly);
     QStringList newItems;
     int rows = 0;
     while (!stream.atEnd()) {
         QString text;
         stream >> text;
         newItems <<text;
         ++rows;
     }

    字符串就可以插入到底層數據。爲了保持一致性,可以通過模型自己的接口實現:

insertRows(beginRow,rows, QModelIndex());
     foreach (QString text,newItems) {
         QModelIndex idx =index(beginRow, 0, QModelIndex());
         setData(idx, text);
         beginRow++;
     }
     return true;
}

    注意,模型通常要提供 QAbstractItemModel::insertRows()函數和 QAbstractItemModel::setData()函數的實現。

九、代理模型

    在模型/視圖框架中,單個模型提供的數據項可以共享於任意多個視圖中,並且每個視圖都可能以完全不同的方式顯示相同的信息。自定義視圖和委託是爲相同數據提供根本不同的呈現方式的有效方法。然而,應用程序經常要提供常規的視圖用到相同數據的不同處理版本上,如一個列表項的不同排序視圖。

    雖然看上去好像適合作爲視圖的內部參數來執行排序和篩選操作,但是這種方法不允許多個視圖共用這種潛在的代價高昂的操作所得的結果。另外一種方法,涉及在模型本身內部的排序,同樣會導致相似的問題:每一個視圖都必須顯示根據最新的處理操作所組織的數據項。

    爲了解決這個問題,模型/視圖框架使用代理模型來管理單獨模型和視圖之間的信息。從視圖角度看,代理模型是跟普通模型表現一樣的組件,可以存取代表該視圖的源模型的數據。不管有多少個代理模型放在視圖和源模型之間,模型/視圖框架使用的信號和槽會確保每一個視圖都能及時的更新。

1、代理模型使用

    代理模型可以安插在一個現有的模型和任意數量的視圖之間。Qt提供了一個標準的代理模型,QSortFilterProxyModel,它通常被實例化就可直接使用,但它也可以子類化以提供自定義的篩選和排序行爲。QSortFilterProxyModel可以以下面的方式使用:

  QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(parent);
     filterModel->setSourceModel(stringListModel);
     QListView *filteredView = new QListView;
     filteredView->setModel(filterModel);

    由於代理模型是從QAbstractItemModel繼承而來的,所以它可以連接到各種視圖,並共用於視圖間。他們也可以通過一個管道途徑的安排,用來處理從其它代理模型得到的信息。
    QSortFilterProxyModel類被設計爲可實例化並直接在程序中使用。子類化這個類並實現所要求的比較操作,可以創建更多自定義的代理模型。

2、自定義代理模型

    通常情況下,代理模型所使用的處理形式涉及將每個項的數據從他的源模型原始位置映射到代理模型中的任意一個不同的位置。在有些模型中,一些項可能在代理模 型中沒有對應的位置;這些模型就是篩選代理模型。視圖使用代理模型提供的模型索引存取項,以及那些在這個模型中沒有包含源模型信息或源項位置的項。

    QSortFilterProxyModel 使得源模型的數據在提供給視圖之前可以被篩選,同時允許源模型的數據以排好序的數據提供給視圖。

3、自定義篩選模型

    QSortFilterProxyModel類提供了一個相當通用多變的篩選模型,可以用於各種常見得情況。對於高級使用者,可以子類化QSortFilterProxyModel來提供一個能夠執行自定義篩選的機制。

    子類化QSortFilterProxyModel可以重新實現兩個虛函數,當請求或使用代理模型的模型索引時要調用這兩個函數:

filterAcceptsColumn()函數用於篩選源模型中指定的列

filterAcceptsRow()函數用於篩選源模型中指定的行

    以上兩個QSortFilterProxyModel函數的默認實現返回true,以確保所有的項都可以傳遞給視圖;重新實現這些函數應該返回false以篩選出單獨的行和列。

4、自定義排序模型

    QSortFilterProxyModel 實例使用Qt內建的qStableSort()函數來建立源模型項和代理模型項之間的映射,在不改變源模型結構的情況下將一個排序後的項顯示到視圖上。爲了提供自定義的排序行爲,就要重新實現 lessThan()函數以執行自定義的比較。

5、自定義數據模型

    QIdentityProxyModel實例不會排序和過濾源模型的結構,但提供了一個數據代理的基類。在QFileSystemModel外,這對於不同文件的背景角色提供不同的顏色可能是有用的。

十、模型的子類化參照

模型子類化需要提供QAbstractItemModel基類定義的多個虛函數的實現。需要實現的函數數量取決於模型的類型,比如是否提供簡單列表、表格、複雜層次項的視圖。繼承自QAbstractListModelQAbstractTableModel的模型能夠利用這兩個基類默認的函數實現。以類樹形結構顯示數據項的模型必須提供QAbstractItemModel的多個函數實現。

在子類模型中需要實現的函數分爲三組:

A、項數據處理:需要實現函數的所有模型需要使視圖和委託能夠查詢模型的維度、檢查項和獲取數據

B、導航和索引創建:多層次模型需要提供視圖能夠導航顯示樹形結構的函數,並且獲取項的模型索引

C、拖放支持和MIME類型處理:模型需要繼承內外拖放操作控制方式的函數。這些函數允許其他組件和應用程序能夠理解的MIME方式進行描述數據。

1、項數據處理

模型能夠提供多種層次的數據訪問方式,比如簡單的只讀組件、重繪操作支持、可編輯。

A、只讀訪問

    爲了提供對模型數據的只讀訪問,必須對模型子類的以下函數重新實現。

    flags()用於其他組件獲取模型的每個項信息。在大多數模型中,需要使用Qt::ItemIsEnabledQt::ItemIsSelectable的組合。

    data()用於提供視圖和委託項數據。通常,模型只需要提供Qt::DisplayRole和任何應用程序用戶指定的角色的數據,但提供Qt::ToolTipRoleQt::AccessibleTextRoleQt::AccessibleDescriptionRole角色的數據也是很好的練習。關於每個角色關聯的類型信息在文檔中查閱Qt::ItemDataRole枚舉類型。

    headerData()爲了顯示錶頭,提供帶信息的視圖。這些信息只能由可以顯示錶頭的視圖獲取。

    rowCount()提供模型中要顯示的數據的行數。

    這四個函數必須在所有模型類型中實現,包括QAbstractListModel的子類列表模型和QAbstractTableModel的子類表格模型。

    此外,以下函數必須在QAbstractTableModelQAbstractItemModel的直接子類中實現。

    columnCount()提供模型要顯示的數據的列數。列表模型由於已經在QAbstractListModel中實現了,所以不需要提供這個函數。

2、可編輯項

可編輯模型允許數據項可以修改,提供插入和刪除行和列的函數。爲了開啓可編輯,下面的函數需要正確實現。

    flags()必須返回每個項相近的標識組合。函數的返回值必須包含Qt::ItemIsEditable標識。

    setData()用於修改由模型索引指定的數據項。爲了接受用戶輸入,這個函數需要處理和Qt::EditRole有關的數據。函數實現需要接受由Qt::ItemDataRole指定的多種角色關聯的數據。在數據項改變後,模型必須發送dataChanged()信號通知變化的其他組件。

    setHeaderData()用於修改水平和垂直的表頭信息。數據項改變後,模型必須發送headerDataChanged()通知變化的其他組件。

3、重繪模型

所有模型支持插入和刪除行,表格模型和層次模型支持插入和刪除列。在模型的維度變化前後通知其他組件是重要的。以下函數的實現是爲了允許模型重繪。但函數實現必須確保合適的函數被調用通知相關的視圖和委託。

    insertRows()用於在所有的模型中插入行和數據項。函數實現在插入新行到任何底層數據結構前必須調用beginInsertRows()函數,在插入行後必須立即調用endInsertRows()函數。

    removeRows()用於刪除所有模型中包含的行和數據項。函數實現中在插入新的列到底層數據結構前需要調用beginRemoveRows()函數,插入新列後必須立即調用endRemoveRows()函數。

    insertColumns()用於在表格模型和層次模型中添加新的行和數據項。函數實現中在從任何底層數據結構中刪除行之前需要先調用beginInsertColumns()函數,刪除後必須立即調用endInsertColumns()函數。

    removeColumns()用於刪除表格模型和層次模型中的列和數據項。函數實現中在從任何底層數據結構中刪除列前必須調用beginRemoveColumns()函數,刪除後必須立即調用endRemoveColumns()函數。

    通常,如果操作成功,這些函數應該返回true。但是,有一些操作部分成功的情況,比如插入的行數少於指定的插入行數。在這樣的情況下,模型應該返回alse指示失敗,使其他相關組件處理這種情況。

    在調用重繪API函數中發送的信號會給相關組件在數據不可用前採取措施的機會。開始和結束函數中對插入和刪除的封裝使模型能夠正確管理永久模型索引。

    通常,開始和結束函數能通知其他組件有關模型底層結構的變化。對於模型結構的複雜變化,可能會涉及到內部數據的重組或排序,發送layoutChanged()信號引起所有相關的視圖更新是必須的。

4、模型數據的惰性填充

    模型數據的惰性填充允許模型的信息請求延緩到實際視圖需要時。

    有些模型需要從遠程數據源獲取數據,或是必須執行耗時的操作爲了獲取數據組織方式的信息。由於視圖通常要求儘可能多的信息爲了精確顯示模型數據,爲了減少不必要的重複數據請求,限制返回信息的數量是有用的。

    在層次模型找到所給項的孩子的數量是代價昂貴的操作,確保模型的rowCount()實現只在需要時調用是有用的。本例中,hasChildren()函數需要一種不昂貴的方式重新實現,QTreeView,爲父項繪製適合的外觀。

    hasChildren()函數重新實現是返回true還是false,爲了弄清有多少個孩子顯示而調用rowCount()函數對於視圖來說並不需要。例如,如果父項沒有擴展顯示子項,QTreeView不必清楚有多少子項。

    如果知道有多少項有子項,重新實現hasChildren()函數中無條件返回true有時是可用的方法。當儘可能快地做模型數據的填充時,這可以確保每個項隨後可以檢查子項。缺點是在用戶試圖顯示不存在的子項時,沒有子項的項在有些視圖中不能正確顯示。

5、導航和模型索引創建

    爲了導航顯示的樹形結構和獲取項的模型索引,層次模型需要提供視圖能夠調用的函數。

    顯示到視圖的結構由底層數據結構決定,每個模型子類由通過提供以下函數的實現創建自身的模型索引決定。

    index() 通過父項的模型索引,函數允許視圖和委託訪問項的子項。如果沒有合法的子項能被找到,函數必須返回QModelIndex()無效的模型索引。

    parent()提供給定子項的父項的模型索引,如果模型索引指定的是模型中的頂層項,或是模型中沒有合法的父項,函數必須返回一個無效的模型索引。

6、拖放支持和MIME類型處理

    model/view類支持拖放操作,對於很多程序來說是提供有效的默認行爲。然而,也可能是定義在拖放操作間項被編碼的方式,他們默認是被拷貝還是移動,還是如何插入一個存在的模型中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章