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

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

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

1、Model/View框架簡介

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

一、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之間經過信號-槽機制來進行通信:app

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

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

二、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等搭配使用。

七、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

2、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

二、模型索引

    爲了確保數據顯示與數據訪問分離,引入了模型索引的概念。經過Model獲取的數據項能夠經過模型索引顯示。Views和Delegates都使用索引來訪問數據項,而後顯示出來。所以,只有Model須要瞭解如何獲取數據,而且Model管理的數據類型能夠定義地很是普遍。模型索引包含一個指向建立它們的Model的指針,這樣能夠避免使用多個Model時引發混淆
 QAbstractItemModel *model = index.model();

    模型索引爲信息塊提供了臨時參照,經過它能夠用來提取或修改Model中的數據。因爲Model常常會從新組織內部的結構,使得模型索引失效,所以不該保存模型索引。若是須要一個對信息塊的長期參照,必須建立一個永久的模型索引。這樣會爲不斷更新的Model信息提供一個參照。臨時模型索引由QModelIndex類提供,而永久模型索引則由QPersistentModelIndex類提供。

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

三、行數和列數

    在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()函數來做爲他們的父項參照

四、父項

    當在一個表格或列表視圖中使用數據時,模型提供的表格型數據接口是較爲理想的。行列號系統正好映射到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);

五、數據項的角色

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

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

wKioL1hCX8yhWbt9AACX8lStoK4767.png

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

六、總結

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

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

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

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

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

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

七、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提供的標準接口能夠建立新的模型。

八、自定義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()

返回表頭信息。

3、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的默認行爲足以應付大多的應用程序,它們提供了基本的編輯功能,也能夠定製特殊的需求。

三、單個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

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

4、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這個例子演示瞭如何創建一個專門用於表格視圖的自定義委託。

二、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,他們跟程序的整合性就會下降,所起的做用也很小。

5、項視圖的選擇處理

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

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

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

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

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

    當前項與選中項

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

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

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

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

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

    在處理選擇時,考慮使用QItemSelectionModel來記錄一個項模型中全部項的選擇狀態是頗有幫助的。只要創建一個選擇模型,項的集合就能夠選擇,撤銷選擇,或者反向選擇,而不須要知道哪些項已經被選擇。任什麼時候候均可以提取全部被選擇項的索引,同時經過信號和槽機制,其餘的部件也能夠知道選擇 模型的變更。

二、選擇模型的使用

    標準視圖類提供了大多數應用程序均可以使用的默認選擇模型。一個視圖所使用的選擇模型能夠經過視圖的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() 函數能夠肯定一個指定的項是不是另外一層次項的父項。

6、自定義Model

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

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

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

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

一、模型設計

    當爲一個現有的數據類型建立一個新的模型時,考慮哪種類型的模型適合於爲數據提供接口是很重要的。若是數據結構能夠用一個列表或表格的項來表示,能夠子類化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的要求,同時爲那些必須實現的虛函數提供指引,以便在不一樣類型的模型中賦予各類特性。

7、項視圖便利類

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

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

一、列表組件

    單層次列表的項用一個 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);

二、樹形組件

    樹形或層次結構型的列表項由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());

三、表格組件

    表格部件跟電子表單程序裏用到的類似,都是用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]
  }

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

8、項視圖的拖放

    模型/視圖框架徹底支持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()函數的實現。

9、代理模型

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

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

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

一、代理模型使用

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

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

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

二、自定義代理模型

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

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

3、自定義篩選模型

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

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

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

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

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

4、自定義排序模型

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

5、自定義數據模型

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

10、模型的子類化參照

模型子類化須要提供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中實現了,因此不須要提供這個函數。

二、可編輯項

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

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

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

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

三、重繪模型

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

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

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

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

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

    一般,若是操做成功,這些函數應該返回true。可是,有一些操做部分紅功的狀況,好比插入的行數少於指定的插入行數。在這樣的狀況下,模型應該返回alse指示失敗,使其餘相關組件處理這種狀況。

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

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

四、模型數據的惰性填充

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

    有些模型須要從遠程數據源獲取數據,或是必須執行耗時的操做爲了獲取數據組織方式的信息。因爲視圖一般要求儘量多的信息爲了精確顯示模型數據,爲了減小沒必要要的重複數據請求,限制返回信息的數量是有用的。

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

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

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

五、導航和模型索引建立

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

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

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

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

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

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

相關文章
相關標籤/搜索